Первый опыт работы с 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 не показал

    Вылечилось перезапуском мускуля.
    Поделиться публикацией

    Комментарии 64

      0
      Какой из клиентов использовали? И почему?
      Были какие то неявности, странности и баги, кроме приведения кодировок?
        +1
        Какой из клиентов использовали? И почему?

        выбран php-handlersocket по той причине, что это враппер над libhandlersocket.
        Если сравнивать с производительностью HS клиента, то за счет медлительности самого РНР, все нативные клиенты, написанные на нем, проигрывают в производительности врапперам. Далеко ходить не надо: сравнимаем производительность мемкешед и редис клиентов. Какой используешь ты и почему?

        Про PEAR клиента — та же история, только с боку.
          0
          Какой используешь ты и почему?

          Пока никакой, не было подходящей задачи. На будующее интересуюсь. А то обычно для новых технологий: куча разных библиотек, 1 вообще не работает, во 2-ой встроенное кэширование, которое нельзя сбросить, в 3-ей 100500 багов и тишина, остальные худо-бедно работают, если знать правильные заклинания и иметь набитые шишки.
            0
            не поверю, что не используешь мемкеш!
              0
              Я думал ты про handlersocket. Для memcached использую memcache, ибо меньше глючит.
                0
                однако не нативнеый IMemcached,
                а реализованный в ввиде экстеншена.

                согл, memcached — это враппер над libmemcached, а она часто глючит. А вот авторы memcache свое творение довели до ума, реализовав практически весь протокол. Я, кстати тоже его использую.
              0
              ну про шишки ты пррравильно подметил
                +2
                +1 про шишки. Иногда офигеваешь, и только после многочасовых танцев с бубном понимаешь причину бага. Взять хотя бы авто-сериализацию в php-memcached после 2000 символов.
                  0
                  можно подробнее?
                    0
                      0
                      при выдачи nginx напрямую из мемкеша (ngx_memcached) есть небольшой патчик
                      который анализирует код возврата memcached, и если там хранилась «сжатая» информация, то выставляет соответствующие заголовки.
                      рекомендую+ съэкономите на памяти.
                        +1
                        а вообще статья зачетная, я про это тоже не знал.
                        Вообще-то в самом пхп-мемкешед модуле много магии.
                          0
                          или скорее всего в libmemcached
                            0
                            Андрей, ты потерял на мемкешед пол дня, я почти три: habrahabr.ru/blogs/php/74643/
                              0
                              Спасибо!) Так, а из-за какой именно баги это было не нашли?
                  +1
                  Были какие то неявности, странности и баги, кроме приведения кодировок?
                  Долго провозился с кодировками. Первое время думал что бага, отписал автору.

                  Решил эту проблему, только после применения нативного протокол через телнет. хотел выяснить, кто виноват: враппер, либа или ограничена возможность HS. Сперва думал, что это бага.
                  Ну, если быть откровенным, я часть функционала, связанного с memcached отлаживаю, используя телнет. Как-то Хабросообществом была недооценена моя статья, в которой я по шагам расписал, как я отлавливал багу в libmemcached. А вот по рассылкам php-hiload да и в самом Сообществе проскакивали похожие баги падения memcached. Так что хочу обратить внимание, что знание нативного протокола всегда помогает в отладке.

                  что осталось за кадром: тестируется функционал композитных ключей.
                    0
                    Ага, была такая же фигня с memcached, когда они сменили поведение при инкременте отсутствующего ключа. Текстовые протоколы, в этом плане, хороши.

                    Спасибо за ответы.
                      0
                      спасибо за хорошие вопросы
                    0
                    см комментарий один из последних комментариев про багу.
                      0
                      А у меня очень большой вопрос: HandlerSocket позволяет осуществить сортировку результата? Или тут тупо PK/K lookup, а сортировать на клиенте, теряя выигранное (и даже проигрывая еще больше)?
                        0
                        Даже хрен с ней, сортировкой, HandlerSocket отдаст записи в том же порядке, в каком были указаны ID в запросе? Типа queryMulti (ID=3,5,1,9) — записи вернутся отсортированые по ключу, по запросу или неопределено?
                          +6
                          Отвечу сразу на два вопроса.
                          1. Сортировка записей в HandlerSocket не предусмотрена.
                          2. При Multi-Get записи будут возвращаться в том порядке, в котором были указаны ID в запросе. Вот пример:

                          1.  
                          2. <?php
                          3. // connect
                          4. $hs = new HandlerSocket('127.0.0.1', 9998);
                          5. if($hs) {
                          6.     print 'Connected to HandlerSocket<br/>';
                          7. } else {
                          8.     print 'Can not connect<br/>';
                          9.     die();
                          10. }
                          11. //index
                          12. $index = $hs->openIndex(
                          13.     1,
                          14.     'mysql',
                          15.     'user',
                          16.     HandlerSocket::PRIMARY,
                          17.     'Host,User,Select_priv'
                          18. );
                          19. if($index) {
                          20.     print 'Index opened<br/>';
                          21. } else {
                          22.     print 'Can not open index<br/>';
                          23.     echo $hs->getError(), PHP_EOL;
                          24.     die();
                          25. }
                          26. $id = rand(1,1000);
                          27. // find
                          28. //$result = $hs->executeSingle(1, '=', array($id), 1, 0);
                          29.  
                          30. // Чтение данных Multi-Get (find по тому же индексу)
                          31. $retval = $hs->executeMulti(
                          32.    array(
                          33.        array(1, '=', array('localhost'), 1, 0),
                          34.        array(1, '=', array('%'), 1, 0),
                          35.        array(1, '=', array('127.0.0.1'), 1, 0)
                          36.     )
                          37. );
                          38.  
                          39. var_dump($retval);
                          40.  
                          41.  

                          ______________________
                          Текст подготовлен в Редакторе Блогов от © SoftCoder.ru

                          А вот результат:
                          1.  
                          2. Connected TO HandlerSocket<br/>Index opened<br/>array(3) {
                          3.   [0]=>
                          4.   array(1) {
                          5.     [0]=>
                          6.     array(3) {
                          7.       [0]=>
                          8.       string(9) "localhost"
                          9.       [1]=>
                          10.       string(16) "debian-sys-maint"
                          11.       [2]=>
                          12.       string(1) "Y"
                          13.     }
                          14.   }
                          15.   [1]=>
                          16.   array(1) {
                          17.     [0]=>
                          18.     array(3) {
                          19.       [0]=>
                          20.       string(1) "%"
                          21.       [1]=>
                          22.       string(6) "daobay"
                          23.       [2]=>
                          24.       string(1) "Y"
                          25.     }
                          26.   }
                          27.   [2]=>
                          28.   array(1) {
                          29.     [0]=>
                          30.     array(3) {
                          31.       [0]=>
                          32.       string(9) "127.0.0.1"
                          33.       [1]=>
                          34.       string(4) "root"
                          35.       [2]=>
                          36.       string(1) "Y"
                          37.     }
                          38.   }
                          39. }
                          40.  
                          41.  

                          ______________________
                          Текст подготовлен в Редакторе Блогов от © SoftCoder.ru
                            –2
                            Спасибо, это отличная новость. Тогда можно отсортировать и выбрать ID в нужном порядке посредством SQL, а данные уже через HandlerSocket.
                            • НЛО прилетело и опубликовало эту надпись здесь
                                +4
                                Очень даже не экзотический, а вполне работающий.
                                Это обобщенный подход, дающий лучшую среднюю производительность, чем сложная сортировка и выемка данных в одном запросе. Т.е. вместо одного запроса всегда два. В моем случае второй запрос можно заменить на HandlerSocket.

                                $IDs = SELECT id FROM megatable WHERE a=10 AND b>9000 AND c BETWEEN 1111 AND 100500 ORDER BY a DESC, c ASC, g ASC

                                $Data = SELECT * FROM megatable WHERE ID IN ($IDs) ORDER BY FIND_IN_SET(ID, $IDs) ASC
                                  0
                                  То же самое касается JOIN'ов. Собираем нужные нам id'шники по всяким разным условиям, сортируем как хотим, а непосредственно загрузку сущностей делаем через HS. По сравнению с JOIN думаю это даст хороший выигрыш на таблицах больших объемов.
                                    0
                                    см мои идеи ниже
                                    собираю предложения
                                  • НЛО прилетело и опубликовало эту надпись здесь
                                      0
                                      Ok, посмотрите план SQL-запроса с JOIN и без.
                                      Пусть t1 — полное время выполнения простого запроса (включая сетевые задержки),
                                      t2 — то же самое для сложного запроса (с JOIN'ами),
                                      t3 — общее время время выполнения запросов HS по получению сущностей.
                                      Наша идея в том, что t2 > t1 + t3.
                                      • НЛО прилетело и опубликовало эту надпись здесь
                                          0
                                          согл, нужно уметь деноморолизовать БД.

                                          В соцсетях, где используется шардинг — вообще отказались от джоинов.
                                            0
                                            Так а я о чем говорю. Делаем денормализацию, получаем меньше запросов с JOIN'ами и больше запросов по PK/K, которые отправляем через HandlerSocket.
                                      • НЛО прилетело и опубликовало эту надпись здесь
                              +1
                              А не могли бы сделать тест производительности, чтобы можно было сравнить быстродействие расширения, PEAR пакета и HSPHP? Спасибо!!!
                                0
                                РЕАR точно делать не буду, так как ./configure --without-pear
                                а HS & php-handlersocket сделаю. Выиигрыш в копейки, но все же, у кого большие нагрузки — приятно.
                                  +1
                                  сделал тест,
                                  дописал в конец темы
                                    0
                                    0.0003 микросекунды на запрос? Это что-то типа 3 млрд. запросов в секунду? :) Можете попробовать с помощью DBench протестировать, оно там умеет на автомате базу случайными данными забивать (см. example) и графики строить. Для вашего случая там надо будет только DBench_Test_Abstract отнаследовать с переопрелением protected function test() на обращение к базе через Handler Socket.
                                      +1
                                      Что-то типа 3 млрд. запросов в секунду — это что-то типа 1 такта процессора на запрос? :) Ясно же, что не может быть такой производительности.
                                        0
                                        Это был риторический вопрос, сарказм т.е. :)

                                        Автор пишет:
                                        время грязное в микросекундах, с учетом на инклуды и инстанс класса
                                        0.005791

                                        Я потому и переспрашиваю, может в данном случае имеются ввиду «секунды» и нужно подправить топик.
                                          0
                                          там все относительно
                                          показать что быстрее и в каких пропорциях
                                            0
                                            С каких пор время в 0.005791μс — это «относительно»?
                                            И я правильно понимаю, что тест содержал одну итерацию?
                                      +1
                                      Время без инклудов и инстансов, чисто одна выборка по 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;

                                      У вас там явная ошибка: вместо microtime() нужно писать microtime(true).
                                    +3
                                    Немного смущает отсутствие документации на php-hanlpersocket

                                    Возможно, немного поможет $ php --re handlersocket
                                      –1
                                      и чем же?
                                      не более чем Class synopsis¶

                                      HandlerSocket {
                                      /* Constants */
                                      const HandlerSocket::PRIMARY;

                                      /* Methods */
                                      __construct ( string $host, int $port, [ array $options ])
                                      public bool openIndex ( int $id, string $db, string $table, string $index, string $fields )
                                      public mixed executeSingle ( int $id, string $op, array $fields [, int $limit, int $skip, strint $modop, array $values ] )
                                      public mixed executeMulti ( array $requests )
                                      public int executeUpdate ( int $id, string $op, array $fields, array $values [, int $limit, int $skip ] )
                                      public int executeDelete ( int $id, string $op, array $fields [, int $limit, int $skip ] )
                                      public bool executeInsert ( int $id, array $values )
                                      public string getError ( void )
                                      }

                                        0
                                        Ну, это, конечно, не документация, поэтому и помочь может лишь немного. Но имхо, все же это лучше, чем рыться в исходниках, чтобы просто узнать, какие параметры принимает метод.
                                        0
                                        вообщее-то я в таких случаях лезу в исходники. Но тут исходники показали, что все параметры тупо передаются на сервер через libh..s…
                                        т.е. копаем логику в граблях самого HS
                                        +1
                                        мои планы:
                                        написать утилитку, аналог redis-cli,
                                        чтоб лучше отлаживать, проверять ключи. Все-таки telnet -это вчерашний день.

                                        есть желание (нет времени) доработать php-handlersocket в сторону:
                                        — фильтрация данных, чтоб это делать не средствами РНР
                                        — сюда же возможность использования простых шаблонов аля '%'
                                        — JOIN рекордсетов. Пока думаю как лучше сделать.

                                        Сегодня перевел статистику (очень тяжелый update на 17 мин) на HS. Выполняется чуть более нескольких минут. Все в режиме тестирования.
                                          0
                                          Готов принять участие в тестировании, если что.
                                          Нативная сортировка + фильтрация в расширении, это клёво. Всё лучше выносить за уровень php. А как планируется реализовать JOINы? Через серию запросов в ресширении, и возвращении полного набора данных (уже сджоиненного) в php?
                                            0
                                            с джоинами все просто:
                                            1) делаем выборку по ключу T1.key1,
                                            2) делаем выборку по ключу T2.key2 = T1.key1,
                                            выборки у нас отсортивованы по клюючам.
                                            3) начинаем итерировать полученный рекордсет T1 и класть его в массив,
                                            одновременно сливая его с рекордсетом T2.

                                            Возможные грабли или подводные камни:
                                            мы не можем вытащить весь рекодсет простым запросом (SELECT *… WHERE region_id=1). Мы обязаны указать значение LIMIT. Соответственно нужно вытягивать либо с заданным LIMIT очень большое число, либо порциями.
                                              0
                                              еще нужно заранее определить размеры буферов под рекордсеты. Можно взять по максимуму, но это не будет эффективно. можно запрашивать их динамически, зная, например, что под одну запись буден не больше 1К. В общем здесь проблемки есть
                                            0
                                            хотелось бы что-то вроде executeMulti('ID', 'sql', 'SELECT id FROM foo WHERE bar=0')
                                            чтобы избежать промежуточного хранения значений ключей в памяти средствами пхп, очень уж избыточно по 60 лишних байт на каждый элемент массива.
                                              0
                                              про память это уж верно.
                                              спасибо. Постараюсь учесть.
                                            +1
                                            что пока не выяснено, но хотелось бы:

                                            public mixed executeSingle ( int $id, string $op, array $fields [, int $limit, int $skip, strint $modop, array $values ] )
                                            последние два параметра: strint $modop, array $values, судя по описанию представляют собой аналог getset (или взять и уничтожить). Однако, у меня пока это не получилось.
                                            –1
                                              +2
                                              Емое! НЕ в тот топик шарахнул :( Сори…
                                                +1
                                                Вот вечно так… наоткрываешь страниц, а потом такие косяки вылазят.
                                              0
                                              Да, вспомнил про одну багу:

                                              когда отлаживался, то сбросил телнет по kill -9
                                              последующие запуски скрипта приводили к его зависанию.

                                              приходилось перезапускать мускуль.
                                                0
                                                SHOW PROCESSLIST процессов handlersocket не показал
                                                0
                                                Еще бы сравнение с любым из PDO/mysqli/mysqlnd с использованием prepared statements.
                                                  0
                                                  это уже лишнее, есть тесты на сравнение с мемкешом и прочими key/value хранилищами (выложу в следующем топике).
                                                    0
                                                    когда следующий топик?
                                                  0
                                                  тесты есть в презенташке моего доклада на AddConf
                                                  www.slideshare.net/akalend/handdler-socket-in-the-addconf

                                                  сейчас нет времени писать топики, работаю над другими интересными проектами,
                                                  есть положительные результаты, но выкладывать как статьи еще рано.

                                                  Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                                                  Самое читаемое