Payload

Payload представляет собой структуру индексированных полей фиксированного типа. Внутренний API Payload представлен тремя классами верхнего уровня:

  • PayloadValue — shared-копия структуры полей записи. Размер структуры равен сумме размера полей + 16 байт заголовка.

  • PayloadType — определение структуры Payload. Содержит вектор полей (имена, типы, размеры, смещения). На один неймспейс существует только одна копия PayloadType. Она изменяема и потокобезопасна. Изменяемость и потокобезопасность обеспечиваются за счёт COW-семантики. При изменении или добавлении полей создаётся новая копия PayloadType.

  • PayloadIface — шаблон интерфейса для управления Payload.

  • Выполняет всю низкоуровневую работу над PayloadValue. Содержит указатели на PayloadValue и PayloadType. Существует в двух экземплярах:

    1. Payload — может изменять PayloadValue.
    2. 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

reindexer::key_string является производным от std::string и, кроме того, содержит счетчик ссылок и экспортированный заголовок с полями указатель + размер.

Размер в байтах Поле
Меняется в зависимости от stl std::string
8 Экспортированный указатель на массив символов строки
4 Экспортированная длина строки
4 Счетчик ссылок

Экспортированный заголовок используется для прямого доступа к Payload из приложения, написанного не на c++ (например, когда Reindexer — часть golang-приложения). Данные в key_string доступны для изменения.

Хранение массивов в 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());
		}
	}