Заявленная тема не новая, но часто актуальная, поэтому прошу рассматривать статью исключительно как памятку для тех, кто работает с совсем бесплатными СУБД. В этой статье описывается схема, как организовать простое решение, чтобы процесс передачи данных между системами не приводил к рассогласованности этих же данных в системе-потребителе или их потере.
Конструктив
В системе-источнике
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.
Внутри кадра последовательность завершения транзакций будет не определена, но на уровне кадров последовательность внесения изменений в БД определена будет однозначно. Кадр нужен исключительно как квант межсистемного взаимодействия.
Описание процесса
Сам процесс распространения данных выглядит тривиально: система-источник порождает изменение, сохраняет в журнал и запускает уведомление в брокер; система-потребитель ловит уведомление (или возбуждается по регламенту), считывает данные в буферную таблицу и в мощном транзакционном порыве переносит их в предметные таблицы.
Что важно …
Запрашивать данные можно постранично, указывая размер пакета, но сессия может считаться полезной если хотя бы один кадр получен полностью. В сессии можно запросить сразу несколько кадров (или вообще не ограничивать количество). После завершения потока данных какого-либо кадра, сохраните идентификатор кадра в соответствующую таблицу. Дальнейшая ответственность за процесс миграции данных (трансформация и перенос в предметные таблицы) лежит целиком на системе-получателе.
Данные обязательно отдавайте в порядке возрастания 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 объекта) можно перезаписывать.