2025-04-21 15:48:39 +03:00
2025-04-21 14:08:42 +03:00
2025-04-20 23:15:44 +03:00
2025-04-21 15:48:39 +03:00
2025-04-20 18:35:14 +03:00
2025-04-20 18:24:28 +03:00
2025-04-21 14:08:42 +03:00
2025-04-20 23:32:04 +03:00

В байтах (C++ layout):

Смещение Размер Тип данных Описание
0 1 uint8_t (atomic) ready (0 = не готова, 1 = готова)
1 1 uint8_t op — код операции (PUT = 0, DELETE = 1, FIND = 2)
2 16 uint8_t[16] key — хэш ключа (Hash128, 128 бит)
18 4 uint32_t value_size — длина данных value
22 1024 uint8_t[1024] value — содержимое для записи (PUT)
1046 4 uint32_t response_size — длина ответа в response
1050 1024 uint8_t[1024] response — содержимое ответа после FIND
2074 1 uint8_t (atomic) response_ready (0 = нет ответа, 1 = ответ готов)
--- --- Итого: 2075 байт на одну команду

⚠️ Реально в памяти компилятор добавит выравнивание.
Обычно команда занимает 2080 байт с учетом паддинга (например, до 8 байт).


2. Структура очереди SharedCommandQueue

Смещение Размер Тип данных Описание
0 8 size_t (atomic) head (где следующий push)
8 8 size_t (atomic) tail (где следующий pop)
16 8 size_t capacity (количество слотов)
24 2080 × N Command[N] Массив команд

N = максимальное количество команд в очереди. Например: 64.


📦 Процесс записи команды (PUT, FIND, DELETE)

  1. Клиент читает head.
  2. Вычисляет next_head = (head + 1) % capacity.
  3. Проверяет, что next_head != tail (иначе очередь заполнена, нужно ждать).
  4. Пишет данные в слот Command по индексу head:
    • ставит ready = 0
    • заполняет op, key, value_size, value
    • обнуляет response_size, response_ready
  5. После полной записи устанавливает ready = 1 (memory_order_release).
  6. После этого обновляет head = next_head (memory_order_release).

🧩 Процесс чтения команды сервером

  1. Сервер читает tail.
  2. Если tail == head, значит нет команд (ждет).
  3. Читает слот Command по индексу tail.
  4. Проверяет ready == 1.
  5. Выполняет команду:
    • для PUT, DELETE ничего не нужно возвращать
    • для FIND после обработки:
      • заполняет response
      • устанавливает response_size
      • устанавливает response_ready = 1 (memory_order_release)
  6. После обработки увеличивает tail = (tail + 1) % capacity (memory_order_release).

📑 Коды операций

Код Операция
0 PUT
1 DELETE
2 FIND

📈 Диаграмма

Client (head)                       Server (tail)
  ↓                                        ↑
[ empty ][ empty ][ empty ][ empty ][ empty ]
    ↑ write PUT -> ready=1                 ↑ read -> process -> tail++
    ↑ write FIND -> ready=1                 ↑ read -> process -> response_ready=1

📋 Минимальная инструкция для других языков

  1. Открыть или создать POSIX shared memory (/shm_rates1).
  2. Маппить размер (например: 24 + 2080×64 байта = ~133 120 байт).
  3. Знать layout структуры SharedCommandQueue и Command.
  4. Атомарно обновлять head/tail через обычные load/store (x86/arm архитектуры обеспечивают это через aligned операции на 64 бита).

⚙️ Примерная формула расчета памяти:

total_size = 24 + (round_up(sizeof(Command)) × capacity)
  • 24 байта на head + tail + capacity
  • capacity = число команд
  • sizeof(Command) = около 2080 байт

Важно

  • head и tail — должны быть атомарными.
  • Доступ к слотам только по индексам (head % capacity) или (tail % capacity).
  • Все указанные поля фиксированы и известны по смещению.
  • Все строки (value, response) — это просто байтовые массивы без \0.

📦 Памятка: Структура SharedCommandQueue в памяти

Общая структура:

Смещение (байты) Размер Описание
0 8 head (size_t, atomic)
8 8 tail (size_t, atomic)
16 8 capacity (size_t, НЕ atomic)
24 2080 × N Команды Command[] (N штук подряд)

📦 Памятка: Структура одного Command

Смещение Размер Описание
0 1 byte ready (atomic<uint8_t>)
1 1 byte op (uint8_t)
2 16 bytes key (Hash128, 128 бит)
18 4 bytes value_size (uint32_t)
22 1024 bytes value (байтовый массив данных)
1046 4 bytes response_size (uint32_t)
1050 1024 bytes response (ответ от сервера)
2074 1 byte response_ready (atomic<uint8_t>)
20752079 5 bytes padding (выравнивание до 8 байт)

Суммарный размер одной команды с паддингом = 2080 байт.


