Payload
Payload представляет собой структуру индексированных полей фиксированного типа.
Внутренний API Payload представлен тремя классами верхнего уровня:
PayloadValue— shared-копия структуры полей записи. Размер структуры равен сумме размера полей + 16 байт заголовка.PayloadType— определение структурыPayload. Содержит вектор полей (имена, типы, размеры, смещения). На один неймспейс существует только одна копияPayloadType. Она изменяема и потокобезопасна. Изменяемость и потокобезопасность обеспечиваются за счёт COW-семантики. При изменении или добавлении полей создаётся новая копияPayloadType.PayloadIface— шаблон интерфейса для управленияPayload.- Выполняет всю низкоуровневую работу над
PayloadValue. Содержит указатели наPayloadValueиPayloadType. Существует в двух экземплярах:Payload— может изменятьPayloadValue.ConstPayload— интерфейс только для чтения, не может изменятьPayloadValue.
API Payload схож с обычным Reflection API и содержит методы Set и Get для управления полями (см. payloadiface.h для получения подробной информации).
Структура PayloadValue
PayloadValue в каждом объекте Payload имеет следующую структуру:
| Поле: | RefCount | Cap | LSN | Поле 1 | … | Поле N | Вложенные массивы |
|---|---|---|---|---|---|---|---|
| Размер в байтах: | 4 | 4 | 8 | Зависит от типа данных | Зависит от типа данных | Зависит от типа данных |
Формат хранения данных полей PayloadValue
Тривиальные типы данных, такие как int32, int64, double, хранятся в PayloadValue в нативном формате:
| Поле | Поле с содержимым с типом значения int |
Поле с содержимым с типом значения int64 |
Поле с содержимым с типом значения double |
Поле с содержимым с типом значения string |
|---|---|---|---|---|
| Размер в байтах | 4 | 8 | 8 | 8 |
Для хранения строк используется 8-байтовый слабый указатель на строку reindexer::p_string.
Важно, что само значение PayloadValue не содержит строки.
p_string
p_string — это оболочка указателя. Она использует 59-й, 60-й и 61-й биты указателя в качестве тега строкового типа (предполагается, что размер адресного пространства x86_64 равен 2^48: https://en.wikipedia.org/wiki/X86-64#Virtual_address_space_details).
| Тег | Объект, на который он указывает |
|---|---|
| 0 | С-подобная null-terminated строка |
| 1 | 4-байтовый заголовок строки (len), за которым следует массив символов строки |
| 2 | std::string |
| 3 | Заголовок (varint len), за которым следует массив символов строки |
| 4 | std::string_view |
| 5 | reindexer::key_string |
| 6 | json-строка. Указывает на 3 байта длины. Если в 3-м байте старший бит == 0, эти 3 байта и есть длина строки, а сама строка лежит в памяти непосредственно перед длиной (начало по адресу ptr - len). Это строки менее 8 Мб.Если в 3-м байте старший бит == 1, то у этой строки длина закодирована 4 байтами (ptr-1, ptr, ptr+1 и ptr+2). Перед этими 4-мя байтами лежат 8 байт, где закодирован указатель на саму строку |
| 7 | msgpack-строка. Содержит указатель на l_msgpack_hdr (4 байта на длину + указатель на строку) |
key_string
key_string является иммутабельной строкой со счетчиком ссылок, а также содержит экспортированный заголовок с полем размер.
| Размер в байтах | Поле |
|---|---|
| 4 | Счетчик ссылок |
| 4 | Экспортированная длина строки |
| N | Строковые данные, находяфщиеся непосредственно за заголовком |
Экспортированный заголовок используется для прямого доступа к Payload из приложения, написанного не на C++ (например, когда Reindexer — часть golang-приложения).
FloatVector
FloatVector/ConstFloatVector - отдельные типы для хранения float32-векторов. Внутри себя они содержат std::unique_ptr<float32[] для хранения данных вектора, а также поле с его размерностью.
Внутри PayloadValue вектора не хранятся. Вместо этого там сохраняются только их представления: FloatVectorView/ConstFloatVectorView. FloatVectorView реализован в виде одного 64-битного числа, в которое упакован невладеющий указатель на вектор (48 бит, опционально), а также его размерность (16 бит).
Особенностью FloatVectorView является то, что он может находиться в striped-состоянии и не указывать ни на какой реальный вектор вообще. Сам же вектор в этом случае будет храниться в соответсвующем индексе и будет доступен только по внутреннему ID документа.
В случаях, когда во время выборки пользователю требуется получить реальные значения векторных полей, используется вспомогательный контейнер FloatVectorsHolderMap или FloatVectorsHolderVector, а содержимое FloatVectorView внутри PayloadValue перезаписывается таким образом, чтобы указывать на извлечённый из индекса вектор.
Хранение массивов в PayloadValue
Массивы хранятся в виде заголовка массива (ArrayHeader), который содержит 4-байтовое начальное смещение, и 4-байтового количества элементов в массиве.
| Поле | Поле 1 Массив Смещение + количество элементов |
Поле N | Массив 1 элемент 1 |
… | Массив N элемент N |
|---|---|---|---|---|---|
| Размер в байтах | 4+4 | Зависит от типа данных | Зависит от типа данных | Зависит от типа данных |
Пример структуры:
struct
{
int64_t f1 = 5;
int f2[] = {6,7,8};
int f3 = 10;
string f4 = "abc";
}
PayloadValue для данного примера:
| Поле | Размер | Смещение | Значение |
|---|---|---|---|
| f1 | 8 | 0 | int64_t(5) |
| f2.offset | 4 | 8 | uint32_t(28) |
| f2.count | 4 | 12 | int32_t(3) |
| f3 | 4 | 16 | int32_t(10) |
| f4 | 8 | 20 | &string(abc) |
| f2[0] | 4 | 28 | int32_t(6) |
| f2[1] | 4 | 32 | int32_t(7) |
| f2[2] | 4 | 36 | int32_t(8) |
Жизненный цикл и владение
Поведение PayloadValue похоже на поведение умного указателя std::shared_ptr. PayloadValue увеличивает внутренний счетчик ссылок при копировании, уменьшает этот счетчик в деструкторе и удаляет хранимые данные, если значение счетчика ссылок равно 0. Счетчик ссылок PayloadValue потокобезопасен.
PayloadValue может быть в одном из трех состояний:
Free: структура данных не выделена.Non Shared: данные структуры принадлежат исключительноPayloadValue.Shared: данные структуры совместно используются с другимиPayloadValue.
PayloadValue в состоянии Shared не допускает модификации.
Перед любой модификацией код должен создать его копию путем вызова PayloadValue::Clone ().
Clone проверит состояние PayloadValue и при необходимости выделит новую структуру либо создаст эксклюзивную копию.
PayloadValue не владеет своими строками по умолчанию.
Для контроля владения строками существуют 2 метода: PayloadIface::AddRefStrings () и PayloadIface::ReleaseStrings ().
AddRefStringsиReleaseStringsне работают по идиоме RAII, поэтому код должен сам контролировать баланс их вызовов. Если код не вызоветReleaseStrings, возникнет утечка памяти.
Пример типового использования
PayloadType type = ns->payloadType_;
PayloadValue value = ns->items_[index];
// Create control object
ConstPayload payload(type, value);
VariantArray keyRefs;
// Dump all fields to stdout
for (int i = 0; i < payload.NumFields(); i++) {
auto &field = payload.Type().Field(i);
printf("\n%s=", field.Name().c_str());
for (auto &elem : payload.Get (i,keyRefs)) {
printf("%s", Variant(elem).toString().c_str());
}
}