Полноформатная адаптация для Habr по мотивам статьи Adrien Obernesser «MariaDB 12.3 – Binlog Inside InnoDB».

Коротко для ленивых

  • В MariaDB 12.3 binlog можно хранить внутри InnoDB через binlog_storage_engine=innodb.

  • Главный эффект: вместо двух fsync() на commit остаётся один, поэтому на write-heavy нагрузке резко растут TPS и снижается tail latency.

  • В тестах из статьи прирост на полном durability-профиле составил примерно 2.4x–3.3x.

  • Backup, restore и ресинк реплик становятся проще, потому что binlog и данные теперь консистентны на уровне одного механизма хранения.

  • Цена за это: обязателен GTID, Galera пока не поддерживается, а innodb_log_file_size нужно подбирать внимательнее из-за роста объёма redo.

  • Если у вас обычная схема primary + async replica на InnoDB, эту возможность точно стоит хотя бы протестировать.

С MariaDB и MySQL в продакшене я регулярно сталкиваюсь с одной и той же практической проблемой: бинарный лог нужен почти всем, но платить за него лишней задержкой на каждом commit хочется далеко не всегда. Особенно это заметно на облачных дисках, где каждая лишняя синхронная запись быстро превращается в реальный потолок по TPS и неприятный хвост latency.

Поэтому новость про binlog_storage_engine=innodb в MariaDB 12.3 я воспринял не как очередную «точечную оптимизацию», а как действительно важное архитектурное изменение. В этой версии binlog можно хранить прямо внутри InnoDB, используя его WAL. Ниже рассмотрим, что именно изменилось, почему это даёт такой заметный прирост производительности, какие ограничения есть уже сейчас и где эту возможность действительно имеет смысл включать.

Почему классический binlog так дорого обходится

Любой DBA, который администрировал MariaDB или MySQL под реальной нагрузкой, хорошо знает этот компромисс: binlog нужен для репликации, восстановления и аудита, но за него почти всегда приходится платить производительностью.

Причина в классическом пути фиксации транзакции. Каждая commit-операция проходит через границу двухфазного коммита между InnoDB и бинарным логом. На практике это означает два независимых способа поддержания долговечности (durability) и, в стандартной конфигурации, два последовательных вызова fsync():

  • один для binlog;

  • второй для redo/WAL InnoDB.

И это происходит независимо от того, как вы настроили innodb_flush_log_at_trx_commit: бинарный лог всё равно живёт отдельно от InnoDB и требует собственной синхронизации.

Отсюда возникают вполне знакомые эффекты:

  • на каждом commit нужно согласовать два разных механизма поддержания долговечности;

  • после падения базы данных (crash), механизм восстановления (recovery) должен согласовать состояние InnoDB и состояние binlog;

  • при sync_binlog=1 и innodb_flush_log_at_trx_commit=1 вы всегда платите за два вызова fsync();

  • при высокой конкуренции транзакций всё это особенно больно бьёт по пропускной способности системы, увеличивая задержку.

На быстрых локальных NVMe это ещё можно пережить. На облачных дисках, где задержка синхронной записи измеряется миллисекундами, проблема становится очень заметной.

MariaDB 12.3 предлагает на это архитектурный ответ. Изменение проходит по задаче MDEV-34705, автор реализации — Kristian Nielsen. Суть в том, что binlog больше не обязан существовать как отдельный плоский файл на уровне сервера: он может жить внутри InnoDB и использовать тот же Write-Ahead Log, что и сами данные.

Это важно подчеркнуть: речь не о небольшой оптимизации, а о перепроектировании всего процесса сохранения данных (commit pipeline).

Как выглядел старый commit path

Если упростить, традиционная фиксация транзакции выглядела так:

BEGIN transaction
  → InnoDB: XA-подготовка, запись в журнал (undo/redo log)
  → Binlog: запись события в файл binlog
  → Binlog: синхронизация binlog-файла с диском — fsync() [sync_binlog=1]
  → InnoDB: commit (фиксация в журнале)
  → InnoDB: синхронизация файла InnoDB с диском — fsync() [innodb_flush_log_at_trx_commit=1]
COMMIT