📋 HEX Layout всего буфера (SharedCommandQueue + команды)

00:00 - 00:07 -> head (size_t, LE)
00:08 - 00:0F -> tail (size_t, LE)
00:10 - 00:17 -> capacity (size_t, LE)

00:18 - 00:18+2080*N -> N команд, каждая:
    00:00 -> ready (1 byte)
    00:01 -> op (1 byte)
    00:02 - 00:11 -> key (16 bytes)
    00:12 - 00:15 -> value_size (4 bytes, LE)
    00:16 - 04:15 -> value (1024 bytes)
    04:16 - 04:19 -> response_size (4 bytes, LE)
    04:20 - 08:1F -> response (1024 bytes)
    08:20 -> response_ready (1 byte)
    08:21 - 08:27 -> padding (5 bytes)

🧪 Мини-псевдокод для любого языка

ptr = mmap(...)

head  = read_uint64(ptr, 0)
tail  = read_uint64(ptr, 8)
capacity = read_uint64(ptr, 16)

command_base = 24

// Пример: прочитать первую команду (index = 0)
offset = command_base + index * 2080

ready = read_uint8(ptr, offset + 0)
op = read_uint8(ptr, offset + 1)
key = read_bytes(ptr, offset + 2, 16)
value_size = read_uint32(ptr, offset + 18)
value = read_bytes(ptr, offset + 22, value_size)
response_ready = read_uint8(ptr, offset + 2074)
response_size = read_uint32(ptr, offset + 1046)
response = read_bytes(ptr, offset + 1050, response_size)

📊 Краткая таблица памяти:

Поле Тип Офсет Размер
head size_t (8B) 0 8B
tail size_t (8B) 8 8B
capacity size_t (8B) 16 8B
commands Command[N] 24 2080×N

📢 Важные правила при использовании

  • Всегда выравнивание по 8 байтам (иначе UB на ARM/M1).
  • Использовать little-endian (x86, arm64 всегда LE).
  • При копировании строк следить за value_size / response_size.
  • Проверять флаг ready перед чтением команды.
  • После обработки команды — инкрементировать tail мод capacity.
  • Ответ через response + response_size + response_ready только для FIND.

Конфиг

Пример:

# Список баз данных
[[database]]
name = "rates1"

[[database]]
name = "test"

# Настройки базы rates1
[databases.rates1]
shm_queue_capacity = 2048        # Размер очереди команд в shared memory (количество команд)
max_memtable_size = 2097152      # Максимальный размер активной Memtable в байтах (до сброса на диск)
l0_queue_capacity = 2048         # Размер очереди для фоновой компакции L0 SSTables
estimated_element_size = 256     # Оценка среднего размера одного элемента в Memtable в байтах

# Настройки базы test
[databases.test]
shm_queue_capacity = 1024
max_memtable_size = 1048576
l0_queue_capacity = 1024
estimated_element_size = 128

Замечания:

Если параметры не заданы для конкретной базы, используются значения по умолчанию:

shm_queue_capacity = 1024
max_memtable_size = 1048576
l0_queue_capacity = 1024
estimated_element_size = 128

Все размеры (max_memtable_size, estimated_element_size) указываются в байтах.

Shared Memory для каждой базы будет создана с именем /shm_<имя_базы>.

Очереди (l0_queue, shm_queue) построены на lock-free кольцевых буферах.

Полное описание параметров конфига

Параметр Тип Описание
shm_queue_capacity size_t Размер очереди команд в shared memory.
Это количество слотов команд (PUT/DELETE/FIND), которые могут одновременно находиться в памяти. Если очередь заполнится — новые команды временно не смогут быть отправлены.
Важно: shared memory выделяется исходя из этого размера, поэтому очередь большого размера требует больше памяти.
max_memtable_size size_t Максимальный размер активной Memtable в байтах.
Когда объем данных в Memtable превышает это значение, Memtable сбрасывается на диск в SSTable-файл.
Если значение маленькое — будет много мелких SSTable-файлов.
Если значение большое — реже будет происходить сброс, но потребление оперативной памяти будет выше.
l0_queue_capacity size_t Размер очереди файлов для компакции L0 уровня.
Когда Memtable сбрасывается в SSTable, файл ставится в очередь на компакцию. Этот параметр задает максимальное количество файлов, которые можно держать в очереди одновременно.
Если значение маленькое — компакция будет происходить чаще.
Если большое — будет задержка в обработке компакции при большом потоке вставок.
estimated_element_size size_t Оценка среднего размера одного элемента в Memtable.
Используется для приближенного расчета реального размера Memtable. Нужно, чтобы не замерять точно каждый вставленный элемент, а быстро определять когда пора сбрасывать Memtable.
Типичный случай: текстовые значения без больших вложений имеют размер 64-256 байт.
Description
No description provided
Readme 171 KiB
Languages
C++ 95.7%
CMake 3.2%
C 1.1%