SharedStorage/README.md

262 lines
14 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

В байтах (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 бита).
---
# ⚙️ Примерная формула расчета памяти:
```text
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)
```
---
# 🧪 Мини-псевдокод для любого языка
```pseudo
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`.
---
# Конфиг
Пример:
```toml
# Список баз данных
[[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
```
## Замечания:
Если параметры не заданы для конкретной базы, используются значения по умолчанию:
```cpp
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**. <br>Это количество слотов команд (PUT/DELETE/FIND), которые могут одновременно находиться в памяти. Если очередь заполнится — новые команды временно не смогут быть отправлены. <br>**Важно**: shared memory выделяется исходя из этого размера, поэтому очередь большого размера требует больше памяти. |
| `max_memtable_size` | `size_t` | **Максимальный размер активной Memtable в байтах**. <br>Когда объем данных в Memtable превышает это значение, Memtable сбрасывается на диск в SSTable-файл. <br>**Если значение маленькое** — будет много мелких SSTable-файлов. <br>**Если значение большое** — реже будет происходить сброс, но потребление оперативной памяти будет выше. |
| `l0_queue_capacity` | `size_t` | **Размер очереди файлов для компакции L0 уровня**. <br>Когда Memtable сбрасывается в SSTable, файл ставится в очередь на компакцию. Этот параметр задает максимальное количество файлов, которые можно держать в очереди одновременно. <br>**Если значение маленькое** — компакция будет происходить чаще. <br>**Если большое** — будет задержка в обработке компакции при большом потоке вставок. |
| `estimated_element_size` | `size_t` | **Оценка среднего размера одного элемента в Memtable**. <br>Используется для **приближенного** расчета реального размера Memtable. Нужно, чтобы **не замерять точно каждый вставленный элемент**, а быстро определять когда пора сбрасывать Memtable. <br>**Типичный случай**: текстовые значения без больших вложений имеют размер 64-256 байт. |