262 lines
14 KiB
Markdown
262 lines
14 KiB
Markdown
В байтах (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>) |
|
||
| 2075–2079 | 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 байт. | |