Синхронизация данных по расписанию: инкрементальный режим с курсором последней синхронизации
Типичный цикл выглядит так: по расписанию cron (например, раз в час) пайплайн читает из вашего хранилища отметку времени последней успешной синхронизации, запрашивает у источника только изменения после этой отметки, выполняет идемпотентную операцию вставки или обновления (UPSERT) в вашей базе и сдвигает курсор только после успешной фиксации данных. Так вы не пересканируете всю таблицу на каждом прогоне и не плодите дубликаты, если провайдер cron или платформа перезапустит задачу.
Обновлено: 2026-04-20
Кратко
Суть ответа
Синхронизация данных по расписанию: инкрементальный режим с курсором последней синхронизации. Пайплайн по расписанию читает сохранённую отметку из базы или другого хранилища состояния, запрашивает у источника только строки с `updated_at` (или аналогом) новее этой отметки, выполняет UPSERT с уникальным ключом по бизнес-идентификатору и обновляет отметку только после успешной фиксации пакета.
Когда подходит и когда нет
- Источник поддерживает фильтрацию по `updated_at`
- Нужна инкрементальная синхронизация без полного перезапроса
На что обратить внимание
- Сдвигать отметку `last_synced_at` (или аналог) до того, как транзакция с данными успешно зафиксирована: при ошибке записи курсор уже «уехал вперёд», а часть строк не попала в целевую базу — расхождение заметят поздно.
- Без идемпотентной записи (UPSERT по первичному ключу или явная дедупликация) повторный прогон после сбоя или повтор доставки события создаёт дубликаты.
Ситуация: нагрузка и где обычно ломается
Почему инкрементальная синхронизация данных по расписанию без сохранённой отметки последнего успешного прогона даёт сбои и лишнюю нагрузку
- VPS crontab: failed syncs go to /dev/null or root mail nobody reads
- Full re-sync every run: re-fetching all records wastes API quota and increases sync time quadratically
- Missing idempotency: network failure mid-sync creates partial state and duplicate records on retry
- No run history: "did the sync run last night?" requires SSH and log search
Если на каждом прогоне вытягивать полный снимок данных, синхронизация со временем становится медленной и дорогой и перегружает источник; при росте объёма один прогон может занять дольше, чем интервал между запусками cron.
Когда упрощённых рецептов мало
Типичные ошибки при инкрементальной синхронизации
Сдвигать отметку `last_synced_at` (или аналог) до того, как транзакция с данными успешно зафиксирована: при ошибке записи курсор уже «уехал вперёд», а часть строк не попала в целевую базу — расхождение заметят поздно.
Без идемпотентной записи (UPSERT по первичному ключу или явная дедупликация) повторный прогон после сбоя или повтор доставки события создаёт дубликаты.
Как Inquir помогает в этом сценарии
Инкрементальная синхронизация в Inquir
Пайплайн по расписанию читает сохранённую отметку из базы или другого хранилища состояния, запрашивает у источника только строки с `updated_at` (или аналогом) новее этой отметки, выполняет UPSERT с уникальным ключом по бизнес-идентификатору и обновляет отметку только после успешной фиксации пакета.
В консоли видна история каждого прогона: сколько строк обработано, на какой отметке остановились при ошибке, когда был следующий запуск — проще отлаживать и согласовывать данные с владельцем источника.
Что вы получаете на платформе
Из чего складывается устойчивая инкрементальная синхронизация
Курсор состояния
Храните отметку последней успешной синхронизации (`last_synced_at` или эквивалент) в базе или key-value; запрашивайте у источника только строки новее этой отметки.
Идемпотентная вставка или обновление (UPSERT)
Используйте конструкцию вида `INSERT … ON CONFLICT (id) DO UPDATE …` или эквивалент в вашей СУБД, чтобы повторный прогон с теми же строками не создавал дубликаты.
Атомарный сдвиг курсора
Обновляйте отметку только после успешной записи всего батча (по возможности в одной транзакции с данными), чтобы сбой посередине не «перепрыгивал» курсор.
Расписание cron в пайплайне
Часовой или суточный прогон задаётся cron-выражением; в консоли сохраняются история запусков и политика повторов при временных сбоях.
Что сделать дальше, по шагам
Как организовать инкрементальную синхронизацию данных по расписанию с отметкой последнего успешного прогона
Прочитать курсор
Запросить `last_synced_at` из базы или persistent storage в начале каждого прогона.
Запросить дельту
`WHERE updated_at > last_synced_at ORDER BY updated_at LIMIT N` — инкрементальный запрос.
Upsert и сдвиг курсора
Сделать upsert батча, затем обновить `last_synced_at = MAX(updated_at)` в одной транзакции.
Пример кода
Incremental sync with watermark cursor
Reads cursor from environment/DB, fetches delta, upserts idempotently, returns new cursor. Cron trigger fires on schedule; retries on failure.
export async function handler(event) { // Read watermark — default to 24h ago on first run const cursor = await db.syncCursors.get('crm-contacts') ?? new Date(Date.now() - 86_400_000).toISOString(); let page = 0, synced = 0, newCursor = cursor; do { const { contacts, nextPage } = await crm.fetchContacts({ updatedAfter: cursor, page }); if (contacts.length === 0) break; await db.contacts.upsertBatch(contacts); // upsert by contacts[].externalId synced += contacts.length; newCursor = contacts.at(-1)?.updatedAt ?? newCursor; page = nextPage; } while (page); await db.syncCursors.set('crm-contacts', newCursor); return { synced, cursor, newCursor }; }
Когда подходит и когда нет
Когда уместен режим с курсором последней синхронизации
Когда это уместно
- Источник поддерживает фильтрацию по `updated_at`
- Нужна инкрементальная синхронизация без полного перезапроса
Когда лучше не трогать
- Источник не поддерживает фильтрацию по времени — нужна полная выгрузка с hash-сравнением
Вопросы и ответы
Вопросы и ответы
Что если прогон упал на середине?
Следующий запуск по расписанию снова прочитает прежнюю отметку `last_synced_at`: те же строки запросятся повторно. При идемпотентном UPSERT дубликаты не появятся.
Где хранить состояние курсора?
Обычно отдельная таблица `sync_state` или key-value с одной записью на каждый источник и тип синхронизации.