Главная проблема здесь в том, что binlog и InnoDB — это два разных файла и две разные точки подтверждения записи. InnoDB не может окончательно завершить commit, пока не удостоверится, что событие уже надёжно сохранено в binlog.

Если сервер падает в неудачный момент — например, между записью binlog и финальным commit внутри InnoDB — восстановление после сбоя должно пройтись по binlog и найти подготовленные, но не завершённые транзакции. Это даёт накладные расходы в двух местах:

  • в режиме стабильной работы, когда мы платим дополнительной задержкой на commit;

  • при восстановлении (recovery), когда приходится дополнительно сканировать и согласовывать состояние.

Group commit частично помогает: несколько транзакций можно синхронизировать вместе. Но базовая цена 2PC никуда не исчезает.

Что меняется в MariaDB 12.3

С включённым binlog_storage_engine=innodb бинарный лог перестаёт быть отдельным плоским файлом. Теперь он хранится внутри табличных пространств InnoDB с расширением .ibb, а его долговечность обеспечивается собственным redo/WAL InnoDB.

В этом режиме путь фиксации упрощается до следующего:

BEGIN transaction
  → InnoDB: запись в журнал (undo/redo log) + запись события в binlog
  → InnoDB: commit (один вызов fsync(), если innodb_flush_log_at_trx_commit=1)
COMMIT

То есть двухфазный коммит между InnoDB и binlog исчезает. Изменения данных и записи бинлога становятся атомарными: они либо попадают в WAL вместе, либо не попадают вовсе.

Именно это даёт два очень серьёзных эффекта.

1. Значения параметра innodb_flush_log_at_trx_commit 0 и 2 перестают быть опасными для binlog-consistency

В старой модели ослабленные режимы flush были рискованными с точки зрения репликации: после падения процесса или ОС binlog и InnoDB могли разойтись. Данные в InnoDB уже зафиксированы, а binlog при этом неполный, или наоборот.

В новой схеме binlog находится внутри того же пути записи, что и данные. Значит, после падения системы процесс восстановления InnoDB восстанавливает консистентное состояние сразу и для таблиц, и для бинлога.

Важно понимать нюанс: это не делает значение 0 магически «безопасным» с точки зрения потери последних данных при сбое питания. Но это устраняет с��арую проблему рассинхронизации между binlog и InnoDB.

2. При innodb_flush_log_at_trx_commit=1 один fsync() заменяет два

Это самый важный прикладной выигрыш. Для наиболее типичного продакшн-профиля теперь нужен один синхронный flush WAL вместо двух. На практике именно поэтому в такой конфигурации виден такой заметный рост TPS и снижение задержки.

Дополнительно улучшаются условия для group commit: binlog больше не остаётся отдельным bottleneck.

Как теперь выглядят файлы binlog

При новом режиме MariaDB создаёт binlog-файлы с расширением .ibb. Они предвыделяются размером max_binlog_size, по умолчанию это 1 ГБ.

Для сравнения, классический FILE binlog обычно выглядит так:

$ ls -lah /var/lib/mysql/binlog/
drwx------ 2 mysql mysql 4.0K  #binlog_cache_files
-rw-rw---- 1 mysql mysql 1.9K  mysqld-bin.000001
-rw-rw---- 1 mysql mysql 4.0K  mysqld-bin.000001.idx
-rw-rw---- 1 mysql mysql 1.4K  mysqld-bin.000002
-rw-rw---- 1 mysql mysql    0  mysqld-bin.000002.idx
-rw-rw---- 1 mysql mysql   80  mysqld-bin.index

В режиме InnoDB binlog каталог выглядит уже так:

$ ls -lah /var/lib/mysql/binlog/
-rw-rw---- 1 mysql mysql 1.0G  binlog-000000.ibb
-rw-rw---- 1 mysql mysql 1.0G  binlog-000001.ibb

Разница заметна сразу.

Во-первых, сразу создаются файлы заранее заданного размера. Во-вторых, пропадают привычные .index, .idx и .state файлы. Состояние GTID пишется внутрь самого binlog как параметр транзакции с интервалом, задаваемым через --innodb-binlog-state-interval.

