Москва: проверенные б/у ноутбуки с гарантией и проверкой перед оплатой
+7 906 722-94-45

Главная → Статьи → Двойная запись: переход между хранилищами данных

Двойная запись: как перейти на новое хранилище без потери данных

Двойная запись: сравнение с outbox и CDC, частичный отказ при записи в два хранилища, расхождение данных, сверка и момент прекращения двойной записи.

Прямой ответ

Двойная запись (dual write) — одновременная запись одних данных в старое и новое хранилище на время перехода. Она позволяет проверить новое хранилище на реальных данных, не переключая чтение.

Проблема двойной записи — отсутствие общей транзакции. Записи в два хранилища выполняются последовательно, и сбой между ними оставляет расхождение:

old_store.write(record)     # выполнено
new_store.write(record)     # сбой — данных в новом хранилище нет

Старое хранилище содержит запись, новое — нет. Расхождение обнаруживается только при сверке.

Три подхода к переходу и их гарантии:

Подход Атомарность Сложность Расхождение
Двойная запись Нет Низкая Возможно, устраняется сверкой
Outbox В пределах одной базы Средняя Исключено
CDC Нет, но гарантирована доставка Высокая Минимально

Двойная запись проста в реализации, но требует сверки для обнаружения расхождений.

Outbox записывает данные и намерение о публикации в одной транзакции, а отдельный процесс переносит запись в новое хранилище:

BEGIN;
INSERT INTO orders (...) VALUES (...);
INSERT INTO outbox (aggregate_id, payload) VALUES (1001, '{...}');
COMMIT;

Обе записи фиксируются одновременно, поэтому состояние «данные есть, намерения нет» невозможно; о механизме — в статье про сагу и outbox.

CDC читает журнал транзакций базы и применяет изменения к новому хранилищу. Приложение пишет только в старое хранилище, а перенос выполняется внешним процессом; о подходе — в статье про CDC.

Выбор определяется допустимостью расхождения и готовностью к инфраструктурной сложности. Двойная запись подходит для короткого перехода с регулярной сверкой. Outbox — когда расхождение недопустимо, а новое хранилище доступно из той же транзакции. CDC — при большом объёме и требовании минимального изменения кода.

Отдельный вопрос — порядок записей. При повторе после сбоя запись может быть применена в ином порядке, и последнее значение перезапишет более новое. Решение — проверка версии либо метки времени перед применением.

Когда двойная запись неприменима

Подход предполагает, что оба хранилища принимают одну и ту же модель данных. Три ситуации нарушают это предположение.

Разная модель данных. Если новое хранилище использует иную структуру — документную вместо реляционной либо графовую вместо табличной — преобразование выполняется при каждой записи. Ошибка преобразования приводит к расхождению, которое сложно обнаружить сверкой по агрегатам.

Разная семантика транзакции. Хранилище без поддержки транзакций не может гарантировать применение набора изменений целиком. Частичное применение оставляет состояние, которого не было в источнике.

Высокая частота обновления одной записи. При десятках обновлений одной записи в секунду порядок применения в новом хранилище может отличаться от исходного. Без проверки версии последнее применённое значение может оказаться устаревшим.

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

Практический критерий: двойная запись подходит, когда преобразование между моделями простое, а частота обновления отдельной записи низкая. В противном случае стоимость обнаружения и устранения расхождений превышает экономию на инфраструктуре.

Практика: переход с двойной записью

1. Включайте двойную запись до переноса существующих данных. Иначе изменения, внесённые во время переноса, попадут только в старое хранилище.

2. Считайте старое хранилище источником истины. До переключения чтения расхождение устраняется в пользу старого хранилища; о фазах — в статье про expand-contract.

3. Сверяйте данные регулярно. Сравнение количества и контрольных сумм по диапазонам выявляет расхождение до переключения чтения.

4. Не прерывайте основную операцию при сбое записи в новое хранилище. Отказ нового хранилища не должен приводить к ошибке для пользователя.

5. Логируйте неудачные записи в новое хранилище. Перечень нужен для восстановления расхождений.

6. Проверяйте версию перед применением. Повторная запись с более старой версией не должна перезаписывать актуальные данные.

7. Измеряйте влияние на задержку. Вторая запись увеличивает время операции, что нужно учитывать до включения.

8. Определите момент прекращения двойной записи. После переключения чтения и подтверждения корректности запись в старое хранилище прекращается.

Рекомендации:

  1. Не используйте двойную запись как постоянное решение. Это переходный механизм, и длительное сосуществование двух хранилищ удваивает сложность.
  2. Автоматизируйте сверку. Ручная проверка выполняется нерегулярно и пропускает расхождения.
  3. Учитывайте порядок операций. Одновременное обновление одной записи из двух процессов даёт неопределённый результат без проверки версии.
  4. Проверяйте поведение при недоступности нового хранилища. Основная операция должна продолжаться, а расхождение — устраняться позже.
  5. Планируйте обратный переход. До удаления старого хранилища возврат должен быть возможен.

Практический сценарий: расхождение данных после сбоя нового хранилища

Контекст: сервис переводили на новую базу с двойной записью. Через неделю сверка показала расхождение в 340 записях.

  1. Диагностика. Новое хранилище было недоступно несколько минут во время обновления. Записи, выполненные в этот период, попали только в старое хранилище. Неудачные записи не логировались, поэтому перечень отсутствовал.
  2. Причина. Двойная запись выполнялась без механизма восстановления при сбое. Отсутствие журнала неудачных записей делало расхождение необнаружимым до сверки.
  3. Действия. (а) Добавили журнал неудачных записей в новое хранилище с последующим автоматическим повтором; (б) сверку автоматизировали и запустили по расписанию с сравнением контрольных сумм по диапазонам идентификаторов; (в) расхождения устранили в пользу старого хранилища как источника истины; (г) добавили проверку версии записи перед применением, чтобы повтор не перезаписывал актуальные данные; (д) переключение чтения выполнили после недели стабильной сверки без расхождений.
  4. Результат. Расхождения обнаруживаются автоматически в течение часа, а переключение чтения выполнено при подтверждённой согласованности данных.

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

FAQ

Можно ли обойтись без двойной записи? Да, при использовании outbox либо CDC. Оба подхода устраняют расхождение, но требуют дополнительной инфраструктуры.

Что делать при сбое записи в новое хранилище? Основная операция продолжается, неудачная запись фиксируется в журнале и повторяется позже. Прерывание основной операции недопустимо.

Как долго держать двойную запись? До переключения чтения и подтверждения согласованности. Обычно от одной до нескольких недель.

Какое хранилище считать источником истины? Старое — до переключения чтения. После переключения источником становится новое хранилище, а старое используется только для возврата. Смешанное чтение из обоих хранилищ во время перехода создаёт неопределённость: при расхождении невозможно определить, какое значение корректно, поэтому чтение выполняется только из одного источника на каждом этапе.

Нужно ли хранить историю изменений при двойной записи? Да, если предполагается разбор расхождений. Журнал применённых изменений позволяет восстановить последовательность и определить момент появления расхождения, а без него остаётся только факт различия без причины.

Источники: microservices.io (Transactional Outbox), PostgreSQL docs (Logical Decoding), Martin Fowler — ссылки в шапке.