Миграция данных старого трекера начинается с проверки конкретной выгрузки. Определите, какие её поля связывают конверсию с кликом и какие связи сохранились в строках. При переносе нельзя считать отсутствующее значение восстановленным без отдельного источника. Проверку разделите на три части: ключи связи, смысл временных полей и записи с неполными связями.
Отдельная задача - не оборвать приём постбеков при смене системы, она разобрана в материале переход между трекерами без потери конверсий. Здесь переключать ещё нечего. Миграция данных старого трекера касается меньшего, и вопрос её старше: что уезжает из старой системы как доказательство и когда этого доказательства достаточно, чтобы объявить переход.
Миграция данных старого трекера: ревизия по строкам и ключам
Формулировка «история у нас полная» не сводится к проверяемому признаку на уровне кампании. Есть набор полей у каждой записи, и каждое поле попадает в один из трёх классов: уезжает (лежит в выгрузке под документированным именем), выводимо (восстанавливается из других сохранённых колонок, и способ восстановления записан), потеряно (не хранилось вовсе или хранится там, куда выгрузка не дотянется).
| Поле строки конверсии | Где держится ключ | Что даёт документированная выгрузка | Класс |
|---|---|---|---|
| Идентификатор клика | собственный click id трекера, приходит в постбеке параметром subid | значение плюс имя параметра, под которым оно пришло | уезжает |
| Тот же идентификатор под чужим именем | таблица алиасов в настройках постбека | сама таблица алиасов, иначе смысл значения неизвестен | выводимо, если настройки вывезли |
| Идентификатор транзакции | параметр tid, отделяющий повторную конверсию от перезаписи | по строке на каждое уникальное tid | уезжает |
| Выплата и её валюта | параметры постбека, иногда отсутствуют | сумма, возможно без валюты | выводимо или потеряно |
| Контекст визита для конверсии | запись клика, с которой сработало сопоставление | только если ключ клика разрешился | часто потерян |
Документация Keitaro описывает требование к связи прямым текстом: «Without subid and status , the tracker cannot correctly process the postback.» (Postback, документация Keitaro) На этом вся ревизия ключей и держится: строка переносима ровно в той мере, в какой в ней жив ключ клика. Там же предупреждение о перезаписи: параметры вида cost, external_id, creative_id приезжают поверх сохранённых ранее значений клика - «will overwrite data previously recorded with the click from the traffic source».
Вместе с выгрузкой сохраните правила сопоставления полей. Если clickid настроен как алиас ключа клика, это соответствие нужно перенести в описание схемы. В примере связь проверяют через идентификатор клика; для другого источника сначала определите его документированные ключи. В отчёте укажите долю строк, где проверяемая связь сохранилась, и отдельно перечислите строки, для которых её подтвердить не удалось.
Какое время лежит в строке на самом деле
Колонки времени переносят чаще всего неправильно, потому что имя поля обещает больше, чем сохранённое значение. В старой строке конверсии может стоять момент клика, момент исхода у рекламодателя, момент, когда трекер принял постбек, и момент, когда сводка собралась в отчёте.
Расхождение описано документами, это не гипотеза:
В трекере конверсии записываются по дате клика, но журнал конверсий строится по дате конверсии: дате получения постбека трекером.
Правило выгрузки Keitaro уточняет: «Конверсии записываются по дате первого постбека, полученного для subid с каждым уникальным значением tid» (перевод). Поэтому дату в CSV журнала конверсий нужно описать как дату приёма постбека и учитывать этот смысл при построении сводки. Для проверки доступности идентификаторов используйте справочник идентификаторов клика.
| Временная база | Кто её записывает | Есть ли в старой выгрузке | Как её подписать |
|---|---|---|---|
| Клик в момент редиректа | трекер | да, если запись клика уцелела | visit_time |
| Исход у рекламодателя | партнёрская сеть или ваш сайт | только если приехало параметром | advertiser_time, часто пусто |
| Приём постбека | трекер, когда обработал запрос | именно по нему сортируется журнал | receipt_time |
| Сборка отчёта | ваш дашборд | только в таблицах самого дашборда | это не поле строки |
| Визит для разорванной связи | никто | подменено временем приёма | не восстанавливается, требует пометки |
Доступность выгрузки и удаление данных ограничивают разные части проверки. Voluum Traffic Log позволяет получить данные давностью до четырёх месяцев, но выбранный интервал одного отчёта не может превышать семь дней. Это глубина доступной истории и размер интервала запроса, а не срок хранения данных (Traffic log, документация Voluum). В Keitaro настройка Stats store term управляет автоматической очисткой статистики; удалённую статистику восстановить нельзя (Cleaning statistics, документация Keitaro). Архив Keitaro отдельно хранит удалённые ресурсы, например кампании и потоки. Его срок настраивается и по умолчанию составляет 60 дней, после чего эти ресурсы удаляются без возможности восстановления (Archive, документация Keitaro).
До отключения источника запишите ограничения отдельно: какие даты доступны в выгрузке, какой интервал охватывает запрос, когда удаляется статистика и какие ресурсы ещё можно восстановить из архива.
Что источник терял до всякой миграции
Voluum описывает запасной способ регистрации конверсии без click ID. Конверсия связывается с кампанией, а не с визитом; временем визита становится время постбека. Каждый постбек считается уникальным, фильтрации дублей нет, а валюта по умолчанию USD, если её не передали отдельно. В базовом варианте часть контекста визита отсутствует. Дополнительные идентификаторы оффера, лендинга, пути или потока позволяют связать конверсию с отдельными элементами воронки; пользовательские переменные тоже нужно передавать явно. Полный визит эти дополнения не восстанавливают (Registering Conversions Without Click IDs, документация Voluum).
Запрос, который строкой так и не стал, оставляет другой след. Keitaro пишет в журнал отброшенное: «If the status parameter is missing, the tracker ignores the postback and logs the event.» Этот журнал отброшенных запросов и есть доказательство разрыва между тем, что отправил партнёр, и тем, что записал источник, а в выгрузку конверсий он не входит вообще (чек-лист диагностики постбеков разбирает границу в живой работе, задача переноса там не рассматривается).
Миграция данных старого трекера требует отдельного подсчёта записей с неполными связями. Разделите опись на три группы.
- Строки без ключа - конверсии, у которых идентификатора клика нет, привязка обычно к кампании или офферу. При переносе сохраняйте доступный контекст кампании или отдельных элементов воронки, не выдавая его за восстановленный визит.
- Строки с удалённой второй стороной - ключ был, а записи клика уже нет: срок хранения истёк, дамп выгрузили без таблицы кликов, таблица кликов не сохранилась. Сумма осталась, связь исчезла.
- Незаписанные исходы - запросы, которые источник проигнорировал или не получил. В выгрузке их не представляет ничего, остаются только журнал отброшенных и отчётность партнёра (двойные конверсии между трекером, сетью и платформой ведёт механику повторов, здесь
tidвсего лишь колонка для классификации).
Две ленты событий: повторяемое сравнение
Для повторяемого сравнения двух лент зафиксируйте один интервал, ключ сопоставления и временную базу. Каждое расхождение отнесите к названной группе. До сравнения согласуйте смысл колонок.
click_key,tid,time_base,time_value,offer_id,lander_id,payout,currency,link_classПоле time_base не украшение: оно указывает, клик или приём записан в time_value, и миграция данных старого трекера без него молча склеивает два журнала, расходящихся по построению. Колонка link_class несёт корзину из предыдущего раздела, тогда разорванная запись остаётся разорванной в новой ленте и не получает правдоподобный визит.
Одного click_key для связи мало. Журнал конверсий описан документированно и содержит записи «for one subid with different tid»: повторные конверсии одного клика различаются ровно полем tid. Сравнение по одному только click_key схлопнет такие строки в одну и покажет это как совпадение. Ниже ключ собирается из пары click_key + tid, и именно эту пару лестница наблюдений дальше называет принятой записью. Пара склеивается литеральным #, и ключ остаётся однозначным, пока этого символа нет ни в одной из колонок; строку с # внутри click_key или tid охранное условие ниже отсекает, иначе одна склейка начала бы означать две разные пары.
# сохраняем как compare_feeds.sh и запускаем bash'ом; один закрытый интервал с двух сторон
set -euo pipefail
TB=receipt_time # то значение time_base, на котором договорились обе ленты
printf 'click_key,tid,time_base,time_value,link_class\nsubid-1,t-1,receipt_time,2026-08-03T11:20:05Z,carried\nsubid-2,t-1,receipt_time,2026-08-04T09:10:00Z,carried\nsubid-3,t-2,receipt_time,2026-08-05T18:42:11Z,purged_key\nsubid-4,,receipt_time,2026-08-05T19:00:00Z,unkeyed\n' > old_export.csv
printf 'click_key,tid,time_base,time_value,link_class\nsubid-1,t-1,receipt_time,2026-08-03T11:20:05Z,carried\nsubid-2,t-1,receipt_time,2026-08-04T09:15:00Z,carried\nsubid-9,t-7,receipt_time,2026-08-06T07:00:00Z,carried\n' > new_feed.csv
: > excluded_rows.csv # строки, которые охранное условие не пустило в join, остаются видны
keyed() { # одна лента -> строки "click_key#tid,time_value"
tail -n +2 "$1" | awk -F, -v tb="$TB" -v bad=excluded_rows.csv '
BEGIN { OFS = "," }
$3 == tb && $1 != "" && $2 != "" && $1 !~ /#/ && $2 !~ /#/ { print $1 "#" $2, $4; next }
{ print >> bad }'
}
join -t, -a1 -a2 -e MISSING -o 1.1,2.1,1.2,2.2 \
<(keyed old_export.csv | LC_ALL=C sort -t, -k1,1) \
<(keyed new_feed.csv | LC_ALL=C sort -t, -k1,1) > joined_rows.csv
awk -F, '{ if ($1 == $2 && $3 == $4) same++
else if ($2 == "MISSING") only_old++
else if ($1 == "MISSING") only_new++
else time_diff++ }
END { printf "joined_rows %d\nsame_key_same_time %d\nonly_in_old_export %d\nonly_in_new_feed %d\nkey_matched_time_differs %d\n",
NR, same+0, only_old+0, only_new+0, time_diff+0 }' joined_rows.csv > bucket_counts.txt
printf 'excluded_unkeyable_rows %d\n' "$(awk 'END { print NR+0 }' excluded_rows.csv)" >> bucket_counts.txt
cat bucket_counts.txtПеречень полей выше задаёт авторский формат сравнения. Сопоставьте его с документированной схемой своего источника. В скрипте заголовок удаляется до сортировки, а строки, не прошедшие проверку ключа, сохраняются в excluded_rows.csv. Просматривайте этот файл вместе с результатом сопоставления. Перед миграцией адаптируйте разбор строк и правила ключа к реальной выгрузке.
Ожидаемый вывод для встроенного примера, шесть строк:
joined_rows 4
same_key_same_time 1
only_in_old_export 1
only_in_new_feed 1
key_matched_time_differs 1
excluded_unkeyable_rows 1В CSV-контракте выше девять колонок. Bash-фикстура использует пять из них: click_key, tid, time_base, time_value, link_class. Поля offer_id, lander_id, payout, currency в неё не включены и в сравнении не участвуют. Последняя старая строка показывает работу охранного условия: у subid-4 пустой tid, поэтому в сопоставление она не попадает, и joined_rows 4 считает только строки с ключом. Отказ тоже остаётся видимым: такая строка дописывается в excluded_rows.csv, а шестой счётчик печатает число строк, до которых сравнение не дошло, то есть шагу 4 есть что объяснять числом. Значения unkeyed и purged_key лежат в колонке link_class, поэтому строка без ключа принадлежит той же описи, что и четыре корзины. Молча её потерять значит получить честное сравнение по подмножеству, которое никто не называл.
Шесть шагов, у каждого свой артефакт:
- Зафиксируйте снимок данных за выбранный исторический интервал и сравнивайте его неизменяемые копии. Поздно поступившие записи учитывайте отдельно; приём рабочих постбеков продолжайте.
- Повторите выгрузку из того же снимка и сравните файлы. Если они различаются, проверьте фильтры, настройки экспорта и выбранный снимок до дальнейшего сравнения.
- Запустите блок выше как скрипт и сохраните
bucket_counts.txt: шесть счётчиков на шести строках, рядомexcluded_rows.csv. Миграция данных старого трекера требует сохранённых файлов для повторной проверки. - Объясните каждую корзину классом: удалённый ключ, разорванная связь, поздний приём, фильтр в новой ленте, строки, которые отсечало условие пустого
tidили символа#. Необъяснённый остаток это находка, погрешность тут ни при чём. - Пересоберите с обеих сторон метрику, по которой вы отчитываетесь (выплаты по офферу, число по статусу), и сверяйте её; построчного сопоставления тут недостаточно.
- Отдельно выпишите, чего ревизия не достала: строки, чей клик давно вышел за документированное окно выгрузки.
Критерий перехода и его пределы
Критерий держится на трёх условиях, каждое проверяемо другим человеком:
- у каждой корзины сравнения названа причина, остаток либо ноль строк, либо явно перечисленное исключение;
- миграция данных старого трекера даёт покрытие по интервалам и по офферам, потому что сквозная цифра прячет кампании, где из источника нечего было выгрузить;
- то же сравнение после остановки старого приёма воспроизводит счётчики, значит, доверять можно самой ленте событий, а не скрипту разовой выгрузки.
Универсальной цели по доле покрытия в миграции данных старого трекера нет: знаменатель это ваши собственные строки, любая цифра из поста вендора описывает кампанийный микс этого вендора. Лестница наблюдений удерживает формулировку на её уровне.
| Уровень | Чем доказан | Чем он не доказан |
|---|---|---|
| Отправлено | исходящий журнал партнёра или сети | что трекер понял запрос |
| Получено | строка в собственном журнале запросов трекера, каким бы ни было его решение | что вызов стал строкой конверсии, потому что журнал из цитаты выше пишет отказ так же аккуратно, как и приём |
| Принято | строка конверсии с ключом click_key и tid | что строка доедет до отчёта и что выгрузка до неё ещё дотянется |
| Вывезено | воспроизводимый файл внутри документированного окна, где эта строка лежит | что новая лента посчитала запись |
| Посчитано | строка в новой ленте или в отчёте по ней | атрибуцию и результаты торгов внутри рекламной платформы |
Последняя строка и есть настоящая граница, и продуктовая страница формулирует её прямо: записи об интеграции «do not promise attribution or bidding outcomes inside an ad platform» (Way2 Telegram tracking, доказательства и границы). Принимает ли приёмник историю задним числом и в каком окне, отдельная проверка; для Telegram-контура она разобрана в статье восстановимость истории в Telegram, здесь сознательно не решается.
Отдельно держатся два предела. При переносе различайте полный визит и доступный контекст кампании или отдельных элементов воронки для конверсии без ключа. Дамп это ещё не ревизия: полный перенос сервера автоматически увозит статистику и настройки вместе с postback-ключом (Transfer Tracker to Another Server, документация Keitaro), а новую систему всё равно кто-то должен спросить, какая временная база лежит в каждой колонке.
Одинаковые суммы с двух сторон могут маскировать смещённую временную базу, разорванные записи, показанные как пути по воронке, и повторы, схлопнутые ключом, который новая лента придумала сама. Основанием перехода служат объяснённые расхождения, совпадение итогов таким основанием не считается.
Нужно разобрать перенос данных?
Пришлите список колонок выгрузки и обезличенный пример расхождения. Первая консультация бесплатная.
Не отправляйте токены и пароли.
Источники
Sources
- Постбек: документация Keitaro
- Журнал конверсий: документация Keitaro
- Очистка статистики: документация Keitaro
- Архив: документация Keitaro
- Перенос трекера на другой сервер: документация Keitaro
- Документация Voluum: журнал трафика
- Документация Voluum: регистрация конверсий без click ID
- Telegram-трекинг Way2: доказательства, границы и дозагрузка истории
Услуги Way2