В результате восстановление позиции GTID выполняется не через внешние индексные файлы, а через сканирование binlog от последней зафиксированной транзакции вперёд.

Есть и ещё одна интересная деталь: в выводе утилиты mariadb-binlog для InnoDB binlog события могут показываться с end_log_pos 0, поскольку позиционирование ведёт сам InnoDB. У классического FILE binlog, напротив, видны реальные смещения в байтах.

Кроме того, транзакции в журнале могут быть распределены по разным файлам. Для пользователя это обычно прозрачно: mariadb-binlog сам соберёт всё корректно, если передать файлы в правильном порядке.

Что это меняет в backup и restore

Здесь начинается уже не только улучшение производительности, но и сильное упрощение обслуживания.

mariadb-backup теперь может забирать binlog как часть транзакционно согласованного состояния. То есть backup получает данные и binlog атомарно, без отдельной ручной сверки позиций.

Это означает следующее:

  • binlog включается в backup по умолчанию;

  • сервер не нужно блокировать на время отдельного копирования binlog;

  • восстановленный backup можно быстрее превратить в реплику;

  • отпадает значительная часть старой ручной возни с позициями.

Практический смысл здесь огромный: операции восстановления и пересоздания реплик становятся заметно менее хрупкими.

Операционная сторона: GTID, ресинк реплик и split-brain

На мой взгляд, это одна из самых важных частей всей истории, потому что реальные DBA-выигрыши здесь не только в TPS, но и в снижении количества ручных процедур.

Если вы используете binlog_storage_engine=innodb, то нормально жить дальше получится только с GTID. Старую схему file/offset здесь нужно считать прошлым этапом.

Что на самом деле означает MASTER_USE_GTID = slave_pos

Это место регулярно вызывает путаницу у тех, кто впервые внимательно читает документацию MariaDB.

В записи:

CHANGE MASTER TO MASTER_USE_GTID = slave_pos;

slave_pos — это не значение, которое вы должны подставить вручную. Это литеральный режим работы, один из трёх допустимых вариантов:

MASTER_USE_GTID = no
MASTER_USE_GTID = slave_pos
MASTER_USE_GTID = current_pos

Смысл slave_pos такой: сервер должен взять стартовую позицию для репликации из собственной системной переменной @@gtid_slave_pos.

То есть DBA передаёт не сами данные, а инструкцию. Фактическая позиция определяется движком и восстанавливается автоматически.

Как выглядела синхронизация реплики раньше

В классической FILE-схеме процедура обычно выглядела так:

  1. Снимаем свежий backup через mariadb-backup.

  2. Читаем из backup файл xtrabackup_binlog_info.

  3. Получаем что-то вроде binlog.000042 position 198472910.

  4. Восстанавливаем backup на реплике.

  5. Вручную прописываем:

CHANGE MASTER TO
  MASTER_LOG_FILE = 'binlog.000042',
  MASTER_LOG_POS = 198472910;
START SLAVE;

Проблема в том, что здесь два независимых источника истины: состояние InnoDB и состояние binlog. И если позиция оказалась смещена хотя бы на одну транзакцию, можно получить либо повторное применение событий, либо их пропуск. Худший вариант — тихий drift без немедленной ошибки.

История с MDEV-21611 хорошо показывает, что такие рассогласования не являются чисто теоретическими.

Как выглядит синхронизация с InnoDB binlog и GTID

Здесь схема радикально проще:

  1. Снимаем backup.

  2. Вместе с данными в него уже попадают .ibb binlog-файлы.

  3. Восстанавливаем backup на реплике.

  4. @@gtid_slave_pos восстанавливается автоматически.

  5. Настраиваем репликацию так:

CHANGE MASTER TO
  MASTER_HOST = 'primary',
  MASTER_USER = 'replicator',
  MASTER_PASSWORD = 'replpass',
  MASTER_USE_GTID = slave_pos,
  MASTER_DEMOTE_TO_SLAVE = 1;
START SLAVE;

И всё. Ни имени binlog-файла, ни byte offset DBA руками больше не указывает.

С практической точки зрения это большое улучшение: резервная копия уже содержит консистентную точку, а реплика сама объявляет мастеру свой GTID-набор и получает дальнейший поток событий ровно оттуда, где нужно.

