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

Импорт из CSV, API или старой базы данных редко ломается на этапе чтения файла. Проблемы начинаются позже: одинаковые записи создаются повторно, даты меняют смысл из-за часового пояса, частично загруженный файл принимают за успешный, а повторный запуск портит уже обработанные данные. Правильно спроектированный импорт заранее разделяет входные данные, проверку, преобразование и запись, поэтому сбой можно локализовать, исправить и безопасно повторить.
Сначала зафиксируйте контракт импорта
До выбора очереди, ORM и формата файла опишите, что именно система принимает и какой результат обязуется получить. Контракт должен быть понятен не только разработчику: его используют владелец данных, аналитик, тестировщик и тот, кто будет разбирать ошибку ночью.
- Источник. Файл, HTTP API, выгрузка из базы или ручная форма. Укажите владельца источника и способ получения.
- Схема. Названия полей, обязательность, типы, допустимые значения, максимальная длина строк.
- Идентичность. Поле или набор полей, по которым система понимает, что две строки описывают один объект.
- Семантика пустых значений. Пустая строка может означать «неизвестно», «удалить прежнее значение» или ошибку. Это три разных сценария.
- Результат. Сколько объектов должно появиться, какие связи обновятся и как подтвердить полноту загрузки.
Проверка результата на этом шаге простая: другой разработчик должен суметь составить пример корректной строки и перечислить минимум пять причин, по которым она будет отклонена.
Импорт считается успешным не тогда, когда процесс завершился без исключения, а когда можно доказать, какие данные приняты, какие отклонены и почему.
Выберите границу между этапами
Надёжная схема обычно состоит из пяти логических этапов: получение, сохранение исходника, проверка, преобразование и публикация в рабочие таблицы. Не обязательно разносить их по пяти сервисам. Важно, чтобы у каждого этапа были собственные вход, выход и статус.
Сохраняйте исходные данные до преобразования
Не начинайте импорт с записи прямо в рабочую таблицу. Сначала сохраните файл или ответ API в неизменном виде, присвойте запуску уникальный идентификатор и запишите метаданные: источник, время получения, размер, контрольную сумму, версию схемы и инициатора запуска.
Так вы сможете повторить преобразование без нового запроса к поставщику данных и доказать, какой именно вход породил конкретную запись. Секреты, токены и персональные данные не следует бездумно помещать в журналы: для них нужны отдельные правила хранения и маскирования.
Разделяйте синтаксические и бизнес-проверки
Синтаксическая проверка отвечает на вопрос «можно ли прочитать значение». Например, дата имеет допустимый формат, число не содержит лишних символов, а обязательное поле заполнено. Бизнес-проверка отвечает на другой вопрос: допустимо ли это значение для вашей предметной области.
Эти ошибки нужно хранить раздельно. Иначе сообщение «неверное значение» не объясняет оператору, что исправить. Для каждой ошибки полезно сохранять номер строки или внешний идентификатор, поле, код причины и безопасный фрагмент исходного значения.
Преобразование не должно менять смысл молча
Опаснее всего тихие исправления: обрезание длинной строки, округление денежных значений, автоматическое угадывание формата даты и замена неизвестного статуса на значение по умолчанию. Если преобразование необходимо, зафиксируйте его в контракте и сделайте измеряемым.
Часовой пояс, валюта, разделитель CSV и кодировка должны быть параметрами импорта, а не догадками программы. Для CSV отдельно проверьте кавычки, переносы строк внутри поля и разделитель. Стандарт RFC 4180 описывает распространённый формат CSV, но конкретный поставщик может использовать дополнительные соглашения.
Спроектируйте повторный запуск
Сбой неизбежен: оборвётся сеть, закончится место на диске, изменится схема источника или упадёт база. Поэтому главный вопрос не «как избежать любой ошибки», а «что произойдёт, если этот запуск повторить через минуту».
Идемпотентность и уникальный ключ
Идемпотентность — свойство операции, при котором повторное выполнение с теми же входными данными приводит к тому же результату, а не создаёт новые копии. Для импорта она обычно строится вокруг стабильного внешнего идентификатора и ограничения уникальности в базе.
Не используйте номер строки как идентификатор: порядок строк может измениться. Если источник не даёт стабильный ключ, сформулируйте правило составного ключа или заведите таблицу сопоставления внешних и внутренних идентификаторов. Хеш от всей строки может помочь обнаруживать изменения, но не заменяет идентификатор объекта: изменение адреса не должно превращать человека в новую сущность.
Выберите стратегию записи
| Стратегия | Когда подходит | Цена и риск |
|---|---|---|
| Только новые записи | Источник содержит одноразовые события или справочник пополняется, но не изменяется | Проста, но изменения существующих объектов не попадут в систему |
| Добавить или обновить по ключу | Источник является актуальным снимком объектов | Нужно заранее определить, что делать с пустыми полями и удалёнными объектами |
| Полная замена набора | Небольшой справочник можно целиком пересобрать из доверенного источника | Нужна безопасная публикация новой версии; ошибка входного файла может заменить корректные данные плохими |
| Событийная загрузка | Важен порядок изменений и история операций | Сложнее повторная обработка, дедупликация и восстановление последовательности |
Если импорт запускается повторно после частичного сбоя, система должна определить уже принятые элементы и не создать их заново. Для этого недостаточно записи в логе приложения: проверка уникальности должна быть защищена на уровне хранилища.
Транзакция не решает всё
Одна большая транзакция выглядит безопасно: либо загрузится всё, либо ничего. Но на большом наборе она удерживает блокировки, увеличивает объём журнала базы и усложняет восстановление. Одна транзакция на каждую строку, наоборот, снижает целостность группы и создаёт много служебных операций.
Границу фиксации выбирают по бизнес-смыслу. Если строки независимы, допустимы порции фиксированного размера. Если объект состоит из нескольких связанных записей, их нужно публиковать вместе. В любом варианте импорт не должен показывать пользователям половину объекта.
Безопасная схема для пакетной загрузки выглядит так: сначала данные попадают во временное или промежуточное хранилище, затем проходят проверки, после чего система публикует принятую версию. Если отдельные строки можно отклонить, это должно быть видно в отчёте, а не скрыто в логах.
Сделайте импорт наблюдаемым
Минимальный журнал запуска должен отвечать на шесть вопросов: кто запустил процесс, какой источник использован, сколько строк получено, сколько принято, сколько отклонено, сколько обновлено и на каком этапе произошёл сбой. Добавьте время каждого этапа и идентификатор запуска, который можно передать в поддержку.
- Храните итоговые счётчики: получено, прочитано, принято, обновлено, пропущено, отклонено.
- Разделяйте технические ошибки и ошибки данных. Недоступная база и неизвестный статус товара требуют разных действий.
- Сохраняйте отчёт об ошибках с ограничением размера и сроком хранения.
- Проверяйте контрольные суммы или другие признаки полноты, если источник их предоставляет.
- Настройте уведомление не только о падении, но и о подозрительном результате: например, когда объём загрузки сильно отличается от обычного.
Порог «подозрительного объёма» нельзя выдумать универсальным числом. Его устанавливают по истории конкретного источника и пересматривают после сезонных изменений.
Где подход ломается
Источник меняет схему без предупреждения
Добавление необязательного поля обычно безопаснее, чем переименование существующего. Но даже добавленное поле может сломать строгий парсер. Сохраняйте версию схемы, проверяйте заголовки до обработки данных и завершайте запуск с понятной ошибкой, если исчезло обязательное поле.
Повторная доставка создаёт дубли
Сетевой клиент может не знать, был ли запрос принят сервером источника до разрыва соединения. Повторная отправка в такой ситуации нормальна. Дубликаты предотвращаются стабильным ключом и повторяемой операцией записи, а не надеждой на «один запуск».
Часть данных принята, часть отклонена
Для массовой загрузки заранее определите режим: атомарный, когда ошибка останавливает весь набор, или частичный, когда корректные записи принимаются, а ошибочные возвращаются в отчёте. Нельзя смешивать режимы незаметно для пользователя. Особенно опасно считать частично принятую выгрузку полной.
Изменения приходят не по порядку
Это типично для очередей, нескольких источников и повторной доставки. Храните время источника и время обработки отдельно, а для конфликтующих изменений задайте правило: версия объекта, монотонный номер события или приоритет доверенного источника. Если такого правила нет, последнее обработанное событие может перезаписать более новое.
Что делать дальше
Возьмите один реальный импорт и опишите его контракт на одной странице: источник, ключ, схема, правила пустых значений, режим ошибок и критерий успешности. Затем прогоните четыре сценария: повтор того же файла, обрыв после середины, лишнее поле и повреждённую строку. После этого добавьте метрики и отчёт по каждому запуску.
В проде смотрите не только на длительность. Важнее доля отклонённых строк, количество повторных запусков, расхождение итоговых счётчиков и возраст последнего успешно опубликованного набора. Инвалидация кеша и синхронизация поискового индекса — отдельные темы; здесь важно лишь включить их в план публикации, если импорт меняет данные, которые читают другие системы.
Инфобез и 152-ФЗ: Аудит политик конфиденциальности, правила сбора cookie.
Спросить за 20 ₽Источники: PostgreSQL Documentation: Populating a Database, RFC 4180: Common Format and MIME Type for Comma-Separated Values (CSV) Files
Частые вопросы
Нужно ли всегда загружать данные через промежуточную таблицу?
Что делать, если источник не предоставляет уникальный идентификатор?
Можно ли просто откатить импорт одной транзакцией?
Что будет, если повторно запустить импорт после сбоя?
Как поступить, если в одной выгрузке есть ошибочные строки?
Что проверить, если импорт работает на тестовом стенде, но не в проде?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.