Как проверить, что выгрузка полная: сверка по числам, а не на глаз
Выгрузка прошла без ошибок, файл открылся, строки выглядят нормально — и на этом проверку часто заканчивают. А через пару недель выясняется, что в CRM было 48 200 клиентов, а в новой системе после переноса — 46 900: полторы тысячи записей просто не доехали, и никто этого не заметил, потому что «выглядело нормально». Единственный объективный способ проверить полноту выгрузки — сверить точные числа: сколько записей в источнике и сколько в файле или базе, которую вы получили. Дальше — как это делать на практике и на каких двух граблях спотыкаются чаще всего.
Содержание
Почему «на глаз» не работает
Человек хорошо замечает структурные аномалии — пустые колонки, битую кодировку, сдвинутые поля. Но он физически не способен заметить отсутствие 3% строк в файле на 50 000 записей: пропавшие строки не оставляют дыры, файл просто короче, и это незаметно при пролистывании. Ещё хуже, если пропадают не случайные строки, а системные — например, все записи одного отдела или одного периода: выборка «на глаз» выглядит однородной и правдоподобной именно потому, что оставшиеся данные внутренне непротиворечивы.
Проблема усугубляется тем, что большинство инструментов экспорта не падают с ошибкой при частичной выгрузке. Обрыв соединения на середине пагинации, лимит API в 10 000 записей за вызов, таймаут на медленном запросе, кодировка, которая не смогла обработать одну строку и тихо её пропустила — всё это обычно даёт на выходе валидный файл без единого сообщения об ошибке. С точки зрения формата выгрузка успешна. С точки зрения полноты — нет. Отличить одно от другого можно только числом, а не просмотром.
Держите это правило простым: выгрузка считается проверенной, когда для неё явно зафиксировано ожидаемое количество записей и это количество совпало с фактическим. Если такого числа нет — выгрузка не проверена, что бы ни показывал беглый просмотр файла.
Точное число в источнике: отчёты, счётчики, API
Первый шаг — получить точное количество записей в исходной системе её собственными средствами, а не оценкой. Вариантов обычно несколько, и они не равноценны по надёжности.
Прямой запрос к базе, если есть доступ, — самый надёжный вариант:
SELECT COUNT(*) FROM clients;
SELECT COUNT(*) FROM orders WHERE created_at BETWEEN '2020-01-01' AND '2026-08-31';
API с полем total или заголовком счётчика. Многие REST API возвращают общее количество отдельно от постраничных данных — не нужно вычитывать все страницы, чтобы узнать итог:
curl -s "https://api.example.com/v1/contacts?page=1&per_page=1" -H "Authorization: Bearer $TOKEN" \
| jq '.total_count'
# либо количество приходит в заголовке ответа
curl -sI "https://api.example.com/v1/contacts" -H "Authorization: Bearer $TOKEN" \
| grep -i x-total-count
Для Bitrix24, например, методы вида crm.item.list возвращают поле total в ответе — его и нужно взять за опорное число, а не количество строк на текущей странице интерфейса.
Отчёт внутри системы. В 1С это может быть универсальный отчёт с группировкой и итоговой строкой количества; в CRM — отчёт «Все контакты» с явным счётчиком в подвале таблицы. Здесь важно не путать число, показанное как «эффектная витрина» (иконка с количеством в меню раздела), с числом из полноценного отчёта — первое часто кешируется и обновляется с задержкой в часы.
Счётчик в интерфейсе — самый ненадёжный вариант, если это не точный отчёт. Многие интерфейсы округляют или ограничивают отображение: «999+», «более 10 000», «показано 1–50 из многих». Такое число годится только как грубая прикидка масштаба, но не как контрольная цифра для сверки — по нему легко пропустить расхождение в тысячи записей, если реальное число «999+» окажется 1400 или 2200.
Зафиксируйте это число письменно вместе с датой и временем получения (запись может измениться, пока вы готовите выгрузку) и с точным описанием, что именно оно включает — это пригодится в следующем разделе, когда вы столкнётесь с фильтрами по умолчанию.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверСверка: сравниваем числа, а не ощущения
Когда выгрузка получена, посчитайте записи в ней тем же способом, что и число из источника — точно, без округлений.
Для CSV — с поправкой на заголовок:
# -1 если первая строка — заголовок
echo $(($(wc -l < export.csv) - 1))
Для JSON-массива:
jq 'length' export.json
Для выгрузки, уже загруженной в промежуточную (staging) таблицу перед импортом в целевую систему, — тот же SELECT COUNT(*), что и в источнике:
SELECT COUNT(*) FROM staging.clients;
Дальше — простое сравнение: источник = выгрузка, или нет. Здесь важно не давать себе право на «примерно совпадает» или «расхождение небольшое, наверное, пара тестовых записей». Расхождение в 5 записей из 50 000 — не мелочь, а сигнал: либо это реальные пропавшие записи, либо это записи, изменившиеся между моментом снятия контрольного числа и моментом выгрузки (гонка во времени, если источник продолжал принимать запись во время экспорта — та же проблема, что разбирается в статье про несогласованный дамп из-за долгого снятия). В обоих случаях это требует объяснения, а не молчаливого пропуска.
Практическое правило: если выгрузка занимает существенное время, снимайте контрольное число из источника дважды — непосредственно перед стартом и сразу после завершения выгрузки. Если оба числа совпадают между собой и с числом в выгрузке — источник был статичен всё время экспорта, и сверка чистая. Если числа из источника разошлись сами по себе (до и после), это ожидаемо и нормально, но тогда сравнивать выгрузку нужно с диапазоном, а не с единственной точкой.
Фильтры по умолчанию, которые молча режут выборку
Самая частая причина «числа не совпали, хотя выгрузка вроде правильная» — контрольное число из источника само посчитано не по полной выборке, а по отфильтрованной, и вы этого не заметили.
Практически любой отчётный интерфейс применяет фильтры по умолчанию, которые не бросаются в глаза:
- Статус записи — отчёт «Клиенты» может по умолчанию показывать только активных, исключая заблокированных, отписавшихся, помеченных дублями.
- Период — отчёт может открываться с диапазоном «последние 12 месяцев» или «текущий финансовый год», а не за всё время.
- Владелец/ответственный — CRM нередко по умолчанию фильтрует «мои записи» или «записи моего отдела», особенно если отчёт открыт под конкретным пользователем, а не под администратором.
- Тип записи — в системах с общей таблицей для разных сущностей (например, задачи и подзадачи в одной таблице) отчёт может по умолчанию скрывать подзадачи или системные записи.
- Тестовые/демо-записи — некоторые системы автоматически прячут записи с пометкой sandbox или test, оставляя их в базе.
Результат — контрольное число из отчёта оказывается меньше, чем реальное количество строк в таблице источника, и вы либо ошибочно решаете, что выгрузка «выгрузила лишнее», либо, что хуже, подгоняете выгрузку под заниженное число, обрезая нужные данные.
Проверка простая: перед тем как брать контрольное число за опорное, явно откройте настройки фильтра отчёта или запроса и убедитесь, что он охватывает «все статусы, весь период, все записи» — либо осознанно зафиксируйте, что выгрузка тоже должна учитывать те же ограничения, и запишите их текстом рядом с числом. Формулировка «источник показывает 12 400 контактов» бесполезна без уточнения «активные, за всё время, включая заблокированных — да/нет».
Архивные и удалённые записи — отдельная категория расхождений
Даже когда фильтры по периоду и статусу учтены, остаётся отдельный класс данных, который стандартные отчёты почти всегда исключают по умолчанию: архивные и логически удалённые (soft-deleted) записи. Они физически есть в базе источника, но не видны ни в интерфейсе, ни в большинстве встроенных отчётов — именно поэтому их проще всего забыть при сверке.
Типичная реализация — колонка-флаг вместо реального удаления строки:
-- сколько видит обычный отчёт (то самое "активное" число)
SELECT COUNT(*) FROM contracts WHERE deleted_at IS NULL;
-- сколько есть в таблице физически
SELECT COUNT(*) FROM contracts;
-- архивные/удалённые отдельно
SELECT COUNT(*) FROM contracts WHERE deleted_at IS NOT NULL;
Если задача — перенос без потери истории или выгрузка для юридического архива, то нужное контрольное число — это сумма всех трёх, а не только «активные». Если же цель — перенос только рабочих, актуальных данных (например, миграция в новую CRM, где старые заблокированные лиды сознательно не нужны), то ориентиром должно быть число активных, и это тоже нужно зафиксировать явно — иначе через полгода кто-то откроет старую систему, увидит там на 1400 записей больше и решит, что миграция была неполной, хотя расхождение — задуманное.
Ключевая мысль: прежде чем сверять числа, ответьте на вопрос «должна ли полная выгрузка включать архив и удалённые записи для этой конкретной задачи» — и зафиксируйте ответ письменно. Без этого решения сверка формально «сойдётся», но будет проверять не то, что нужно.
Похожая логика применима и к самому процессу переноса: если вы переносите базу целиком между серверами, а не только выгружаете отчётные данные, будет полезно посмотреть на чек-лист проверки успешности миграции — там разбирается более широкий набор проверок, из которых сверка по числам записей — только первый, хотя и обязательный, пункт.
Что делать при расхождении: разбор, а не игнор
Если контрольное число и число в выгрузке не совпали — выгрузка не считается успешной, точка. Расхождение нельзя закрыть повторным запуском «на всякий случай» без понимания причины: если причина систематическая (лимит API, таймаут, фильтр), повторный запуск с теми же настройками даст тот же результат.
Порядок разбора, который работает на практике:
- Проверьте логи инструмента экспорта. Многие ETL-инструменты и скрипты на базе API пишут в лог пропущенные или завершившиеся с ошибкой запросы, даже если общий процесс отчитался «успешно». Ищите строки про retry, timeout, rate limit, skipped record.
- Проверьте лимиты пагинации и rate limit API. Частая причина — экспорт останавливается на определённой странице из-за ограничения запросов в минуту, а обработчик ошибок молча завершает цикл вместо падения с исключением. Прогоните выгрузку с логированием номера каждой полученной страницы и сверьте:
количество_страниц × размер_страницыдолжно давать примерно ожидаемое число.
- Разбейте сверку по частям. Если полное число не совпадает, посчитайте контрольные числа по диапазонам — по месяцам создания, по отделам, по диапазону ID — и сравните с тем же диапазоном в выгрузке. Это быстро локализует, где именно теряются данные: в конкретном периоде, у конкретного статуса, или равномерно по всему объёму (что чаще указывает на кодировку или битые символы, из-за которых парсер выгрузки тихо отбрасывает строку — этот сценарий подробно разобран в статье про то, как CSV незаметно теряет данные при обмене между системами).
- Проверьте, не была ли выгрузка нецелостной по времени. Если источник продолжал принимать запись, пока шёл долгий экспорт, часть новых записей могла не попасть в выгрузку не по ошибке, а потому что появилась уже после того, как соответствующая таблица была прочитана. Это не баг инструмента, а следствие способа снятия данных — тот же класс проблем, что у
mysqldumpбез--single-transaction, разобранный в статье про несогласованный бэкап без единой транзакции.
- Задокументируйте итог. Если расхождение объяснено и приемлемо (например, разница — это записи, физически удалённые в источнике между снятием контрольного числа и выгрузкой), зафиксируйте формулу расхождения текстом: «источник N, выгрузка N−k, где k — записи, удалённые в интервале между X и Y, подтверждено логом изменений». Без такой записи следующий человек, который откроет оба числа, снова не будет знать, ошибка это или нет.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверНужны сами нейросети для контента?
Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.
Частые вопросы
Что делать, если источник вообще не даёт точного числа — ни через отчёт, ни через API?
Ищите способ получить его окольным путём: прямой SQL-запрос при наличии доступа к базе, экспорт полного списка ID (без данных, только идентификаторы) и подсчёт строк, обращение в поддержку системы с прямым вопросом «сколько записей типа X у нас в базе на сегодня». Если ни один способ недоступен, явно зафиксируйте это ограничение в отчёте о выгрузке — «точная сверка невозможна, доступен только приблизительный счётчик интерфейса» — это честнее, чем притвориться, что сверка была сделана.
Нужно ли повторять сверку при каждой регулярной выгрузке или достаточно один раз?
Для разовой миграции или переноса — сверка нужна один раз, но тщательно. Для регулярных выгрузок (ежедневный экспорт в хранилище, синхронизация между системами) сверку стоит автоматизировать: сравнивать количество записей за период на каждом запуске и алармить при расхождении сверх допустимого порога — вручную проверять каждый прогон никто не будет, и именно поэтому там чаще всего и накапливаются незамеченные потери.
Дубли в источнике могут исказить сверку?
Да, в обе стороны. Если источник считает дубли как отдельные записи, а выгрузка их дедуплицирует (или наоборот), числа разойдутся закономерно, а не из-за потери данных. Прежде чем паниковать из-за расхождения, проверьте, не отличается ли логика подсчёта дублей у контрольного отчёта и у инструмента выгрузки.
Числа совпали — значит, с данными всё точно в порядке?
Совпадение чисел подтверждает только полноту по количеству записей — что все строки на месте. Это не гарантирует целостность содержимого: правильность значений в полях, отсутствие обрезанных строк, корректность связей между таблицами. Сверка по числам — необходимая, но не единственная проверка; для связанных таблиц полезно дополнительно свериться по сумме или контрольной агрегатной метрике (например, сумма поля amount по всем заказам), которая чувствительна не только к количеству строк, но и к их содержимому.
Сколько времени в среднем занимает такая сверка?
Для одной таблицы или сущности — минуты, если контрольное число доступно через API или прямой SQL. Основное время уходит не на сам подсчёт, а на выяснение, что именно контрольное число включает — какие фильтры, какой статус, какой период. Закладывайте время именно на это выяснение, а не на техническую часть сравнения.
Обсудить статью, задать вопрос или начать новую тему
Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.
Перейти в сообщество →