Почему это особенно важно при split-brain

Настоящая ценность GTID и нового binlog особенно хорошо проявляется в неприятных аварийных сценариях.

Представим, что primary и replica временно потеряли связь.

  • Primary успел зафиксировать GTID с 0-1-1 по 0-1-10042.

  • Replica отставала и успела получить только до 0-1-10039.

  • Затем replica временно стала primary и приняла ещё три локальные записи.

В результате формально у обоих серверов может оказаться диапазон до 0-1-10042, но содержимое последних транзакций будет разным.

В старой FILE-схеме такое расхождение часто приходилось расследовать вручную: просматривать binlog двух сторон, искать точку расхождения, а затем принимать решение, можно ли безопасно откатиться или проще пересоздать реплику с нуля.

С GTID картинка становится гораздо более явной.

На проблемной реплике:

SELECT @@gtid_slave_pos;

На актуальном primary:

SELECT @@gtid_binlog_pos;

Если включён gtid_strict_mode = ON, MariaDB не позволит молча применить GTID с другим содержимым. Вместо тихой порчи данных вы получите явную ошибку.

Для ��инхронизации достаточно вернуть последнюю подтверждённо корректную позицию:

STOP SLAVE;
RESET SLAVE;
SET GLOBAL gtid_slave_pos = '0-1-10039';
START SLAVE;

После этого сервер снова начнёт получать события с нужной точки без ручной арифметики по byte offset.

Для чего нужен MASTER_DEMOTE_TO_SLAVE=1

Это очень полезный механизм для бывших primary.

Если сервер какое-то время принимал локальные записи, его @@gtid_binlog_pos уже содержит GTID, которых текущий primary может не знать. Если в этот момент просто использовать MASTER_USE_GTID = slave_pos, можно получить дыру между тем, что сервер уже когда-то локально записал, и тем, откуда он будет считать себя репликой.

MASTER_DEMOTE_TO_SLAVE = 1 решает это автоматически: MariaDB берёт объединение @@gtid_slave_pos и @@gtid_binlog_pos, записывает его как новую стартовую позицию и уже потом подключается к primary.

По-человечески это читается так: «если у меня были локальные записи, учти их при старте репликации, чтобы я не попытался заново применить уже имеющееся состояние».

В результате типовое руководство по восстановлению после split-brain сильно упрощается:

STOP SLAVE;
SET GLOBAL gtid_slave_pos = '<last_known_good_gtid>';
CHANGE MASTER TO MASTER_USE_GTID = slave_pos, MASTER_DEMOTE_TO_SLAVE = 1;
START SLAVE;

Именно здесь новая архитектура начинает экономить не только миллисекунды, но и реальные часы инженерного времени.

А что с Galera

Здесь есть важное ограничение: в текущем виде Galera с InnoDB binlog не работает.

Причина упирается не в «недопиленную галочку», а в архитектуру wsrep.

Galera-кластер реплицирует не через binlog. Он использует write set protocol:

  • транзакция доходит до стадии prepare;

  • из неё извлекается набор изменений;

  • этот write set рассылается узлам;

  • проходит сертификация конфликтов;

  • затем commit выполняется согласованно на всех узлах.

Упрощённая схема такая:

Client writes to Node A
  → InnoDB: prepare transaction
  → wsrep: extract write set from transaction
  → wsrep: broadcast write set to all nodes
  → wsrep: certification round
  → wsrep: apply write set on all nodes simultaneously
  → InnoDB: commit on all nodes
  → binlog written AFTER commit

Проблема в том, что wsrep historically встраивается между prepare и commit. А новая схема InnoDB binlog как раз делает prepare и commit одной атомарной операцией без отдельной точки остановки.

То есть wsrep теряет место, куда он может встроить сертификацию.

Поэтому для поддержки Galera нужно не просто «разрешить фичу», а переинтегрировать wsrep на другом уровне API движка. Это серьёзная инженерная задача. В MDEV-34705 это отмечено как будущее направление, но в 12.3 поддержки нет.

