NoSQL
3 February 2011

Первый опыт работы с Handler Socket & php_handlersocket

handlersocket

Немного вскружилла голову статья Использование MySQL как NoSQL — История о том, как достичь 750,000 запросов в секунду (Перевод моего друга Вадима). Есть и другие материалы по этой теме. И вот дошли руки до экспериментов.

Под PHP разработано три разных клиента:
extension code.google.com/p/php-handlersocket
PEAR openpear.org/package/Net_HandlerSocket
PHP native github.com/tz-lom/HSPHP

Ниже приведены мои впечатления о первых экспериментах.

Не буду пересказывать статью про установку. Если Вы устанавливали MySQL из исходников — у Вас не должно возникнуть проблем.

После установки handlersocket и выполении команды SHOW PROCESSLIST; должно появится неч-то следующее:

handlersocket passive

В нагруженном состоянии картинка будет приблизительно такой
handlersocket active

по кол-ву загруженных процессов мы можем видеть загрузку handlersocket. На изображении видно, что открыто три соединения, одно из них активно (выполняется).

Кол-во рабочих тредов можно регулировать в my.conf параметрами:
loose_handlersocket_threads = 16
loose_handlersocket_threads_wr = 1


Если вы собрали хоть одно расширение под РНР, то и с установкой php-hanlpersocket не должно возникнуть проблем. Немного смущает отсутствие документации. К выбору примера и наглядности особых претензий нет. Тестировалось как на MyISAM, так и InnoDb.

Выборка по PRIMARY KEY (на примере) прошла на ура. Пример есть в документации на php-handlersocket.
Поиск выборки осуществляется как по операции равно "=", также возможны операции:
больше '>',
меньше '<'
больше или равно '>='
меньше или равно '<='

C выборкой по символьным индексам необходимо учитывать кодировку. При необходимости, делать преобразования используя iconv, иначе ни чего не найдете и потеряете много времени.

Пример выборки по ключу name:
CREATE TABLE `test`.`cities` (
 `id_city` int(10) unsigned NOT NULL AUTO_INCREMENT,
 `id_region` int(10) unsigned NOT NULL,
 `id_country` mediumint(8) unsigned NOT NULL,
 `city_name` varchar(255) NOT NULL,
 `city_order` int(10) unsigned NOT NULL,
 PRIMARY KEY (`id_city`),
 KEY `id_region` (`id_region`),
 KEY `name` (`city_name`)
) ENGINE=MyISAM


$hs = new HandlerSocket($host, $port);

if (!($hs->openIndex(0, $dbname, $table, 'name', 'city_name,id_city,id_region')))
{
  echo $hs->getError(), PHP_EOL;
  die('error open index');
}

$retval = $hs->executeSingle(0, '=', array('Ярцево'), 10);



Ответ:
array(1) {
[0]=> array(2) {
[0]=> string(6) "Ярцево"
[1]=> string(4) "1976"
[2]=> string(1) "72"
}
}


Можно организовать выборку по всему региону (id_region=1):
if (!($hs->openIndex(0, $dbname, $table, 'id_region', 'city_name,id_city,id_region')))
{
  die($hs->getError());
}
$retval = $hs->executeSingle(0, '=', array(1), 100, $off);

Константа 100 — кол-во выборочных записей, $off — смещение, аналогично LIMIT,OFFSET в SELECT.
Ответом будет массив из 100 или менее элементов, аналогично выполнению SQL: SELECT 'id_region', 'city_name,id_city,id_region' FROM cityes WHERE id_region=1 LIMIT 100,$off;

Например, удобно, при реализации автокомплита, используя операции (знаки) '>=' или '<=' можно выбрать необходимое кол-во слов начинающихся с опредленных букв. Отследить диапазон стандартными средствами невозможно, но если данные брать порциями и их проверять на удовлетворению второму условию, то можно осуществлять в принципе любые простые выборки.

Протокол. Хочется отметить, что при разработке его знание очень помогает в отладке. Запросы первое время, особенно когда что-то не получается, постоянно приходится сравнивать с результатами полученные через выполнение в telnet. Описание Протокола лежит в файле protokol.en.txt или его перевод в статье «Введение в HandlerSocket: описание протокола и расширения php-handlersocket»

Использование Протокола очень простое: В telnet коннектимся по порту 9998 на хост БД.
telnet localhost 9998
P 0 test cities name city_name,id_city
// для разделения используем символ TAB (\t), если значение NULL, то используется двойной TAB
0 1 ---> если нет ошибки всегда выдает 0 1
/// делаем заппрос на получение данных
0 > 1 a 10
/// номер канала(int), операция (<>= ...) кол-во ключей, значение[, Limit, Offset]
0 2 A Baa 10564 A Barqueira 10565 A Corua 10566 A da Beja 15510 A dos Arcos 15511 A dos Bispos 15512 A dos Cunhados 15513 A dos Francos 15514 A Merca 11120 A Nario 13257
// в ответе код=0 (успех), рекодсет содержит два поля (city_name,id_city) в соответствии с запросом
// далее идет данные рекордсета, разделенные TAB, строка кончается BK(\n)


В заключение хочется добавить, что можно делать запросы по составным ключам, используя массивы, и мультизапросы, т.е. несколько запросов на одно соединение:

$hs->openIndex(0, $dbname, $table, 'PRIMARY', 'city_name,id_city,id_region');
$hs->openIndex(1, $dbname, $table, 'id_region', 'city_name,id_city,id_region');
$retval = $hs->executeMulti(
  array(array(0, '=', array('23'), 1, 0),
     array(1, '=', array('3'), 10, 0)));


* This source code was highlighted with Source Code Highlighter.


За гранью обзора остались операции INSERT/UPDATE/DELETE. Надеюсь в ближайшем будущим дать более подробный обзор.

Handlersocket хорошо показал в сочетании со sphinx. C посощью Сфинкса осуществляем поиск необходимых id документа, а с помощью handlersocket мгновенно выбираем необходимую информацию.

Сравнение производительности клиентов
верхняя строчка PHPHS
нижняя php-handlersocket
время грязное в микросекундах, с учетом на инклуды и инстанс класса
0.005791
0.001404
---
0.007095
0.001383
---
0.00456
0.002563
---
0.006104
0.001384


Время без инклудов и инстансов, чисто одна выборка по PK
$t1 = microtime();
$retval = $hs->executeSingle(2, '=', array('60187'));
echo microtime()-$t1, PHP_EOL;
$t1 = microtime();
$res = $rs->select('=','60187');
echo microtime()-$t1, PHP_EOL;

0.000451
0.000632
---
0.00039400000000001
0.00020700000000007
---
0.00040000000000007
0.00021399999999994
---
0.00053999999999998
0.002058
---
0.002926
0.0002089999999999
---
0.000386
0.00021100000000002


Выявленная бага
Когда отлаживался, то сбросил телнет по kill -9, что привело к зависанию сокета.
Последующие перезапуски скрипта приводили к его зависанию (сокет ожидал чтения).

SHOW PROCESSLIST процессов handlersocket не показал

Вылечилось перезапуском мускуля.

+40
9.8k 153
Comments 64