Tarantool это замечательное высокопроизводительное no-Sql решение, разработка компании Mail.Ru. Исходники
Данное решение позволяет использовать как режим key/value, так и выборку множества записей в рекордсет по одному или нескольким критериям (полям поиска). Аналогов в рунете и не только, я пока не встречал. С натяжкой можно сравнить редис. Но в редисе — списковые данные и их нельзя выбирать по ключу. Судя до утверждениям разработчиков, скорость доступа по ключу превосходит memcache, при этом еще в бэдграунде осуществляется постоянное сохранение данных на диск. Но к сожалению, данная разработка имеет единственный perl клиент для доступа к данным, из-за чего не имеет такой популярности, как например у redis или memcache.
В doc/box-protocol источников есть описание Протокола, которое я в настоящее время переработал для написания клиента на Си и PHP. Изучив Протокол, вы можете реализоать нативный клиент на любимом Вам языке. Надеюсь, данная статья в этом Вам пригодится.
Все данные в этой базе разделены на namespace, скорее всего это аналог БД в MySQL. Нумерация всех неймспейсов — цифровая ( 0, 1, 2 и тд). На каждый namespace можно наложить определенный индекс. Нумерация Индексов — так же цифровая. Индексы накладываются на одно или несколько полей. Индекс может иметь тип HASH или TREE
Все Индесы и неймспейсы прописываются в Конфиге. Ниже показан пример, где прописано два индекса, цифровой и символьный. При том, второй индекс составной:
namespace[0].enabled = 1
namespace[0].index[0].type = "HASH"
namespace[0].index[0].unique = 1
namespace[0].index[0].key_field[0].fieldno = 0
namespace[0].index[0].key_field[0].type = "NUM"
namespace[0].index[0].key_field[1].fieldno = 1
namespace[0].index[1].type = "TREE"
namespace[0].index[1].key_field[0].fieldno = 0
namespace[0].index[1].key_field[0].type = "STR"
namespace[0].index[1].key_field[1].fieldno = 1
Необходимо отметить про ключи. Ключи могут быть цифровыми (1,2,3… 6.2 *10^9 ) или символьными.
Все данные в неймспейсе хранятся в ввиде кортежей. Картеж представляет собойй набор полей. Поле может быть либо цифровым, либо символьным.
Обмен между клиентом и сервером осущестляется Сообщениями. Все Сообщения в tarantool Протокол делятся на Запрос Request и ответ Responce. Каждое Сообщение имеет обязательный Заголовок Header и так же может иметь Tело.
Заголовок Сообщения включает: тип Сообщения, длину тела и идентификатор запроса.
Структура Заголовка:
<blockquote>typedef struct { uint32_t type; uint32_t len; uint32_t request_id; } Header; </blockquote>
Определены следующие типы Сообщений:
INSERT 0xd (13)
SELECT 0x11 (17)
UPDATE 0x13 (19)
DELETE 0x14 (29)
PING 0xff 0xff 0x0 0x0 (65280)
Идентификатор запроса устанавливается клиентом и может быть нулевой.
Общая структура запроса:
typedef struct { Header header; union { InsertRequest insert; SelectRequest select; UpdateRequest insert; DeleteRequest insert; }; } Request;
Команда PING не имеет тела, по этому отсутствует PingRequest ;)
Тело команды INSERT состоит из номера namespace, над которым будет осуществляться операция, флага и кортежа.
namespace — Это пространство, в котором хранятся кортежи. Нумерация неймспейсов цифроая. В каждом неймспейсе могут быть определены индексы. Первичный индекс (PRIMARY) должен
присутствовать обязательно. Индексы определены в конфигурационном файле.
В настоящее время определен только единственный флаг BOX_RETURN_TUPLE (0x01) который показывает, вернуть ли данные в теле ответа.
Структура INSERT запроса:
typedef struct { uint32_t namespaceNo; uint32_t flag; Tuple tuple; } InsertRequest;
Все данные описываются с помощью кортежей Tuple. Кортеж состоит из поля cardinality, которое представляет собой размерность кортежа (кол-во полей) и массива полей. В общем виде это будет выглядеть так:
typedef struct { uint32_t card; Field field[]; } Tuple;
Каждое поле представлено массивом байт. Поле может иметь: int32, int64 или потоком байт.
В настоящее время я пока определил так:
typedef Field u_char * data;
Все данные полей упакованы с помощью LED128 en.wikipedia.org/wiki/LEB128
тело SELECT запроса включает: номер namespace, номер индекса, по которому происходит выборка, смещение offset и размер вывода limit и кол-во кортежей и сами кортежи. Параметры offset и limit аналогичны выборке: SELECT * FROM… LIMIT
typedef struct { uint32_t namespaceNo; uint32_t indexNo; uint32_t offset; uint32_t limit; uint32_t count; Tuple tuples[]; } SelectRequest;
Если мы указываем SELECT * FROM t0 WHERE k0=1, то кол-во кортежей=1 и значение Tuple должно соответствовать 1. Если определен вторичный составной индекс k1 (цифровое поле и символьное), то на запрос
SELECT * FROM t0 WHERE k1 = (21, 'USSR')
кол-во кортежей=2 и должно быть представлено два значения Tuple. Необходимо сделать пояснение, что представленный sql схематичный, и не соответствует стандарту SQL'92. Дело в том, что данные в tarantool/box представлены кортежами, а не таблицами (столбцы и строки). На кортеж может содержать любое кол-во полей. Все кортежи хранятся в немспейсе. Однако на мемспейс можно налажить HASH или rbTREE индекс по которому будет осуществлен поиск.
Тело UPDATE запроса включает: номер неймспейса, флаг, кортеж, кол-во операций и сами операции. Поля флаг и кортеж аналогичны операции UPDATE. Кол-во операций может быть равно нолю. Структура будет выглядеть следующим образом:
typedef struct { uint32_t namespaceNo; uint32_t flag; Tuple tuple; int32_t count; Operation operation[]; } UpdateRequest;
Каждая Операция представляет собой структуру, содержащей номер поля по которому будет проведена операция, код операции и аргумент.
Используются коды операций:
0 — присвоение аргумента данному полю.
Если аргументом является тип int32, то так же возможны следующие действия:
1 — добавить аргумент к существующему полю
2 — выполнить AND с существующем полем
3 — выполнить XOR с существующем полем
4 — выполнить OR с существующем полем
typedef struct { int32_t fieldNo; int8_t opcode; Field arg; } Operation;
Операция DELETE всегда выполняется по первичному ключу и содержит номер неймспейса и кортеж. Структура операции DELETE представлена ниже:
typedef struct { uint32_t namespaceNo; Tuple tuple; } SelectRequest;
Каждый ответ сервера содержит: Заголовок Header, код ответа и при необходимости тело ответа. Заголовок ответа — аналогичен заголовку запроса. Код возрата 0 — успех, либо смотрим ошибки в include/iproto.h
В общем виде получается следующая структура ответа:
typedef struct { Header header; int32_t code; union { SelectResponce selectBody; insertResponce insertBody; uint8_t * data; int32_t count; }; } Responce;
Тело ответа на запрос SELECT стостоит из поля содержащего общее кол-во кортежей и самого множества возвращаемых кортежей. Если результат запроса пустой, то кортежи не возвращаются и поле количества содержит ноль.
typedef struct { int32_t count; FqTuple tuples[]; } SelectResponce;
Каждый возвращаемый кортеж (FqTuple) содержит размер кортежа, некий идентификаро cardinality, который выступает как разделитель (boundaries) и самого кортежа.
typedef struct { int32_t size; uint32_t card; Tuple tuple; } FqTuple;
Если в запросе InsertRequest установлен флаг BOX_RETURN_TUPLE, то ответ может содержать тело:
typedef struct { int32_t count; FqTuple tuple; } InsertResponce;
Аналогичным является и ответ на запрос UPDATE.
Запрос Delete возвращает количество удаленый записей. Так как при удалении используется только первичный индекс, то мы можем удалить только одну запись, соответственно вернется 0 или 1. Это поле count структуры Responce. Еще в структуре выделен массив байт data для анализа данных.
Возможны в тексте есть некоторые неточности, где-то можно использоать int32_t вместо uint32_t.
Возможно что-то я недопонял, так что буду рад делововой критики от авторов этого замечательного проекта.