Что это означает на практике

  • сами Galera-ноды пока не могут использовать binlog_storage_engine=innodb;

  • async-реплики, которые висят за Galera-нодой, остаются рабочей схемой, если сама Galera-нода продолжает использовать FILE binlog;

  • смешивать storage engine binlog в одной репликационной цепочке «как попало» не получится: формат определяет источник.

Матрица решений по топологиям

Для быстрого ориентира:

Топология

InnoDB binlog

Комментарий

Standalone primary + async replicas на GTID

Да

Основной и полностью поддержанный сценарий

Primary + async replicas на file/offset

Нет

Сначала миграция на GTID

Semi-sync AFTER_COMMIT + async replicas

Да

Поддерживается

Semi-sync AFTER_SYNC

Нет

Архитектурно несовместимо

Galera cluster nodes

Нет

Пока отсутствует поддержка wsrep

Async replica от Galera node

Да, но косвенно

При условии, что сама Galera-нода остаётся на FILE binlog

MaxScale read/write split

Нужно тестировать

Протокол репликации прежний, но failover-логика может требовать пересмотра

MySQL InnoDB Cluster

Не относится

Это отдельный мир, не MariaDB

Ограничения и открытые проблемы

Фича не зря идёт как опциональная. Перед продакшн-включением ограничения лучше понимать заранее.

Ограничения по дизайну в 12.3.1

  • При переходе на новый binlog старые .bin файлы автоматически не мигрируются. In-place преобразования нет.

  • GTID обязателен. Классические file/offset механизмы больше не являются рабочим интерфейсом.

  • MASTER_POS_WAIT() и похожие file/offset API недоступны. Вместо них нужен MASTER_GTID_WAIT().

  • sync_binlog формально принимается, но фактически игнорируется. Контроль долговечности теперь идёт через innodb_flush_log_at_trx_commit.

  • Инструменты, которые читают .bin напрямую, формат .ibb не поймут.

  • AFTER_SYNC для semi-sync не поддерживается. Рабочая схема — только AFTER_COMMIT.

  • Galera в 12.3.1 с этим режимом не поддерживается.

Открытые вопросы в JIRA

На март 2026 года стоит обратить внимание как минимум на следующее.

MDEV-38462 — возможные проблемы crash recovery при недостаточном innodb_log_file_size в новом режиме. Это логично: теперь redo log несёт не только обычные изменения InnoDB, но и binlog payload. Если лог слишком мал относительно write-rate, recovery может упереться в его размер.

MDEV-38304 — запрос на сохранение binlog в archived redo log. Это не блокер, но показатель того, что история с архивированием ещё развивается.

Сама основная реализация идёт по MDEV-34705. Перед продакшн-включением имеет смысл просмотреть и связанные подзадачи.

Что важно учесть на тестовом стенде и в проде

Есть несколько практических замечаний, которые легко упустить.

Во-первых, не стоит тестировать innodb_flush_log_at_trx_commit=0 на shared cloud storage с агрессивным host caching. Можно получить красивые, но бесполезные цифры. Если хочется увидеть реальное поведение, лучше использовать выделённый Premium SSD или Ultra Disk и отключать лишнее кэширование на стороне платформы.

Во-вторых, из-за предварительного выделения .ibb по 1 ГБ потребление диска выглядит «большим» уже в самом начале. Это не баг, а особенность формата, но для алертов и capacity planning это важно.

В-третьих, sync_binlog, который теперь игнорируется, может стать неприятной ловушкой при миграции. Если команды исторически использовали этот параметр как индикатор «надежного binlog», мониторинг и runbook нужно пересмотреть.

Дизайн бенчмарка

В статье сравнивались две конфигурации:

  • binlog_storage_engine=FILE — классический вариант;

  • binlog_storage_engine=innodb — новый вариант.

Каждая из них тестировалась в трёх профилях долговечности.

D1: полная долговечность

innodb_flush_log_at_trx_commit=1
sync_binlog=1

Это тот самый production-grade режим, где раньше commit стоил два fsync().

D2: буферизация на стороне ОС

innodb_flush_log_at_trx_commit=2
sync_binlog=0

D3: максимальная пропускная способность

innodb_flush_log_at_trx_commit=0

