Обратное заполнение данных: как не положить прод
Массовое обновление старых записей может забить диск, разогнать репликационное отставание и замедлить обычные запросы. Разбираем типовой сценарий сбоя на PostgreSQL 16 и планируем backfill так, чтобы его можно было остановить и безопасно продолжить.

Фоновая задача обновляет исторические записи, а пользователи тем временем ждут страницы, реплика отстаёт, диск растёт быстрее ожидаемого. Так выглядит типовой сбой при обратном заполнении данных — backfill. Это не отчёт о конкретной компании: фактических замеров и деталей инцидента в исходных данных нет, поэтому ниже — разбор распространённого сценария без выдуманных цифр. Примеры последствий относятся к PostgreSQL 16; точные значения для вашей системы нужно получить на нагрузочном тесте и в продакшене.
Почему фоновая задача становится проблемой
Представим, что в таблице заказов появилось новое поле, которое нужно заполнить для старых записей. Запуск одного большого обновления кажется простым: база сама обработает подходящие строки. Но эта операция конкурирует с приложением за процессор, дисковые операции и блокировки. В PostgreSQL обновление создаёт новую версию строки; старая версия не исчезает сразу. Она становится недоступной для новых снимков данных, но место освобождается только после очистки, когда это безопасно.
Поэтому цена backfill — не только время выполнения запроса. Большая запись может увеличить объём WAL — журнала, который PostgreSQL использует для восстановления и репликации*, нагрузить дисковую подсистему и задержать применение изменений на репликах. Если обновление долго держит транзакцию открытой, очистка старых версий строк может затрудниться. А если задача конкурирует с запросами приложения за одни и те же строки, пользовательская задержка вырастет раньше, чем процесс завершится.
Backfill безопасен не тогда, когда его можно запустить. Он безопасен тогда, когда его можно остановить, повторить и продолжить с понятной точки.
* WAL (Write-Ahead Log) — журнал предварительной записи: изменения сначала фиксируются в нём, а затем попадают в файлы данных. Репликация PostgreSQL также передаёт изменения через WAL.
Как развивается типовой инцидент
Часто проблема начинается не с ошибки в SQL, а с неверного представления о масштабе работы. Разработчик проверил обновление на небольшой выборке, увидел корректный результат и запустил ту же операцию для всей таблицы. На небольшом объёме тест не показал, как изменятся время ответа, расход места и задержка репликации.
- Обновление запускают одной транзакцией. Пока она идёт, откат становится дорогим, а освободить захваченные ресурсы нельзя до завершения. Если процесс прервётся, результат может потребовать повторной проверки; если транзакция завершится, длинная запись уже успеет создать нагрузку.
- Текущие запросы начинают конкурировать с backfill. Если фоновой задаче нужны те же строки или индексы, что и приложению, растут ожидания и время ответа. Признак — ухудшение пользовательских метрик одновременно с активной записью.
- Реплика отстаёт, а мониторинг смотрит только на главный узел. На основном сервере операция может выглядеть терпимо, но чтение с реплики уже отдаёт устаревшее состояние. Если приложение зависит от актуальности таких чтений, это становится отдельным пользовательским сбоем.
- Задачу останавливают, но не знают, с какого места продолжать. Повторный запуск без идемпотентности способен перезаписать уже обработанные значения или пропустить записи, появившиеся во время работы.
Это не неизбежная цепочка: на результат влияют размер и устройство таблицы, индексы, скорость накопителя, характер запросов, настройки репликации и свободное место. Важно измерять не только прогресс задачи, но и состояние системы, которой она делит ресурсы.
Что измерить до запуска
На PostgreSQL 16 сначала зафиксируйте исходный фон: типичное время ответа приложения, загрузку диска и процессора, свободное место, активность записи и задержку реплик. Отдельно проверьте размер таблицы и индексов, долю строк, которые нужно менять, и наличие длинных транзакций. Не переносите показатели тестовой базы на прод: важны те же объём данных, профиль запросов и близкая конфигурация инфраструктуры.
На пробном запуске измеряйте время обработки ограниченного диапазона и побочные эффекты. Записывайте прогресс вместе с последним обработанным ключом и числом изменённых строк. Следите, не ухудшаются ли пользовательские метрики и не растёт ли отставание реплики. Заранее определите условия паузы: например, выход задержки ответа или репликации за согласованный с командой порог. Универсального безопасного числа здесь нет — порог зависит от SLO и того, насколько свежие данные нужны приложению.
Стратегии backfill и их цена
| Подход | Что выигрываете | Чем платите и где риск |
|---|---|---|
| Одна большая транзакция | Меньше логики управления процессом; изменения фиксируются вместе. | Долгая транзакция, тяжёлый откат, пиковая запись и слабая управляемость. Подходит только после проверки на объёме, сопоставимом с рабочим. |
| Пакеты с короткими транзакциями | Можно остановить задачу между пакетами, сохранить контрольную точку и ограничивать нагрузку. | Нужны корректное продолжение, контроль повторной обработки и наблюдение между пакетами. Слишком крупные пакеты возвращают часть рисков большого обновления. |
| Новая таблица или поле с постепенным переключением | Можно отделить подготовку данных от переключения приложения и проверить результат до использования. | Появляется дополнительная логика синхронизации и перехода. На время миграции возможны двойная запись, расхождения и дополнительные затраты на хранение. |
Для большинства уже работающих систем отправная точка — короткие пакеты с фиксируемым прогрессом. Важно выбирать диапазоны по устойчивому ключу и после каждого пакета завершать транзакцию. Не смещайте выборку простой схемой «пропустить N строк»: по мере изменений порядок и состав выборки могут меняться, а продолжение станет трудно проверить. Если ключи не отражают порядок появления записей или часть строк не имеет подходящего ключа, способ разбиения нужно подобрать отдельно.
Как сделать процесс возобновляемым
- Сформулируйте условие готовности записи. Опишите, как определить, что значение можно заполнить и что оно ещё не заполнено. Если запись уже обработана, повторный запуск не должен менять корректный результат. Проверка: повторное выполнение на небольшом тестовом наборе не портит готовые значения.
- Зафиксируйте границы работы. Определите, какие записи относятся к историческим данным: например, по ключу или времени создания, если этот признак надёжен. Новые записи должны получать значение обычным путём приложения, иначе во время backfill появится движущаяся цель. Проверка: новые и старые записи покрываются понятными, не пересекающимися правилами.
- Обрабатывайте ограниченные диапазоны. Размер пакета подбирайте на копии с сопоставимой нагрузкой, затем начинайте с консервативного значения. После каждого пакета фиксируйте прогресс, число найденных и изменённых строк. Проверка: после остановки вы можете назвать последнюю завершённую границу и продолжить без пропусков.
- Ограничьте конкуренцию с приложением. Запускайте задачу в период меньшей нагрузки, если это помогает, но не считайте ночное окно гарантией безопасности. Между пакетами оценивайте задержку запросов, очередь задач, свободное место и отставание реплик. Проверка: метрики остаются в согласованных пределах, а при ухудшении backfill можно приостановить.
- Сверьте результат независимо. Сравните количество записей, которые должны быть заполнены, с количеством фактически обработанных; отдельно проверьте выборку по разным диапазонам и пограничные даты. Проверка: повторный аудит не находит необъяснимых пропусков или некорректных значений.
Не запускайте очистку вручную только потому, что после обновления выросло число старых версий строк: сначала проверьте состояние системы и штатную работу обслуживания. Подробности о поведении UPDATE и обслуживании таблиц для PostgreSQL 16 есть в официальной документации.
Где подход ломается
Пакетная обработка не делает задачу автоматически безопасной. Если один диапазон содержит непропорционально много тяжёлых строк, равные по числу записей пакеты могут занимать разное время. Если приложение параллельно меняет те же поля, backfill рискует перезаписать более свежие данные. А если значение рассчитывается по источнику, который сам меняется, важно определить, какое состояние источника считать правильным.
Отдельная ловушка — неполный контрольный пункт. Если прогресс записан раньше, чем завершилась транзакция с данными, процесс может пропустить часть строк после сбоя. Если, наоборот, данные зафиксировались, а отметка о прогрессе нет, диапазон обработается повторно. Идемпотентность снижает риск повторного запуска, но не заменяет проверку порядка фиксации данных и прогресса.
Остановите работу и пересмотрите план, если время ответа вышло за согласованный предел, реплика отстаёт больше допустимого для продукта, свободное место быстро сокращается или после пакета невозможно объяснить число изменённых строк. Не пытайтесь компенсировать проблему увеличением пакета: сначала выясните, какое ограничение стало узким.
Что делать дальше
Перед запуском составьте короткий план отката или паузы, назначьте владельца мониторинга и проведите пробный прогон на репрезентативных данных. После завершения backfill проверьте не только заполненность поля, но и поведение приложения на новых и старых записях, состояние репликации и накопившуюся нагрузку на обслуживание таблиц. Следующий полезный шаг — добавить метрику прогресса и сигнал остановки в сам процесс, чтобы дежурному не приходилось угадывать, безопасно ли продолжать.
Инфографика для маркетплейса: Три главных преимущества товара короткими фразами, выноски в безопасной зоне превью, точный размер площадки.
Спросить за 60 ₽Источники: PostgreSQL 16: UPDATE, PostgreSQL 16: Routine Vacuuming
Частые вопросы
Можно ли просто запустить одно UPDATE для всей таблицы?
Как выбрать размер пакета?
Что будет, если задача упадёт посередине?
Нужно ли останавливать приложение на время backfill?
Почему на реплике данные отстают, хотя основной сервер ещё отвечает?
Можно ли удалить старые версии строк сразу после обновления?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.