Как я добавила групповой коммит в свою LSM‑базу на Go и не пожалела
- суббота, 6 июня 2026 г. в 00:00:16
Синхронный WAL очень частое явление в базах данных, делая их durability максимальной. При таком исходе каждый батч записи это вызов fsync, и это дало мне 956k opr/s на 16кб значениях, звучит хорошо, но на самом деле: скорость записи упала в 5 раз.
В этой статье я расскажу:
Что такое групповой коммит на пальцах
Почему групповой коммит не для финтеха
Как это реализовано у меня
Как изменились цифры до и после внедрения
Во сколько раз ScoriaDB с group commit быстрее BadgerDB и Pebble.
Если вы пишете хранилище, логгер, кэш или просто любите копаться в LSM‑движках — добро пожаловать на борт, нас ждет короткое путешествие.
Представьте, если бы в вашем мессенджере нельзя было отправить кучу фоток выделяя галочками.
Вам нужно скинуть другу их 10 штук, приходится отправлять поштучно, а каждая фотография это отдельное сообщение и уведомление, что весьма неудобно и времязатратно.
Но, как и в математике, где что‑то общее выносят за скобку, для группы фоток можно вынести их отправку.
Групповой коммит это почти тоже самое, что отправка фоток одной пачкой. Вместо того, чтобы писать все записи по отдельности и после каждой делать запрос на диск, fsync делается только на пачку.
Групповой коммит работает по таймеру, в течение которого накапливаются данные. Не по количеству, как с фотками ( напомню, что чаще всего одним сообщением 10 фоток) потому что идея считать сколько записей не очень производительна.
И этот интервал времени, по мере которого данные будут принадлежать одной пачке, необходимо задать — в ScoriaDB это 10 мс.
В ScoriaDB этот режим включён по умолчанию. Для финансовых задач не рекомендуется групповой коммит.
Потому что там недопустима потеря записи.
Если питание отключится за эти 10 мс — пачка транзакций исчезнет безвозвратно.
Чтобы не произошло катастрофы там используют синхронный режим: fsync после каждой записи.
От группового коммита стоит отказаться в любом сценарии, где потеря одной записи влечет репутационный или денежный ущерб.
Как отключить?
При открытии базы передайте параметр GroupCommitEnabled: false через внутренний API. (В след релизе появится простой флаг в публичном конструкторе)
Логи, метрики, трейсы. Если при краше вы потеряете последние 10 мс логов — мир навряд ли рухнет.
Разработка и тестирование. Когда база гоняется локально и накапливает тонны данных — скорость важнее, чем мифический краш посередине теста.
Любая аналитика, где важна пропускная способность, а не каждая запись. Например, видеонаблюдение.
Кэш, сессии, временные данные. Они и так живут в памяти, а на диске, как подстраховка от перезапуска.
Большие пакетные вставки данных (ETL, миграции, бэкапы). Там важна итоговая сумма, а не каждая промежуточная запись.
IoT‑сенсоры, показания счётчиков. Если потеряется — ничего страшного, следующий цикл перезапишет. Но строго нет, если это датчик критичный или повлечет денежные убытки, даже за 10мс.
Игровая аналитика (действия игроков). Потеря нескольких кликов за 10 мс незаметна на фоне общего трафика. Но покупки внутри игры — уже финансы, там отдельно.
У него есть три простых вещи:
Буфер — обычная переменная в памяти. Вы кидаете туда свои записи, они лежат и ждут своей очереди.
Интервал — 10 мс.
Канал для сигнала «Сбросить на диск сейчас» — при закрытии базы можно сбросить все, даже если таймер еще не прошел.
По истечению таймера, или сигналу из канала, он вызывается.
Смотрит буфер: если там ничего нет, спим дальше.
Что‑то пришло? — тогда:
Берёт всё из буфера
Записывает в файл WAL
Просит диск это зафиксировать через fsync
Очищает буфер
Для этого продуман мьютекс. Один заходит — закрывает, кладёт запись, выходит — открывает. Остальные ждут своей очереди.
Где лежит код?
Вот ссылка на тот файл в репозитории:
Железо: Intel Core i3-1215U (8 потоков), NVMe SSD
Среда: Linux, Btrfs (тоже имеет значение)
Размер | ops/s | Задержка |
|---|---|---|
16 байт | 956 000 | 1 126 нс |
4 КБ | 215 564 | 4 639 нс |
Размер | ops/s | Задержка | Прирост |
|---|---|---|---|
16 байт | 1 438 000 | 695 нс | +62% |
4 КБ | 224 300 | 4 458 нс | +4% |
Для маленьких значений прирост значительный — в 1,62 раза. Для 4 КБ разница скромнее, ведь узкое место уже не WAL, а копирование данных в Value Log и работа с диском (проблема, которую я решу в след. релизе).
Групповой коммит сильнее всего ускорил сам журнал предзаписи
Режим WAL | ops/s | Задержка (нс/оп) | Прирост |
|---|---|---|---|
Синхронный (fsync на запись) | 2 392 000 | 418 | — |
Групповой коммит (таймер 10 мс) | 10 537 000 | 94,9 | +340% |