Отдельно измерялись:

  • количество TPS через sysbench oltp_write_only;

  • скорость коммитов, включая перцентили p50/p95/p99;

  • время коммита для большой транзакции;

  • время восстановления после kill -9;

  • отставание (lag) реплики под нагрузкой;

  • объём записи в журнал redo log;

  • косвенно количество fsync() через системные метрики.

Тесты запускались при 1, 8, 32 и 64 потоках.

Лабораторный стенд

Тестовый стенд был развёрнут в Azure.

Primary:

  • Standard_E4ds_v5;

  • 4 vCPU;

  • 32 ГБ RAM.

Replica:

  • Standard_E2ds_v5;

  • 2 vCPU;

  • 16 ГБ RAM.

Диски:

  • P30 Premium SSD 500 ГБ;

  • 5000 IOPS;

  • host caching отключён.

ОС — Ubuntu 24.04 LTS. Использовалась MariaDB 12.3.1 RC из binary tarball. Каталог данных, redo log и binlog находились на одном P30-диске в /data/mysql.

Параметры теста:

  • sysbench oltp_write_only;

  • 4 таблицы по 1 млн строк;

  • 60 секунд на прогон;

  • 15 секунд прогрева;

  • 2 повтора на каждую точку;

  • buffer pool 20 ГБ;

  • redo log 4 ГБ.

Результаты: D1 даёт 2.4–3.3x прироста TPS

Вот ключевая таблица для полного режима долговечности:

Threads

FILE TPS

InnoDB TPS

Ускорение

FILE p99

InnoDB p99

1

129

307

2.4x

16.1 ms

8.1 ms

8

444

1073

2.4x

37.3 ms

15.1 ms

32

1392

4625

3.3x

43.8 ms

34.1 ms

64

2564

8279

3.2x

72.1 ms

29.4 ms

Даже на одном потоке выигрыш уже заметен. Под высокой конкуренцией он становится ещё более выразительным. Это важный момент: эффект не сводится к «косметическому улучшению». Убирается действительно дорогой системный вызов с горячего пути commit.

Но ещё важнее история с p99. В FILE-режиме хвостовая задержка растёт очень резко: с 16 до 72 мс. В режиме InnoDB binlog рост тоже есть, но он существенно мягче: с 8 до 29 мс.

Для прикладных команд это иногда даже важнее, чем рост среднего TPS.

D2 и D3: когда fsync() уже убрали, преимущества почти нет

Результаты для D2/D3 выглядят так:

Threads

FILE D2

InnoDB D2

FILE D3

InnoDB D3

1

2950

2861

2943

2985

8

9961

9924

10027

11681

32

10818

11091

11202

10973

64

10815

10462

11121

10751

При насыщении оба варианта упираются примерно в один и тот же потолок. В данном случае ограничение уже не в синхронизации binlog, а в записи страниц InnoDB и лимите диска по IOPS.

Это, кстати, хороший контрольный результат. Он подтверждает, что основной выигрыш D1 возникает именно из-за устранения дополнительного fsync() binlog. Когда fsync() уже нет, устранять больше нечего.

Восстановление после аварии: скорость сопоставима, архитектура чище

Результаты recovery были такими:

Профиль

Время восстановления

Страниц к восстановлению

Комментарий

file_d1

34.1 s

54820

XA prepared, 2PC reconciliation

innodb_d1

39.2 s

41688

Единый recovery path

file_d2

29.1 s

45430

Нужна сверка binlog и InnoDB

innodb_d2

27.1 s

43219

Единый recovery path

file_d3

33.1 s

44309

Согласование состояния после краха

innodb_d3

39.2 s

44846

Единый recovery path

По wall-clock времени здесь нет драматического разрыва. Но архитектурно разница всё равно важна.

В FILE-схеме recovery должен учитывать XA prepare и согласовывать разные источники истины. В режиме InnoDB binlog восстановление становится однопутевым: без отдельной логики для reconciliation между движком и бинарным логом.

То есть дело не только в секунде туда или сюда, а в уменьшении класса потенциальных сбоев.

Большие транзакции: да, раздутие журнала реально существует

Есть и обратная сторона.

В тесте с одной большой транзакцией результаты были такими:

Метрика

FILE

InnoDB

Commit 100K-row UPDATE

