{"id":13455,"url":"\/distributions\/13455\/click?bit=1&hash=8bce2c32fc522b9cfe1ab89089eff75ab558dbec8812c3dda390faecf1c743f2","title":"\u00ab\u0410 \u0442\u044b \u0442\u043e\u0447\u043d\u043e \u0440\u0438\u0435\u043b\u0442\u043e\u0440?\u00bb \u0438 \u0434\u0440\u0443\u0433\u0438\u0435 \u043d\u0435\u043b\u043e\u0432\u043a\u0438\u0435 \u0432\u043e\u043f\u0440\u043e\u0441\u044b \u0431\u0440\u043e\u043a\u0435\u0440\u0443","buttonText":"\u041f\u043e\u043a\u0430\u0436\u0438\u0442\u0435","imageUuid":"ca4cf1a1-a5ed-5aca-9f34-357accc11bb1","isPaidAndBannersEnabled":false}
Лысенко Андрей

Сохранение консистентности данных при взаимодействии систем

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

Конструктив

В системе-источнике

1. Создайте обычную табличку – журнал (data_log) изменений данных вот с такими полями (минимум):

id – идентификатор записи
action – код действия с записью
time – дата и время события // не обязательно для задачи
transaction_id – идентификатор транзакции // не обязательно для задачи
frame_id – идентификатор кадра (default = NULL)
table_name – имя таблицы
record_id – идентификатор записи
affected_fields – имена полей в которых сделаны изменения
object_data – предметные данные объекта (JSON)
user_id – идентификатор оператора // не обязательно для задачи
consumer – потребитель // для генератора фейковых событий

Если в вашей СУБД есть возможность – сделайте партицию/секцию на frame_id is null.

2. Организуйте запись всех изменений в системе в эту таблицу (через API). Данные можно хранить ограниченное время.

3. Создайте раскадровщик журнала (см. пояснения ниже).

4. Выставьте REST API для отдачи данных. В API реализуйте отдельный эндпоинт под генерацию фейковых событий, для первичного подключения потребителя.

5. Организуйте публикацию уведомлений о новом кадре в брокер сообщений.

В системе-получателе

1. Создайте буферную таблицу для получения данных.

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

3. Создайте скрипт обращения к системам-источникам.

4. Организуйте подключение к брокеру сообщений (если нужно).

5. Создайте скрипт для чтения данных из систем-источников в буферную таблицу.

6. Создайте скрипт для переноса данных в предметные таблицы.

Раскадровщик

Скрипт, который запускается строго после завершения транзакции (т.е. никак с транзакциями не должен быть связан) или по регламенту (например, раз в секунду). Размечает записи в data_log идентификатором кадра, получаемым каждый раз из sequence.

UPDATE data_log SET frame_id = <NEW_ID> WHERE frame_id is null

Внутри кадра последовательность завершения транзакций будет не определена, но на уровне кадров последовательность внесения изменений в БД определена будет однозначно. Кадр нужен исключительно как квант межсистемного взаимодействия.

Описание процесса

Сам процесс распространения данных выглядит тривиально: система-источник порождает изменение, сохраняет в журнал и запускает уведомление в брокер; система-потребитель ловит уведомление (или возбуждается по регламенту), считывает данные в буферную таблицу и в мощном транзакционном порыве переносит их в предметные таблицы.

Что важно …

Запрашивать данные можно постранично, указывая размер пакета, но сессия может считаться полезной если хотя бы один кадр получен полностью. В сессии можно запросить сразу несколько кадров (или вообще не ограничивать количество). После завершения потока данных какого-либо кадра, сохраните идентификатор кадра в соответствующую таблицу. Дальнейшая ответственность за процесс миграции данных (трансформация и перенос в предметные таблицы) лежит целиком на системе-получателе.

Данные обязательно отдавайте в порядке возрастания frame_id, id. Читать строки из data_log можно только с frame_id is not null.

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

Что интересно …

Полученные данные на стороне системы-потребителя всегда будут консистентны (по спецификации системы-источника). Не будет никаких проблем с зависшими транзакциями.

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

В уведомлении (через брокер) достаточно послать список задетых таблиц и полей (JSON) в кадре. Это поможет избежать большого количества лишних обращений к API, если потребитель не заинтересован в них.

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

consumer – система потребитель (в заголовках).
afterFrameId – последний, обработанный потребителем, кадр.
action – параметр для фильтрации по действию.
affected (JSON) – фильтр - список таблиц и полей, изменения в которых интересны получателю.
content (JSON) – список таблиц и полей, которые нужно включить в ответ.
frameCount – ограничение на количество получаемых кадров.
pageNum – номер запрашиваемой страницы.
pageSize – размер страницы.

Если потребителю не интересны изменения (количество и последовательность) объектов, то при записи в буферную таблицу объекты с одинаковым идентификатором (система + таблица + id объекта) можно перезаписывать.

0
Комментарии
Читать все 0 комментариев
null