Режим | ops/s | Задержка (нс/оп) | Прирост |
|---|---|---|---|
Синхронный | 1 432 000 | 698 | — |
Групповой коммит | 5 917 000 | 169 | +313% |
Масштабируемость на 8 потоках улучшилась в 4,1 раза — групповой коммит снимает конкуренцию за мьютекс WAL.
Также он никак не влияет на чтение — MVCC гарантирует, что читатели не блокируются писателями.
git clone https://github.com/f4ga/ScoriaDB.git cd ScoriaDB go test -bench='BenchmarkPut' -benchtime=5s -count=5 -benchmem ./pkg/scoria/
Цифры будут зависеть от вашего железа. У меня проц слабоват и, кажется, эти бенчи кратно сократили жизнь ноуту... С современным диском и >8 ядер, может быть ещё быстрее. Будет интересно, что выйдет на HDD.
Все бенчмарки запущены на одном железе
База данных | Режим записи | Durability | ops/s (16 байт) |
|---|---|---|---|
ScoriaDB | group commit (таймер 10 мс) | Подтверждение диском через 5 мс (в среднем), max 10 мс | 1 438 000 |
Pebble | sync per op (fsync на каждую запись) | Каждая операция гарантированно на диске после возврата | 472 000 |
BadgerDB | sync per op (fsync на каждую запись) | Каждая операция гарантированно на диске после возврата | 171 000 |
Для справки: ScoriaDB в режиме batch sync (fsync на каждый батч, без группового коммита) даёт 956 000 ops/s — это тоже выше, чем у конкурентов
ScoriaDB с групповым коммитом даёт в 3 раза больше операций в секунду, чем Pebble, и в 8,4 раза больше, чем BadgerDB.
Таблица иллюстрирует, какой выигрыш даёт переход к групповому коммиту. Но открою секрет, она и без него была в пару раз быстрее.
Контракты надежности принципиально разные, но Групповой коммит в ScoriaDB можно отключить.
Групповой коммит — это честный инженерный компромисс.
Плюс: 1,44 млн операций в секунду (прирост в 1,6×).
Минус: задержка до 10 мс и потеря последней группы при краше.
ScoriaDB все же отличается своей сборкой решений. Здесь есть MVCC (многоверсионность), Column Families (отдельные LSM‑деревья), встроенный gRPC/REST‑сервер, и все не на плюсах, а на гошке.
Заходите, смотрите, критикуйте и предлагайте, не забудьте поддержать проект звездочкой, автору будет приятно.
README на двух языках, дока только на английском.
Если вы знаете хранилища на Go, где есть групповой коммит, то поделитесь пожалуйста, потому что я не смогла найти такого.
Спасибо, что дочитали до конца. Буду рада поддержать обсуждение и ответить на вопросы, надеюсь удастся прочитать ваши issues или PR.