1793 ms

1564 ms

Размер журнала

1.03x

1.98x

Изменённые данные

104 MB

104 MB

Записано в журнал

107 MB

206 MB

Именно потому, что binlog теперь проходит через журнал (redo log), общий объём записи в журнал почти удваивается.

Для типичных OLTP-нагрузок с короткими транзакциями это обычно не критично: выигрыш от устранения fsync() перекрывает этот эффект. Но для bulk ETL, массовых INSERT и больших UPDATE это уже надо учитывать отдельно.

Отсюда прямое практическое следствие: innodb_log_file_size нужно подбирать более щедро, чем раньше.

Репликационный lag: узкое место остаётся на стороне реплики

В статье отдельно измеряли отставание (lag) одной async-реплики под sustained 16-thread write load.

Метрика

FILE D1

InnoDB D1

TPS primary

1276

1994

Lag на 60s

58s

89s

Lag на 120s

121s

148s

Lag на 180s

185s

206s

Lag на 300s

313s

321s

Рост lag

~1.0 s/s

~1.1 s/s

На первый взгляд может показаться, что InnoDB binlog увеличивает задержку, потому что цифры выше. Но правильная интерпретация здесь другая: primary начинает коммитить существенно быстрее, а узким местом становится выполнение SQL apply на стороне реплики.

То есть новая схема не ломает репликацию как таковую. Она просто снимает ограничение с primary, после чего проблема смещается в сторону реплики.

Если дальше включать параллельное исполнение (parallel apply) на реплике, преимущество, вероятно, станет ещё более заметным.

Что это означает для продакшна

Если коротко, то для GTID-based async replication это одно из самых сильных улучшений MariaDB 12.3.

Что мы получаем:

  • 2.4–3.3x прироста TPS в строгом durability-профиле;

  • заметно более низкий p99 при росте конкуренции;

  • устранение рассинхронизации между binlog и InnoDB как отдельного класса проблем;

  • более простой backup/restore и ресинк реплик;

  • меньше ручной арифметики с file/offset;

  • более чистую и предсказуемую модель восстановления.

Но есть и условия:

  • нужен GTID;

  • нужно забыть про старую file/offset-операционку;

  • придётся внимательнее относиться к innodb_log_file_size;

  • нельзя рассчитывать на Galera в текущем релизе;

  • часть старых binlog-readers придётся заменить или проверить на совместимость.

Когда я бы включал эту фичу

В первую очередь — на схеме с одним мастером и асинхронными репликами, где используется InnoDB и уже принят GTID как стандартная модель репликации.

Особенно хорошо это выглядит там, где:

  • важна низкая задержка;

  • тяжёлая OLTP-нагрузка упирается в производительность диска;

  • инфраструктура живёт в облаке и каждый лишний fsync() реально стоит дорого;

  • хочется упростить backup/restore и полное перевосстановление реплик.

Когда я бы не спешил

Есть несколько сценариев, где я бы был осторожнее.

  • Galera-кластеры: пока просто нет поддержки.

  • ETL-нагрузки с очень большими batch-транзакциями: сначала я бы проверил, достаточно ли места для хранения журнала и корректно ли настроен его размер.

  • Инсталляции, завязанные на старые инструменты, которые читают .bin напрямую.

  • Вы ещё не готовы перейти на GTID как на единственный интерфейс репликации.

Рекомендации по внедрению

Если планировать пилот, я бы шёл так:

  1. Сначала убедиться, что вся репликация уже живёт на GTID.

  2. Отдельно проверить инструменты: backup, CDC, мониторинг, failover automation.

  3. Заранее увеличить innodb_log_file_size минимум до 4 ГБ, а при интенсивной записи рассматривать и более высокие значения.

  4. Прогнать собственный write-heavy benchmark именно на вашей storage-платформе.

  5. Добавить мониторинг checkpoint age и признаков pressure на redo log.

  6. Проверить, что никто внутри команды не рассчитывает на sync_binlog как на реально работающий рычаг.

Итог

Для OLTP-сценариев на MariaDB 12.3 это, на мой взгляд, одна из самых важных новых возможностей за последние годы.

Исторически binlog был компромиссом: без него нельзя, но за него приходилось платить и по TPS, и по задержкам, и по сложности эксплуатации. В режиме binlog_storage_engine=innodb часть этой цены просто исчезает, потому что исчезает сама архитектурная причина двойной синхронизации.

Особенно ценно, что выигрыш здесь не только в бенчмарках. Меняется и повседневная операционная модель: backup становится чище, resync реплик — проще, split-brain recovery — понятнее, а file/offset-арифметика уходит в прошлое.

Если у вас уже есть GTID, InnoDB и обычная асинхронная репликация, эту возможность точно стоит как минимум протестировать.

Глоссарий

GTID (Global Transaction ID)

Глобальный идентификатор транзакции — уникальный номер, присваиваемый каждой транзакции в репликационной топологии MariaDB/MySQL. Используется для отслеживания положения репликации независимо от файлов и байтовых смещений. Формат: domain_id-server_id-sequence_number.

WAL (Write-Ahead Logging)

Протокол гарантии долговечности, при котором все изменения сначала записываются в журнал (redo log) перед применением к основным данным. Это гарантирует, что даже при сбое питания или краше базы данные можно восстановить из журнала.

Durability (долговечность)

В контексте СУБД это свойство ACID, гарантирующее, что подтверждённые транзакции сохраняются даже после сбоя.

Redo log / Redo Journal

Журнал повторного выполнения в InnoDB, который содержит запись всех изменений до их применения к данным. Используется при восстановлении после краша для воспроизведения транзакций, которые были записаны, но не полностью применены.

Binlog (Binary Log)

Бинарный журнал изменений базы данных в MariaDB/MySQL. Содержит события репликации (INSERT, UPDATE, DELETE и т.д.) и используется для репликации и восстановления данных.

Fsync

Системный вызов, который принудительно синхронизирует все буферизованные данные процесса с диском. В контексте БД это критичная операция для гарантии долговечности, но затратная по производительности.

2PC (Two-Phase Commit)

Двухфазный протокол коммита — архитектурный подход, при котором есть две независимые точки коммита, которые необходимо согласовывать между собой. В старой модели MariaDB это были отдельные коммиты для binlog и InnoDB.

XA (eXtended Architecture)

Распределённый протокол транзакций для управления транзакциями, затрагивающими несколько источников данных. В контексте MariaDB используется для согласования состояния между InnoDB и binlog при двухфазном коммите.

Replication Lag (Репликационная задержка)

Временная разница между моментом коммита транзакции на primary и моментом её применения на replica. Измеряется в секундах или миллисекундах.

Split-brain (Расщепление мозга)

Сценарий отказа репликации, при котором primary и replica теряют связь и независимо принимают данные, что приводит к несогласованности состояния на обоих серверах.

Group Commit

Оптимизация, при которой несколько транзакций синхронизируются и записываются на диск вместе, амортизируя стоимость fsync() между несколькими операциями.

Crash Recovery

Процесс восстановления базы данных после неожиданного завершения сервера (падение, отключение питания и т.д.) путём воспроизведения операций из redo log и отката неполных транзакций из undo log.

Semi-sync Replication

Режим репликации, при котором primary ждёт подтверждения от replica перед завершением коммита. AFTER_SYNC означает ожидание после синхронизации replica, AFTER_COMMIT — после локального коммита.

Galera Cluster

Синхронный кластер репликации для MariaDB/MySQL, в котором все узлы содержат идентичные копии данных благодаря протоколу write sets и сертификации конфликтов.

IOPS (Input/Output Operations Per Second)

Количество операций чтения/записи, которые диск может выполнить в секунду. Один из ключевых параметров производительности хранилища для операций БД.

Throughput (Пропускная способность)

Количество успешно обработанных операций в единицу времени (обычно TPS — транзакции в секунду или QPS — запросы в секунду).

Tail Latency (p99, p95 latency)

Задержка наихудшего случая для 99-го, 95-го процентиля операций. Показывает, какой процент операций превышает эту задержку, и важна для пользовательского опыта.

TPS (Transactions Per Second)

Количество транзакций, успешно обработанных сервером в секунду. Основная метрика производительности для OLTP-систем.

Ссылки