Handlersocket Protocol in detail

    Hello everyone. I decided to publish the Russian version of my own article, “HandlerSocket protocol explained,” published at http://wk-photo.ru/en/events/view/handlersocket-protocol-explained/ .

    image

    So, you went, went and came to HandlerSocket . Pure honey. This is a devilishly fast voodoo. And the protocol used is really simple, like two pennies. Well, if to be honest, who cares about the details of the protocol, if anyway some kind of library is used that takes care of everything? If, in spite of everything, you still want to know what kind of neonka is inside, you can google this page . A few hours - and you are an expert. Well, or you want everything in 15 minutes. Then good deed under kat!



    It is assumed that you are already at least remotely familiar with the Handlersocket, you have MySQL / MariaDB / Percona server with the plugin turned on. I used MariaDB, the Handlersocket is there by default, you just need to enable the plugin, seasoning my.cnf with a small number of lines to taste .

    As an advertisement: https://github.com/crocodile2u/zhandlersocket - my extension for PHP to work with Handlersocket.

    Go. I log in to the MariaDB console and create a table:

    CREATE TABLE movie (
      id int not null auto_increment primary key, 
      genre varchar(20) not null, 
      title varchar(100) not null, 
      view_count int default 0, 
      key(genre)
    ) engine innodb;


    Now connect to the Handlersocket. Port 9999, that is, the default port for writing: with this connection, I can both read and write. Since usually you still need to read more often than write, it will be more efficient to think in advance what actions will be needed in the application and open the connection to the port for reading or reading / writing, respectively. For reference: by default, the Handlersocket starts 16 workers for processing read-only connections and one worker for processing read-write connections.

    ~/ telnet localhost 9999
    Trying 127.0.0.1...
    Connected to localhost.
    Escape character is '^]'.


    Class. Now try to talk with this thing. The first thing to do when using the Handlersocket is to open the index. For any queries you send to HS to find or modify data, you need an index identifier. Let's see what the documentation says: The

    request " open_index " has the following syntax:

    P /indexid/ /dbname/ /tablename/ /indexname/ /columns/ [/fcolumns/]


    • / indexid / is a number in decimal.
    • / dbname /, / tablename /, and / indexname / are strings. To open the primary key, use PRIMARY as / indexname /.
    • / columns / is a comma-separated list of column names.
    • / fcolumns / is a comma-separated list of column names. This parameter is optional.


    Well, everything is clear ... Oh, and whose pants with a motor are what is this / indexid /? This is the index identifier your application should provide. It is not generated by the Handlersocket. In fact, the application can send a request, consisting of a number of lines, to HS simultaneously:

    Open index 1
    Find row(s) in index 1
    Find/Modify row(s) in index 1
    ...


    This is only possible if the index ID is assigned by the application, otherwise you would have to first send a request to HS to open the index, get an identifier from HS, and only then use this identifier in your queries.

    We will continue our exercises in the HS console (telnet). Forgot to say, request parts in HS are separated by TABs. In our example, I use the optional parameter / fcolumns / for the first index. We still need it.

    ~/ telnet localhost 9999
    ...
    P   1   test    movie   id,genre,title,view_count   genre
    2   1   idxnum


    What is it? Doca claims that in return we should get "0 1"! A. Well, yes, it's my fault, I forgot the / indexname / parameter.

    P   1   test    movie   PRIMARY id,genre,title,view_count   genre
    0   1


    Now the order. We are going to knock on the table with the primary key, so that / indexname / we will have PRIMARY. Let's open another HS index, for a MySQL index called genre:

    P   2   test    movie   genre   id,genre,title,view_count
    0   1


    You, you will feed. And this one opened. Let's now add lines to the plate? What do we have in the documentation?

    The 'insert' request has the following syntax.

    /indexid/ + /vlen/ /v1/ ... /vn/


    • / vlen / indicates the length of the trailing parameters / v1 / ... / vn /. This must be smaller than or equal to the length of / columns / specified by the corresponding 'open_index' request.
    • / v1 / ... / vn / specify the column values ​​to set. For columns not in / columns /, the default values ​​for each column are set.

    ... and we should get an answer like this:

    If 'insert' is succeeded, HanderSocket returns a line of the following syntax.

    0 1


    Let's try ...

    1   +   3   0   Sci-Fi  Star wars
    0   1   1


    Oops The answer is not quite like in the dock. Additional unit. Let's see what happens with the table in the MariaDB console:

    SELECT * FROM movie;
    +----+--------+-----------+
    | id | genre  | title     |
    +----+--------+-----------+
    |  1 | Sci-Fi | Star wars |
    +----+--------+-----------+


    It worked! Come on, come on ...

    1   +   3   0   Comedy  Dumb & Dumber
    0   1   2
    1   +   3   0   Thriller    The Silence of the Lambs
    0   1   3


    Again look at MariaDB:

    SELECT * FROM movie;
    +----+----------+--------------------------+
    | id | genre    | title                    |
    +----+----------+--------------------------+
    |  1 | Sci-Fi   | Star wars                |
    |  2 | Comedy   | Dumb & Dumber            |
    |  3 | Thriller | The Silence of the Lambs |
    +----+----------+--------------------------+


    Oops! The unknown third digit that HS returns is exactly the same as the AUTO_INCREMENT id field!
    Now let's try to specify the value for id manually ...

    1   +   3   1   Sci-Fi  Star Trek
    1   1   121


    OK. A “1” in the first position of the answer tells us that an error has occurred. HS, however, is not too verbose. From the additional information we only have the magic number "121", which means ... Yes, the devil knows it, actually, but I think that in such a simple way we are hinted at an error like "DUPLICATE KEY". One more time…

    1   +   3   4   Sci-Fi  Star Trek
    0   1   0


    We look in MariaDB, our new record is added with ID = 4:

    SELECT * FROM movie;
    +----+----------+--------------------------+
    | id | genre    | title                    |
    +----+----------+--------------------------+
    |              ...                         |
    |  4 | Sci-Fi   | Star Trek                |
    +----+----------+--------------------------+


    Something like this, we insert records into tables via HS and get the values ​​of the AUTO_INCREMENT field. The documentation is silent about this. Perhaps because in the old days HS did not support AUTO_INCREMENT.

    Be careful with columns that have dynamic defaults . Such as "created_at DATETIME DEFAULT CURRENT_TIMESTAMP" or "updated_at DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP". When inserts and updates, you must set their values ​​manually ! Otherwise, instead of the current time stamp, when you insert into the created_at field, you will get "0000-00-00 00:00:00". And if you do not specify a value for updated_at during the update, the previous value will be there.

    We finished with INSERTs, uhh ... Now let's try to choose something from HS. We look in man ...

    The 'find' request has the following syntax.

    / indexid / / op / / vlen / / v1 / ... / vn / [LIM] [IN] [FILTER ...]

    LIM is a sequence of the following parameters.

    / limit / / offset /

    IN is a sequence of the following parameters.

    @ /icol/ /ivlen/ /iv1/ ... /ivn/


    FILETER is a sequence of the following parameters.

    /ftyp/ /fop/ /fcol/ /fval/


    • / indexid / is a number. This number must be an / indexid / specified by a 'open_index' request executed previously on the same connection.
    • / op / specifies the comparison operation to use. The current version of HandlerSocket supports '=', '/', '/ =', '/', and '/ ='.
    • / vlen / indicates the length of the trailing parameters / v1 / ... / vn /. This must be smaller than or equal to the number of index columns specified by the / indexname / parameter of the corresponding 'open_index' request.
    • / v1 / ... / vn / specify the index column values ​​to fetch.
    • LIM is optional. / limit / and / offset / are numbers. When omitted, it works as if 1 and 0 are specified. These parameter works like LIMIT of SQL. These values ​​don't include the number of records skipped by a filter.
    • IN is optional. It works like WHERE ... IN syntax of SQL. / icol / must be smaller than the number of index columns specified by the / indexname / parameter of the corresponding 'open_index' request. If IN is specified in a find request, the / icol / -th parameter value of / v1 / ... / vn / is ignored.
    • FILTERs are optional. A FILTER specifies a filter. / ftyp / is either 'F' (filter) or 'W' (while). / fop / specifies the comparison operation to use. / fcol / must be smaller than the number of columns specified by the / fcolumns / parameter of the corresponding 'open_index' request. Multiple filters can be specified, and work as the logical AND of them. The difference of 'F' and 'W' is that, when a record does not meet the specified condition, 'F' simply skips the record, and 'W' stops the loop.


    Well, everything is clear at a glance. Let's go further.
    Okay, okay, joking. Let's try to find the record by ID. First comes / indexid / - in our case it is 1 (the index that we opened for the primary key). Then / op /: we want to get the entry with id = 1, so that would be "=". Then, / vlen /. This is the number of columns in the index. An index can consist of several columns. In our case, however, this is 1. Then comes the part / v1 / ... / vn /. These are the column values, in our example one column and a value of 1:

    1   =   1   1
    0   3   1   Sci-Fi  Star wars


    Hooray, it worked! The result can be interpreted as follows:

    0 - no errors;
    3 - number of columns
    [1  Sci-Fi  Star wars] - the row that we've got from InnoDB.


    What if we try another operation (/ op /)? For example, get entries with id> 1:

    1   >   1   1
    0   3   2   Comedy  Dumb & Dumber


    How so? Only one entry? But we had more, I give a tooth! Oook, this is in the documentation: “LIM is optional. / limit / and / offset / are numbers. When omitted, it works as if 1 and 0 are specified. " Let's try to specify enough LIMIT to get all the rows that interest us.

    1   >   1   1   10  0
    0   3   2   Comedy  Dumb & Dumber   3   Thriller    The Silence of the Lambs    6   Sci-Fi  Star Trek


    All results are returned in one line:

    0 - no errors;
    3 - number of columns
    [2  Comedy  Dumb & Dumber] - row 1
    [3  Thriller    The Silence of the Lambs] - row 2
    [6  Sci-Fi  Star Trek] - row 3


    All this will need to be analyzed in the client application (we know the number of columns, we know the order in which we listed them when opening the index, so there is no problem). Maybe you ask yourself a question: what will happen if there is a tab character in our data? Still sprinkled! The protocol describes the transmission of characters from 0x00 to 0x0f. Client libraries must encode these characters using a simple algorithm (the prefix 0x01 is added to them and shifted by 0x40). The client library should take care of this. For simplicity, we will not use characters in our examples that require coding.

    In addition, we have another IN request.- that is, you can get data immediately heaps of records by their keys, in one request! I conducted several experiments. For starters, I did not know what to do with / op /, / vlen / and / v1 / ... / vn /. After all, we are going to indicate all the id that we want to get, in the IN part . Well, let's try the same as we tried with a simple search by id.

    The IN condition is "@ 0 1 2", that is, column number 0, one value, and this value is 2.

    1   =   1   1   @   0   1   2
    2   1   modop


    "Modop". Thank HandlerSocket Now everything is clear! Let's try to change a little ...

    1   =   1   0   @   0   1   2
    2   1   modop


    Again. What do you want? What if I specify LIMIT?

    1       =   1   0   1   0   @   0   1   2
    0   3   2   Comedy  Dumb & Dumber


    That's it! It seems like you need to specify LIMIT and OFFSET for IN queries. / op /, / vlen / and / v1 / ... / vn / should also be specified, although this is only confusing. In the example below, I specify a value of 1 instead of 0 in the / vn / - section and get exactly the same answer!

    1   =   1   1   1   0   @   0   1   2
    0   3   2   Comedy  Dumb & Dumber


    I will try to change / op /:

    1   >   1   1   1   0   @   0   1   2
    0   3   3   Thriller    The Silence of the Lambs


    And where is this from? Well, let's try to get more than one id:

    1   >   1   1   1   0   @   0   2   2   3
    0   3   3   Thriller    The Silence of the Lambs


    Damn, I forgot about LIMIT. We indicate that we need 2 two entries:

    1   >   1   1   2   0   @   0   2   2   3
    0   3   3   Thriller    The Silence of the Lambs    6   Sci-Fi  Star Trek


    Obviously, our ids were ignored, and HS returned records with id> 1. In the end, I found out that in the IN request you can safely specify "=" as / op /, and 0 as the value / v1 /. You may notice that we got the results exactly in the order we specified them in the IN condition. However, I do not know if this is a coincidence or not. I would not rely on it.

    Let's move on to the filters? What if we take our last request and ask HS to filter by genre = 'Sci-Fi'? Remember when we opened the index, we specified / fcolumns /? Now they are useful to apply additional filters. If we did not specify / fcolumns /, any query with filters would return “2 1 filterfld”.

    1   =   1   0   3   0   @   0   3   2   3   1   F   =   0   Sci-Fi
    0   3   1   Sci-Fi  Star wars


    It worked! OK, that’s probably all about getting records from HS. Now let's move on to UPDATEs. What about documentation?

    The 'find_modify' request has the following syntax.

    /indexid/ /op/ /vlen/ /v1/ ... /vn/ [LIM] [IN] [FILTER ...] MOD


    MOD is a sequence of the following parameters.

    /mop/ /m1/ ... /mk/


    • / mop / is' U '(update),' + '(increment),' - '(decrement),' D '(delete),' U? ',' +? ',' -? ', or' D ? '. If the '?' suffix is ​​specified, it returns the contents of the records before modification (as if it's a 'find' request), instead of the number of modified records.
    • / m1 / ... / mk / specifies the column values ​​to set. The length of / m1 / ... / mk / must be smaller than or equal to the length of / columns / specified by the corresponding 'open_index' request. If / mop / is 'D', these parameters are ignored. If / mop / is '+' or '-', values ​​must be numeric. If / mop / is '-' and it attempts to change a column value from negative to positive or positive to negative, the column value is not modified.


    The first part looks familiar. We only saw all this when we looked at FIND queries, and it looks like these are WHERE conditions for our UDPATE. Update the series with id = 1 and set view_count = 100:

    1   =   1   1   U   1   Sci-Fi  Star Wars   100
    2   1   modop


    modop. So glad. How clear! What if you specify LIMIT?

    1   =   1   1   1   0   U   1   Sci-Fi  Star Wars   100
    0   1   1


    Soooo Let's look at MariaDB:

    SELECT * FROM movie WHERE id = 1;
    +----+--------+-----------+------------+
    | id | genre  | title     | view_count |
    +----+--------+-----------+------------+
    |  1 | Sci-Fi | Star Wars |        100 |
    +----+--------+-----------+------------+


    Let's try a little more complicated. Increase view_count for all comedies by 10.

    1   /   1   0   1000    0   F   =   0   Comedy  +   0   0   0   10
    0   1   1


    Looks like a norm! Let's look at MariaDB:

    SELECT * FROM movie;
    +----+----------+--------------------------+------------+
    | id | genre    | title                    | view_count |
    +----+----------+--------------------------+------------+
    |  1 | Sci-Fi   | Star Wars                |        100 |
    |  2 | 0        | 0                        |         10 |
    |  3 | Thriller | The Silence of the Lambs |          0 |
    |  6 | Sci-Fi   | Star Trek                |          0 |
    +----+----------+--------------------------+------------+


    No, it didn’t work. At least it didn't work well. The counter really increased by 10, but also all other columns were updated. Since the id is numeric, and we said to increase it by 0, it remained untouched. I will return everything back:

    1   =   1   2   1   0   U   2   Comedy  Dumb & Dumber   10


    I can say right away that specifying a value for the “genre” and “title” columns in the increment request will not work. They will get the value “0”, just as happened on our first attempt. In order for the increment to work as it should, you need to open a separate index, which will contain only the columns "id" and "view_count":

    P   3   test    movie   PRIMARY id,view_count   genre
    0   1
    3   /   1   0   1000    0   F   =   0   Comedy  +   0   10
    0   1   1


    Well, another thing:

    SELECT * FROM movie;
    +----+----------+--------------------------+------------+
    | id | genre    | title                    | view_count |
    +----+----------+--------------------------+------------+
    |  1 | Sci-Fi   | Star Wars                |        100 |
    |  2 | Comedy   | Dumb & Dumber            |         30 |
    |  3 | Thriller | The Silence of the Lambs |          0 |
    |  6 | Sci-Fi   | Star Trek                |          0 |
    +----+----------+--------------------------+------------+


    That's all. Happy handlersocketing!

    Also popular now: