Legacy Modernization & Data Migrations — DE Interview PrepМиграции устаревших систем и данных — Подготовка к DE-интервью

Fast-revision cheatsheet for Data Engineering interviews. Tailored for Senior/Lead DE. Core theme: modernize legacy processes, reduce technical debt, own data migrations, workflow redesigns, technical transformation. Шпаргалка для быстрого повторения перед Data Engineering интервью. Заточена под Senior/Lead DE. Основная тема: модернизация устаревших процессов, снижение технического долга, управление миграциями данных, переработка рабочих процессов, техническая трансформация.
Anchor story: McMakler — three concurrent platform migrations, EUR 1M+ saved/yr, zero downtime for 200+ business usersЯкорь: McMakler — три параллельные миграции платформ, экономия 1M+ EUR/год, ноль простоев для 200+ бизнес-пользователей Meta — consolidating 10 logging systems into one modelMeta — консолидация 10 систем логирования в одну модель

1. Assess & inventory a legacy system1. Оценка и инвентаризация устаревшей системы

You can't migrate what you haven't mapped

Нельзя мигрировать то, что не отобразил на карте

Before touching anything, build a factual inventory. Most failed migrations fail here: a hidden consumer, an undocumented job, a side-effect nobody owned.

Прежде чем что-то трогать, составь фактическую инвентаризацию. Большинство провалившихся миграций падают здесь: скрытый потребитель, недокументированная задача, побочный эффект, которым никто не владеет.

What to inventory

Что инвентаризировать

  • Inputs / sources: upstream feeds, files, APIs, vendor data, schedules.
  • Transforms: every pipeline/job, its logic, owner, runtime, cost.
  • Outputs / consumers: tables, dashboards, exports, downstream pipelines, humans. Map by querying access logs, not by asking.
  • Contracts: schemas, SLAs, freshness, precision/units, semantics.
  • Hidden state: manual steps, cron on someone's box, Excel macros, "the one person who knows".
  • Входы / источники: входящие фиды, файлы, API, данные вендоров, расписания.
  • Трансформации: каждый пайплайн/джоба, его логика, владелец, время работы, стоимость.
  • Выходы / потребители: таблицы, дашборды, экспорты, нижележащие пайплайны, люди. Составляй карту через логи доступа, не через опросы.
  • Контракты: схемы, SLA, свежесть, точность/единицы, семантика.
  • Скрытое состояние: ручные шаги, cron на чьей-то машине, Excel-макросы, «тот один человек, кто в курсе».

How to discover real usage

Как обнаружить реальное использование

  • Query / access logs: who actually read each table in last 90d? Drop the zero-read tables from scope.
  • Lineage tooling: trace column-level dependencies; find orphan consumers.
  • Network/auth logs: which services call which endpoints.
  • Interviews as confirmation, not source of truth — people forget; logs don't.
  • Логи запросов / доступа: кто реально читал каждую таблицу за последние 90 дней? Убери из скоупа таблицы с нулевыми чтениями.
  • Lineage-инструменты: трассируй зависимости на уровне колонок; ищи осиротевших потребителей.
  • Сетевые/auth логи: какие сервисы вызывают какие эндпоинты.
  • Интервью — подтверждение, а не источник истины — люди забывают; логи нет.
Amal anchor McMakler: Step 0 across all three migrations was a usage census. We found ~40% of "critical" reports had zero reads in 90 days. Cutting them from scope shrank the migration and was the single biggest risk reduction.
Якорь Amal McMakler: Шаг 0 для всех трёх миграций — перепись использования. Мы обнаружили, что ~40% «критических» отчётов имели ноль чтений за 90 дней. Исключение их из скоупа сократило миграцию и стало самым большим снижением риска.
Interviewer will probe "How did you know you'd captured every consumer?" Answer: I trust access logs over tribal knowledge, then run in parallel so any missed consumer surfaces as a diff before cutover (defense in depth, not perfect upfront discovery).
О чём спросит интервьюер «Откуда ты знал, что захватил всех потребителей?» Ответ: я доверяю логам доступа больше, чем племенным знаниям, затем запускаю параллельно, чтобы любой пропущенный потребитель всплыл как diff до переключения (многоуровневая защита, а не идеальное обнаружение на старте).

2. Quantify & prioritize technical debt2. Количественная оценка и приоритизация технического долга

Treat debt like a loan: principal + interest

Относись к долгу как к кредиту: тело + проценты

Not all debt is worth paying. The decision is economic: interest-bearing debt (compounding pain) gets paid first; cheap/dormant debt can wait.

Не весь долг стоит отдавать. Решение экономическое: долг с процентами (растущая боль) отдаётся первым; дешёвый/спящий долг может подождать.

How to quantify "interest"

Как количественно оценить «проценты»

  • $ cost: licensing, compute, storage burned by the legacy path.
  • Toil hours: manual reruns, on-call pages, firefighting per month.
  • Velocity tax: how much does every new feature cost extra because of this debt?
  • Risk: incident frequency, data-quality defects, single points of failure.
  • $ стоимость: лицензии, вычисления, хранилище, сжигаемое устаревшим путём.
  • Часы рутинной работы (toil): ручные перезапуски, дежурные вызовы, пожаротушение в месяц.
  • Налог на скорость: насколько дороже обходится каждая новая фича из-за этого долга?
  • Риск: частота инцидентов, дефекты качества данных, единые точки отказа.

Prioritize: impact vs effort

Приоритизация: влияние vs усилия

 Low effortHigh effort
High impactDo now (quick wins)Plan / project
Low impactBacklog (batch)Don't (won't fix)
 Малые усилияБольшие усилия
Большое влияниеДелать сейчас (быстрые победы)Планировать / проект
Малое влияниеБэклог (батч)Не делать (won't fix)

Pay down debt that bears interest and blocks the roadmap. Ignore debt that's stable, isolated, and cheap.

Отдавай долг, который несёт проценты и блокирует роадмап. Игнорируй долг, который стабилен, изолирован и дёшев.

Amal anchor (honest) The McMakler triple migration was a debt-paydown thesis: proprietary orchestration was interest-bearing (100s of K EUR/yr licensing + vendor lock-in + slow feature delivery). That's exactly the debt you pay first. The Postgres analytical layer was high-impact too (no tests, no lineage, slow). We bundled them because they shared the same cutover surface.
Якорь Amal (честно) Тройная миграция McMakler была тезисом о выплате долга: проприетарная оркестрация была долгом с процентами (сотни тысяч EUR/год лицензии + vendor lock-in + медленная доставка фичей). Это именно тот долг, который отдаёшь первым. Postgres-слой аналитики тоже был высокоимпактным (нет тестов, нет lineage, медленно). Мы объединили их, потому что они делили одну поверхность переключения.
Gotcha Don't "rewrite everything" because it's ugly. Ugliness isn't debt. Debt is what costs you measurably. Refactoring stable, isolated, cheap code is negative ROI — it adds risk for no return.
Подвох Не «переписывай всё», потому что уродливо. Уродливость — не долг. Долг — это то, что измеримо стоит денег. Рефакторинг стабильного, изолированного, дёшевого кода — отрицательная окупаемость инвестиций (ROI) — он добавляет риск без отдачи.

3. Migration strategies & tradeoffs3. Стратегии миграции и компромиссы

The strategy menu

Меню стратегий

PatternWhat it isBest forRisk
Strangler FigRoute traffic through a facade; replace pieces one at a time until the old system is "strangled" and removed.Large systems you can decompose; can't stop the business.Low — incremental, reversible per slice.
Parallel-run / Dual-writeRun old + new together; write to both, compare outputs continuously.Correctness-critical data (finance, econ). Validation engine.Low risk, higher cost (2x compute for a window).
Branch by abstractionInsert an abstraction layer in-place, build the new impl behind it, flip a flag.Replacing a component inside a codebase without a long-lived fork.Low — keeps trunk green throughout.
Big-bangBuild new fully, cut over everyone at once.Small/simple systems, hard deadlines, schemas that can't coexist.High — no incremental validation; rollback is all-or-nothing.
IncrementalMigrate by slice (table, domain, consumer group, time-range).Almost everything large. Default posture.Low per step; longer total timeline.
Lift & shiftMove as-is to new infra, change as little as possible.Speed, deadline (data-center exit), de-risk infra first.Carries debt over; cheap and fast.
Re-architectRedesign data model / processing while moving.When the old design is the actual problem.Highest — two big variables (infra + design) at once.
ПаттернЧто этоЛучше всего дляРиск
Strangler FigНаправляй трафик через фасад; заменяй по кусочкам, пока старая система не «задушена» и удалена.Большие системы, которые можно декомпозировать; нельзя остановить бизнес.Низкий — инкрементально, обратимо по срезу.
Parallel-run / Dual-writeЗапускай старую + новую вместе; пиши в обе, непрерывно сравнивай выходы.Данные критичные по корректности (финансы, экономика). Движок валидации.Низкий риск, выше стоимость (2x compute на окно).
Branch by abstractionВставь слой абстракции на месте, построй новую реализацию за ним, переключи флаг.Замена компонента внутри кодовой базы без долгоживущей ветки.Низкий — держит trunk зелёным всё время.
Big-bangПострой новую полностью, переключи всех сразу.Маленькие/простые системы, жёсткие дедлайны, схемы, которые не могут сосуществовать.Высокий — нет инкрементальной валидации; откат всё-или-ничего.
IncrementalМигрируй по срезу (таблица, домен, группа потребителей, временной диапазон).Почти всё большое. Позиция по умолчанию.Низкий на шаг; дольше общий таймлайн.
Lift & shiftПеренеси как есть на новую инфру, меняй как можно меньше.Скорость, дедлайн (выход из дата-центра), снизь риск инфры сначала.Переносит долг; дёшево и быстро.
Re-architectПеределай модель данных / обработку в процессе переноса.Когда старый дизайн — настоящая проблема.Самый высокий — две большие переменные (инфра + дизайн) сразу.
Gotcha Don't combine re-architect + big-bang. Changing both the platform and the data model in a single all-at-once cutover is how migrations make headlines. If you must re-architect, do it incrementally with parallel-run validation.
Подвох Не комбинируй re-architect + big-bang. Менять и платформу, и модель данных в одном переключении всех сразу — так миграции попадают в заголовки. Если нужна переделка архитектуры, делай её инкрементально с валидацией parallel-run.

Lift-and-shift first, re-architect later (the pragmatic combo)

Сначала lift-and-shift, потом re-architect (прагматичная комбо)

A strong general answer: de-risk infra with a near-lift-and-shift, prove parity, cut over, then re-architect on the new stable platform where each improvement is independently testable. One variable at a time.

Сильный общий ответ: снизь риск инфры почти-lift-and-shift, докажи паритет, переключись, затем переделывай архитектуру на новой стабильной платформе, где каждое улучшение независимо тестируемо. Одна переменная за раз.

Interviewer will probe "When would you choose big-bang?" Good answer: tiny scope, schemas that genuinely can't coexist, a hard external deadline, or where running two systems costs more than the blast radius of a clean cut. Then I de-risk with a full dress-rehearsal cutover in staging and a tested rollback.
О чём спросит интервьюер «Когда ты выберешь big-bang?» Хороший ответ: крошечный скоуп, схемы, которые реально не могут сосуществовать, жёсткий внешний дедлайн, или когда работа двух систем стоит дороже, чем радиус поражения чистого разреза. Тогда я снижаю риск полной генеральной репетицией переключения в staging и протестированным откатом.

4. Strangler fig + parallel-run (diagram)4. Strangler fig + parallel-run (диаграмма)

How the facade, dual-write and diff fit together

Как фасад, dual-write и diff работают вместе

┌──────────────────────────────────────────────────────────┐ │ CONSUMERS (200+ users, dashboards, │ │ downstream pipelines) │ └───────────────────────────┬──────────────────────────────┘ │ reads ┌──────────▼───────────┐ │ FACADE / ROUTER │ strangler fig: │ (view, flag, proxy) │ flips per-slice └─────┬───────────┬─────┘ read old (default)│ │ read new (per migrated slice) │ │ ┌────────────────▼──┐ ┌────▼───────────────────┐ │ LEGACY system │ │ NEW system │ │ (old WH / │ │ (new WH / │ │ old orch / │ │ new orch / │ │ old analytics) │ │ new analytics) │ └────────────┬───────┘ └───────────┬────────────┘ WRITES go to BOTH (dual-write) during parallel-run │ │ └───────────┬───────────┘ │ ┌─────────▼──────────┐ │ RECONCILER/DIFF │ row counts, hashes, │ old ⟷ new │ null/precision checks, └─────────┬──────────┘ business KPIs │ diffs ≈ 0 for N consecutive days ──► CUTOVER slice diffs spike / SLA breach ──► ROLLBACK slice (flip flag)
┌──────────────────────────────────────────────────────────┐ │ ПОТРЕБИТЕЛИ (200+ пользователей, дашборды, │ │ нижележащие пайплайны) │ └───────────────────────────┬──────────────────────────────┘ │ чтения ┌──────────▼───────────┐ │ ФАСАД / РОУТЕР │ strangler fig: │ (view, флаг, proxy) │ переключает по срезу └─────┬───────────┬─────┘ читать старое │ │ читать новое (по мигрированному срезу) (по умолчанию) │ │ ┌────────────────▼──┐ ┌────▼───────────────────┐ │ LEGACY система │ │ НОВАЯ система │ │ (старое WH / │ │ (новое WH / │ │ старая орк. / │ │ новая орк. / │ │ старая аналит.) │ │ новая аналитика) │ └────────────┬───────┘ └───────────┬────────────┘ ЗАПИСИ идут В ОБЕ (dual-write) во время parallel-run │ │ └───────────┬───────────┘ │ ┌─────────▼──────────┐ │ RECONCILER/DIFF │ счётчики строк, хеши, │ старое ⟷ новое │ проверки null/точности, └─────────┬──────────┘ бизнес KPI │ diff ≈ 0 N дней подряд ──► ПЕРЕКЛЮЧИТЬ срез diff выросли / нарушен SLA ──► ОТКАТ среза (флаг обратно)

Each slice (a table, a domain, a consumer group) walks the loop independently: dual-write → diff → green for N days → flip facade → decommission that legacy piece. The facade is the single control point; flipping a flag is the entire cutover and the entire rollback.

Каждый срез (таблица, домен, группа потребителей) проходит цикл независимо: dual-write → diff → зелено N дней → переключить фасад → списать этот legacy-кусок. Фасад — единая точка управления; переключение флага — это всё переключение и весь откат.

Why it wins Zero downtime falls out for free: consumers always read through the facade, which always points at a live system. Rollback is one flag, not a restore-from-backup fire drill.
Почему это выигрывает Ноль простоев получается бесплатно: потребители всегда читают через фасад, который всегда указывает на живую систему. Откат — один флаг, а не пожарное восстановление из бэкапа.

5. Data migration mechanics5. Механики миграции данных

Schema mapping & type/precision pitfallsМаппинг схем и ловушки типов/точности

Schema mapping

Маппинг схемы

  • Build an explicit source→target column map: name, type, nullability, units, semantics. Versioned, reviewed.
  • Handle renames, splits, merges, derived columns deliberately — not silently.
  • Decide policy for columns that exist in only one side (drop / default / compute).
  • Построй явный маппинг колонок источник→цель: имя, тип, nullability, единицы, семантика. Версионированный, проревьюенный.
  • Обрабатывай переименования, разбиения, слияния, вычисляемые колонки осознанно — не молча.
  • Определи политику для колонок, которые есть только на одной стороне (drop / default / compute).

Type & precision traps (econ data!)

Ловушки типов и точности (данные по экономике!)

  • Float vs DECIMAL/NUMERIC: never store money/rates as float. Rounding diffs will fail your diff and corrupt KPIs.
  • Timestamps & timezones: TZ-naive vs UTC, DST, epoch vs ISO. A favourite silent corruptor.
  • Integer width / overflow, string truncation (varchar length), charset/collation.
  • NULL vs empty-string vs 0, sentinel values, default semantics.
  • Rounding/precision differences between engines (Postgres vs Snowflake) — pin precision explicitly.
  • Float vs DECIMAL/NUMERIC: никогда не храни деньги/ставки как float. Различия округления провалят твой diff и повредят KPI.
  • Timestamps & таймзоны: TZ-naive vs UTC, DST, epoch vs ISO. Любимый тихий повреждатель.
  • Ширина integer / переполнение, обрезка строк (длина varchar), charset/collation.
  • NULL vs пустая строка vs 0, сентинельные значения, семантика дефолтов.
  • Различия округления/точности между движками (Postgres vs Snowflake) — закрепи точность явно.
Gotcha The diff that "won't go to zero". Usually it's not a logic bug — it's float rounding, timezone shift, or NULL-vs-empty. Build the reconciler to bucket diffs by type so you can tell a real defect from a representation difference, and define a tolerance band for floats.
Подвох Diff, который «не идёт к нулю». Обычно это не логическая ошибка — это округление float, сдвиг таймзоны или NULL-vs-пустая. Построй reconciler так, чтобы он группировал diff по типу, чтобы ты мог отличить реальный дефект от различия представления, и определи полосу допуска для floats.
Idempotent backfillsИдемпотентные backfill'ы
  • Idempotent = re-runnable. Running a backfill partition twice must yield the same result, never duplicate. Use MERGE/upsert keyed on a natural/business key, or partition-level INSERT OVERWRITE — never blind INSERT.
  • Partition / chunk by date or key range; checkpoint progress so a failure resumes, not restarts.
  • Throttle to protect source & target; backfill during low-traffic windows.
  • Watermark the boundary between historical backfill and live CDC so they meet exactly once.
  • Идемпотентный = перезапускаемый. Запуск backfill партиции дважды должен давать тот же результат, никогда не дублировать. Используй MERGE/upsert на натуральном/бизнес-ключе или INSERT OVERWRITE на уровне партиции — никогда слепой INSERT.
  • Партиционируй / чанкуй по дате или диапазону ключей; чекпойнти прогресс, чтобы сбой возобновлял, а не рестартовал.
  • Троттли, чтобы защитить источник и цель; делай backfill в окна малого трафика.
  • Водяной знак (watermark) границы между историческим backfill и живым CDC, чтобы они встретились ровно раз.
-- idempotent partition backfill (re-run safe)
MERGE INTO target_econ_series AS tgt
USING staged_backfill_partition AS src
  ON tgt.series_id = src.series_id
 AND tgt.observation_date = src.observation_date
WHEN MATCHED THEN UPDATE SET value = src.value, load_ts = src.load_ts
WHEN NOT MATCHED THEN INSERT (series_id, observation_date, value, load_ts)
VALUES (src.series_id, src.observation_date, src.value, src.load_ts);
-- идемпотентный backfill партиции (безопасно перезапускать)
MERGE INTO target_econ_series AS tgt
USING staged_backfill_partition AS src
  ON tgt.series_id = src.series_id
 AND tgt.observation_date = src.observation_date
WHEN MATCHED THEN UPDATE SET value = src.value, load_ts = src.load_ts
WHEN NOT MATCHED THEN INSERT (series_id, observation_date, value, load_ts)
VALUES (src.series_id, src.observation_date, src.observation_date, src.value, src.load_ts);
CDC (Change Data Capture) & the backfill/live handoffCDC (Change Data Capture) и передача backfill→live

For low-downtime data moves you combine a bulk historical backfill with ongoing CDC so the new system stays in sync while you validate.

Для переносов данных с малыми простоями ты комбинируешь массовый исторический backfill с непрерывным CDC, чтобы новая система оставалась в синхроне, пока ты валидируешь.

  • CDC sources: log/WAL-based (best — captures deletes, low load), trigger-based, or query/timestamp-based (misses deletes).
  • Pattern: snapshot at time T → backfill everything ≤ T → start CDC stream from T → new system converges to live → flip reads.
  • Order matters: handle out-of-order events and the snapshot/stream overlap; dedupe on primary key + version.
  • CDC keeps the parallel-run window live, not stale, so the diff stays meaningful right up to cutover.
  • Источники CDC: на базе лога/WAL (лучше всего — захватывает удаления, малая нагрузка), на базе триггеров или на базе запросов/timestamp (пропускает удаления).
  • Паттерн: снимок в момент T → backfill всего ≤ T → стартуй поток CDC с T → новая система конвергирует к live → переключай чтения.
  • Порядок важен: обрабатывай события вне порядка и пересечение снимка/потока; дедуплицируй на первичном ключе + версии.
  • CDC держит окно parallel-run живым, а не устаревшим, поэтому diff остаётся значимым вплоть до переключения.

6. Reconciliation, validation & cutover criteria6. Сверка, валидация и критерии переключения

Parallel-run is your validation engine

Parallel-run — твой движок валидации

Old and new run side by side; you diff their outputs continuously. The diff is the proof you cut over on — not vibes, not "looks right".

Старое и новое работают бок о бок; ты непрерывно сравниваешь их выходы. Diff — это доказательство, на котором ты переключаешься — не «ощущения», не «выглядит правильно».

Layered reconciliation checks (cheap → strict)

Многослойные проверки сверки (дёшево → строго)

  1. Row counts per partition — catches gross loss/dupes fast.
  2. Aggregate checksums — SUM/MIN/MAX/COUNT per column & per partition; cheap, catches most drift.
  3. Row-level hash diff — hash each row both sides, set-diff; pinpoints exact rows.
  4. Business KPI parity — the metrics consumers actually look at must match within tolerance.
  5. Distribution checks — nulls %, cardinality, min/max/percentiles per column.
  1. Счётчики строк на партицию — быстро ловит грубые потери/дубли.
  2. Агрегированные чексуммы — SUM/MIN/MAX/COUNT на колонку и партицию; дёшево, ловит большинство сдвигов.
  3. Хеш-diff на уровне строк — хешируй каждую строку с обеих сторон, set-diff; точно указывает строки.
  4. Паритет бизнес-KPI — метрики, на которые реально смотрят потребители, должны совпадать в пределах допуска.
  5. Проверки распределений — % nulls, cardinality, min/max/процентили на колонку.
Tolerance Define tolerance up front. Exact match for keys/counts; small epsilon for floats; documented, sign-off'd exceptions for known representation diffs. A diff that's "explained and bounded" is green.
Допуск Определи допуск заранее. Точное совпадение для ключей/счётчиков; малый эпсилон для floats; задокументированные, согласованные исключения для известных различий представления. Diff, который «объяснён и ограничен» — зелёный.

Cutover criteria & rollback plan

Критерии переключения и план отката

Go criteria (write them down)

Критерии Go (запиши их)

  • Diffs within tolerance for N consecutive days/runs.
  • New system meets SLA/freshness/latency at full load.
  • Consumers have validated their own reports on new.
  • Rollback tested, runbook written, owner + comms ready.
  • Diff в пределах допуска N дней/запусков подряд.
  • Новая система выполняет SLA/свежесть/задержку на полной нагрузке.
  • Потребители провалидировали свои отчёты на новой.
  • Откат протестирован, runbook написан, владелец + коммуникации готовы.

Rollback plan

План отката

  • Keep legacy warm through a soak period after cutover — don't decommission day one.
  • One-flag rollback via the facade; tested before cutover, not improvised.
  • Define rollback triggers (diff spike, SLA breach, consumer escalation) and who calls it.
  • Keep dual-write running during soak so rolling back loses no data.
  • Держи legacy тёплым в течение периода отмачивания (soak) после переключения — не списывай в первый день.
  • Откат одним флагом через фасад; протестирован до переключения, а не импровизирован.
  • Определи триггеры отката (скачок diff, нарушение SLA, эскалация потребителя) и кто его вызывает.
  • Держи dual-write работающим во время soak, чтобы откат не терял данные.
Interviewer will probe "What's your rollback story if you cut over and it's wrong at 2am?" Answer: facade flag flips reads back to legacy in seconds; dual-write means legacy never went stale; soak period means it's still warm. Rollback is a flag, not a restore.
О чём спросит интервьюер «Какой у тебя сценарий отката, если переключил, а в 2 часа ночи не так?» Ответ: флаг фасада переключает чтения обратно на legacy за секунды; dual-write означает, что legacy никогда не устарел; период soak означает, что он ещё тёплый. Откат — это флаг, а не восстановление.

7. Zero-downtime cutover techniques7. Техники переключения без простоя

How "no downtime for 200+ users" actually works

Как «ноль простоя для 200+ пользователей» реально работает

  • Facade / indirection: consumers read a view, alias, or service that you repoint. They never know which system answers.
  • Dual-write window: both systems stay current, so the switch carries no data gap.
  • Feature flags / per-cohort routing: flip 1% → 10% → 100%, or one consumer group at a time. Canary the cutover.
  • Blue/green: full new env stood up in parallel; switch routing; old env kept as instant fallback.
  • Expand/contract (parallel change) for schema: add new columns/tables, dual-write, migrate readers, then remove old — never break in place.
  • Backward-compatible views: present the old schema shape on top of the new model so consumers don't change on day one.
  • Фасад / indirection: потребители читают view, alias или сервис, который ты перенаправляешь. Они никогда не знают, какая система отвечает.
  • Окно dual-write: обе системы остаются актуальными, поэтому переключение не несёт разрыва данных.
  • Feature flags / маршрутизация по когортам: переключай 1% → 10% → 100%, или одну группу потребителей за раз. Canary-переключение.
  • Blue/green: полное новое окружение поднято параллельно; переключи маршрутизацию; старое окружение остаётся как мгновенный fallback.
  • Expand/contract (параллельное изменение) для схемы: добавь новые колонки/таблицы, dual-write, мигрируй читателей, затем удали старое — никогда не ломай на месте.
  • Обратно-совместимые view: представляй старую форму схемы поверх новой модели, чтобы потребители не менялись в первый день.
Amal anchor McMakler zero-downtime mechanic: business users read curated views. We rebuilt the analytical layer in dbt behind identically-named views, dual-ran, diffed, and repointed the view definitions. From the user's seat, nothing changed except things got faster. The orchestration swap (proprietary → Airflow) ran the same DAG logic in parallel and we cut over per-DAG once outputs matched.
Якорь Amal Механика нулевого простоя McMakler: бизнес-пользователи читали кураторские views. Мы перестроили аналитический слой в dbt за идентично названными views, запускали dual, сравнивали diff и перенаправляли определения view. С места пользователя ничего не изменилось, кроме того что стало быстрее. Замена оркестрации (проприетарная → Airflow) запускала ту же логику DAG параллельно, и мы переключались по DAG, как только выходы совпадали.

8. Managing risk & stakeholders8. Управление риском и стейкхолдерами

The migration is 50% technical, 50% trust

Миграция — 50% техника, 50% доверие

Risk management

Управление риском

  • Shrink scope first (drop unused, defer nice-to-haves). Smaller migration = smaller risk.
  • Reversible slices: never take a step you can't undo with a flag.
  • Defense in depth: parallel-run + diffs + soak + rollback. No single check is trusted alone.
  • Dress rehearsal: practice the cutover in staging end-to-end, including rollback.
  • Decommission deliberately: only after soak; archive legacy data before deleting.
  • Сократи скоуп сначала (убери неиспользуемое, отложи nice-to-have). Меньше миграция = меньше риск.
  • Обратимые срезы: никогда не делай шаг, который не можешь отменить флагом.
  • Эшелонированная защита (defense in depth): parallel-run + diff + soak + rollback. Ни одной проверке не доверяют в одиночку.
  • Генеральная репетиция: практикуй переключение в staging end-to-end, включая откат.
  • Списывай осознанно: только после soak; архивируй legacy-данные перед удалением.

Stakeholders

Стейкхолдеры

  • Make consumers co-owners: they validate their reports on the new system and sign off. Their green light is a go-criterion.
  • Over-communicate: timeline, what changes, what doesn't, the rollback promise. Surprise is the enemy of trust.
  • "Nothing changes for you" is the best message — backward-compatible interfaces let you deliver it.
  • Show the diff dashboard: transparency turns skeptics into believers.
  • Tie to value they care about: cost saved, faster refresh, fewer incidents — not "new tech".
  • Сделай потребителей совладельцами: они валидируют свои отчёты на новой системе и подписываются. Их зелёный свет — критерий go.
  • Переобщайся: таймлайн, что меняется, что нет, обещание отката. Сюрприз — враг доверия.
  • «Для вас ничего не меняется» — лучшее сообщение — обратно-совместимые интерфейсы позволяют его доставить.
  • Покажи дашборд diff: прозрачность превращает скептиков в верующих.
  • Привяжи к ценности, которая им важна: сэкономленные деньги, более быстрое обновление, меньше инцидентов — не «новая технология».
Interviewer will probe "How did you get 200+ business users to trust a swapped-out backend?" Answer: they never had to trust it blindly — identical interfaces, a public diff dashboard proving parity, their own sign-off on their reports, and a one-flag rollback I could demo. Trust came from evidence, not promises.
О чём спросит интервьюер «Как ты заставил 200+ бизнес-пользователей доверять подменённому бэкенду?» Ответ: им никогда не нужно было доверять слепо — идентичные интерфейсы, публичный дашборд diff, доказывающий паритет, их собственная подпись под их отчётами, и откат одним флагом, который я мог продемонстрировать. Доверие пришло из доказательств, а не обещаний.

9. Decomposing a monolith pipeline9. Декомпозиция монолитного пайплайна

Find the seams, cut along them

Найди швы, режь по ним

  • Map the DAG: what depends on what. Identify natural boundaries (by data domain, by source, by consumer).
  • Cut at stable interfaces: a materialized table/contract between stages is a clean seam; cut there, not through tangled in-memory logic.
  • Extract leaf-first: peel off the least-coupled stage first to learn the pattern cheaply.
  • Strangler per stage: replace one stage behind the facade table; downstream sees the same contract.
  • Make stages idempotent & independently runnable — that's what lets you migrate/test them in isolation.
  • Branch by abstraction inside the code where there's no natural table boundary to flip.
  • Отобрази DAG: что от чего зависит. Определи естественные границы (по домену данных, по источнику, по потребителю).
  • Режь по стабильным интерфейсам: материализованная таблица/контракт между этапами — чистый шов; режь там, а не сквозь запутанную логику в памяти.
  • Извлекай лист первым: отдели наименее связанный этап первым, чтобы дёшево изучить паттерн.
  • Strangler на этап: замени один этап за фасад-таблицей; нижележащее видит тот же контракт.
  • Сделай этапы идемпотентными и независимо запускаемыми — это то, что позволяет мигрировать/тестировать их изолированно.
  • Branch by abstraction внутри кода, где нет естественной границы таблицы для переключения.
Amal anchor Meta — 10 logging systems → one model: classic decomposition. Ten overlapping loggers with divergent schemas. We defined a unified canonical event model (the contract), mapped each legacy logger onto it, dual-logged during transition, diffed counts/fields per source, migrated consumers to the unified model, then decommissioned legacy loggers (incl. tombstoning configs in phases). Same strangler + parallel-run playbook, applied to instrumentation instead of a warehouse.
Якорь Amal Meta — 10 систем логирования → одна модель: классическая декомпозиция. Десять пересекающихся логгеров с расходящимися схемами. Мы определили единую каноничную модель событий (контракт), отобразили каждый legacy-логгер на неё, dual-логгировали во время перехода, сравнивали счётчики/поля на источник, мигрировали потребителей на единую модель, затем списали legacy-логгеры (вкл. tombstoning конфигов по фазам). Тот же playbook strangler + parallel-run, применённый к инструментарию вместо хранилища.

10. Common failure modes10. Распространённые режимы сбоев

  • Missed consumer — undiscovered downstream breaks at cutover. Fix: access-log census + parallel-run surfaces it as a diff.
  • Big-bang + re-architect together — two unknowns at once, no rollback granularity.
  • Silent data corruption — float/TZ/NULL/precision drift slips past weak checks. Fix: typed, bucketed diffs + tolerances.
  • Non-idempotent backfill — re-run creates dupes or gaps. Fix: MERGE/overwrite + watermarks.
  • Scope creep — "while we're here, let's also…". Re-architecting mid-migration. Freeze scope.
  • Decommissioning too early — kill legacy before soak, then can't roll back.
  • No rollback rehearsal — rollback path untested until you need it at 2am.
  • Stakeholder surprise — users find out at cutover. Erodes all future trust.
  • Endless parallel-run — never defining "good enough to cut", paying 2x forever.
  • Пропущенный потребитель — необнаруженное нижележащее ломается при переключении. Лечение: перепись по логам доступа + parallel-run всплывает как diff.
  • Big-bang + re-architect вместе — две неизвестные сразу, нет гранулярности отката.
  • Тихое повреждение данных — сдвиг float/TZ/NULL/точности проскальзывает мимо слабых проверок. Лечение: типизированные, сгруппированные diff + допуски.
  • Неидемпотентный backfill — перезапуск создаёт дубли или разрывы. Лечение: MERGE/overwrite + watermarks.
  • Разрастание скоупа — «раз уж мы здесь, давайте ещё…». Переделка архитектуры в середине миграции. Заморозь скоуп.
  • Списание слишком рано — убил legacy до soak, потом не откатишь.
  • Нет репетиции отката — путь отката не протестирован, пока не понадобился в 2 часа ночи.
  • Сюрприз стейкхолдера — пользователи узнают при переключении. Размывает всё будущее доверие.
  • Бесконечный parallel-run — никогда не определяешь «достаточно хорошо для переключения», платишь 2x навсегда.
Gotcha "It works in staging." Staging rarely has production data volume, skew, or weird historical rows. The first real corruption you find is usually a 2014 row with a NULL where the schema "never has NULLs". Test against a production-scale sample.
Подвох «Работает в staging». Staging редко имеет объём данных продакшена, перекосы или странные исторические строки. Первое реальное повреждение, которое ты найдёшь, обычно — строка 2014 года с NULL, где схема «никогда не имеет NULL». Тестируй против продакшн-масштабного сэмпла.

11. Measuring success11. Измерение успеха

Define what "done well" means before you start

Определи, что означает «сделано хорошо», прежде чем начать

Correctness

Корректность

  • Output parity within tolerance
  • Zero data-loss / dupes post-cutover
  • DQ defect rate flat or down
  • Паритет выходов в пределах допуска
  • Ноль потерь данных / дублей после переключения
  • Частота дефектов DQ на том же уровне или ниже

Reliability / UX

Надёжность / UX

  • Zero (or bounded) downtime
  • SLA / freshness met or better
  • Fewer incidents / pages
  • Ноль (или ограниченный) простой
  • SLA / свежесть выполнены или лучше
  • Меньше инцидентов / вызовов

Business value

Бизнес-ценность

  • $ saved (licensing/compute)
  • Faster refresh / lower latency
  • Faster feature delivery (debt paid)
  • Legacy fully decommissioned
  • $ сэкономлено (лицензии/вычисления)
  • Быстрее обновление / ниже задержка
  • Быстрее доставка фичей (долг отдан)
  • Legacy полностью списан
McMakler KPIs EUR 1M+ annual savings | 0 downtime for 200+ users | 3 concurrent migrations delivered | 10 → 1 logging systems (Meta)
KPI McMakler EUR 1M+ годовой экономии | 0 простоя для 200+ пользователей | 3 параллельные миграции доставлены | 10 → 1 систем логирования (Meta)
Interviewer will probe "How do you know the migration succeeded vs just finished?" Answer: success = parity proven by diffs + value realized (cost down, speed up) + legacy actually decommissioned. A migration that leaves legacy running "just in case" forever didn't succeed; it added a system.
О чём спросит интервьюер «Откуда ты знаешь, что миграция успешна, а не просто закончилась?» Ответ: успех = паритет доказан diff + ценность реализована (стоимость снижена, скорость выросла) + legacy фактически списан. Миграция, которая оставляет legacy работающим «на всякий случай» навсегда, не успешна; она добавила систему.

12. Worked examples (your stories, STAR-ready)12. Разобранные примеры (твои истории, готовые к STAR)

McMakler — three concurrent platform migrations

McMakler — три параллельные миграции платформ

Situation: Analytics ran on a warehouse, a proprietary orchestrator (100s of K EUR/yr licensing + lock-in), and a Postgres analytical layer with no tests/lineage. 200+ business users depended on it daily. Interest-bearing debt blocking the roadmap.

Ситуация: Аналитика работала на хранилище, проприетарном оркестраторе (сотни тысяч EUR/год лицензии + vendor lock-in) и Postgres-слое аналитики без тестов/lineage. 200+ бизнес-пользователей зависели от этого ежедневно. Долг с процентами, блокирующий роадмап.

Task: Modernize all three, cut cost, no downtime — concurrently, because they shared a cutover surface.

Задача: Модернизировать все три, снизить стоимость, без простоев — параллельно, потому что они делили поверхность переключения.

Action:

Действие:

  • Assess: usage census via access logs; dropped unused reports from scope.
  • Strangler + parallel-run: stood up Snowflake + Airflow + dbt alongside legacy; dual-ran the same logic.
  • Zero-downtime: rebuilt curated layer in dbt behind identically-named views; orchestration cut over per-DAG once outputs matched; reconciler diffed counts, hashes, and KPIs continuously.
  • Cutover: per-slice, on N-days-green criteria, legacy kept warm through soak with one-flag rollback.
  • Оценка: перепись использования через логи доступа; убрал неиспользуемые отчёты из скоупа.
  • Strangler + parallel-run: поднял Snowflake + Airflow + dbt рядом с legacy; dual-запускал ту же логику.
  • Ноль простоев: перестроил кураторский слой в dbt за идентично названными views; оркестрация переключалась по DAG, как только выходы совпадали; reconciler непрерывно сравнивал счётчики, хеши и KPI.
  • Переключение: по срезу, по критерию N-дней-зелено, legacy держал тёплым через soak с откатом одним флагом.

Result: EUR 1M+/yr saved (eliminated proprietary orchestration licensing + cheaper compute), zero downtime for 200+ users, faster refreshes, tested+lineaged analytical layer.

Результат: EUR 1M+/год сэкономлено (убрал лицензии проприетарной оркестрации + более дешёвые вычисления), ноль простоев для 200+ пользователей, более быстрые обновления, протестированный+lineaged аналитический слой.

Soundbite (rehearse verbatim) "I never asked users to trust the new stack. I gave them identical interfaces, a diff dashboard proving parity, and a rollback I could flip in seconds. The risk lived in flags, not in a big-bang night."
Саундбайт (репетируй дословно) «Я никогда не просил пользователей доверять новому стеку. Я дал им идентичные интерфейсы, дашборд diff, доказывающий паритет, и откат, который мог переключить за секунды. Риск жил во флагах, а не в ночи big-bang.»

Meta — consolidating 10 logging systems into one model

Meta — консолидация 10 систем логирования в одну модель

Situation: Ten overlapping logging systems with divergent schemas; duplicated data, unclear ownership, high maintenance & instrumentation cost.

Ситуация: Десять пересекающихся систем логирования с расходящимися схемами; дублированные данные, неясное владение, высокая стоимость поддержки и инструментирования.

Task: Consolidate into one canonical event model and retire the redundant loggers.

Задача: Консолидировать в одну каноничную модель событий и списать избыточные логгеры.

Action: Defined the unified model (the contract); mapped each legacy logger onto it; dual-logged during transition; diffed counts/fields per source; migrated consumers; decommissioned legacy loggers in phases (tombstone configs first, then delete).

Действие: Определил единую модель (контракт); отобразил каждый legacy-логгер на неё; dual-логгировал во время перехода; сравнивал счётчики/поля на источник; мигрировал потребителей; списал legacy-логгеры по фазам (сначала tombstone конфигов, затем удаление).

Result: 10 → 1 model, reduced redundancy & maintenance, clearer ownership and lineage. Same strangler/parallel-run playbook applied to instrumentation.

Результат: 10 → 1 модель, снижена избыточность и поддержка, более ясное владение и lineage. Тот же playbook strangler/parallel-run, применённый к инструментарию.

Bridge to econ data Economics data feeds are correctness-critical and consumer-heavy — exactly where parallel-run + typed diffs + backward-compatible interfaces shine. The McMakler/Meta playbook ports directly to modernizing legacy econ-data processes.
Мост к econ-данным Фиды экономических данных критичны по корректности и тяжелы по потребителям — именно там, где parallel-run + типизированные diff + обратно-совместимые интерфейсы блистают. Playbook McMakler/Meta портируется напрямую на модернизацию устаревших процессов econ-данных.

13. Self-quiz (tap to flip)13. Самопроверка (тапни, чтобы перевернуть)

What is the strangler fig pattern and why is it low-risk?
Что такое паттерн strangler fig и почему он низкорисковый?
Tap to reveal
Тапни, чтобы показать
Route consumers through a facade and replace the legacy system one slice at a time until it's fully "strangled" and removed. Low-risk because each slice is small, validated in parallel, and reversible with a flag — never a single all-or-nothing leap.
Направляй потребителей через фасад и заменяй legacy-систему по срезу за раз, пока она полностью не «задушена» и удалена. Низкий риск, потому что каждый срез мал, валидирован параллельно и обратим флагом — никогда одного скачка всё-или-ничего.
How do you achieve a zero-downtime data cutover?
Как достичь переключения данных без простоя?
Tap to reveal
Тапни, чтобы показать
Facade/indirection (consumers read a view they never see repointed) + dual-write (both systems stay current, no data gap) + per-cohort flag flip / blue-green + backward-compatible schema via expand/contract. Cutover = repoint the facade; rollback = flip it back.
Фасад/indirection (потребители читают view, которую не видят перенаправленной) + dual-write (обе системы остаются актуальными, нет разрыва данных) + переключение флага по когорте / blue-green + обратно-совместимая схема через expand/contract. Переключение = перенаправить фасад; откат = переключить обратно.
What makes a backfill idempotent, and why does it matter?
Что делает backfill идемпотентным, и почему это важно?
Tap to reveal
Тапни, чтобы показать
Re-running it produces the same result, no dupes. Achieved with MERGE/upsert on a business key or partition INSERT OVERWRITE (never blind INSERT), plus checkpoints and a watermark joining backfill to live CDC. Matters because backfills fail and must resume/retry safely.
Перезапуск даёт тот же результат, нет дублей. Достигается MERGE/upsert на бизнес-ключе или partition INSERT OVERWRITE (никогда слепой INSERT), плюс чекпойнты и watermark, соединяющий backfill с живым CDC. Важно, потому что backfill'ы падают и должны безопасно возобновляться/ретраиться.
Your old-vs-new diff won't go to zero. First suspects?
Твой diff старое-vs-новое не идёт к нулю. Первые подозреваемые?
Tap to reveal
Тапни, чтобы показать
Representation differences, not logic: float rounding (use DECIMAL + tolerance), timezone/DST shifts, NULL-vs-empty-string-vs-0, varchar truncation, engine precision diffs. Bucket diffs by type to separate real defects from explainable ones; define a tolerance band.
Различия представления, а не логики: округление float (используй DECIMAL + допуск), сдвиги таймзоны/DST, NULL-vs-пустая-строка-vs-0, обрезка varchar, различия точности движков. Группируй diff по типу, чтобы отделить реальные дефекты от объяснимых; определи полосу допуска.
How do you prioritize which tech debt to pay down?
Как приоритизировать, какой тех. долг отдавать?
Tap to reveal
Тапни, чтобы показать
Treat it as a loan: pay interest-bearing debt first (compounding $ cost, toil, velocity tax, risk). Use an impact-vs-effort grid — high-impact/low-effort = quick wins, high-impact/high-effort = projects, low-impact = backlog or won't-fix. Ugly ≠ debt; stable+isolated+cheap code stays.
Относись к нему как к кредиту: отдавай долг с процентами первым (растущая $ стоимость, toil, налог на скорость, риск). Используй сетку влияние-vs-усилия — высокое влияние/малые усилия = быстрые победы, высокое влияние/большие усилия = проекты, малое влияние = бэклог или won't-fix. Уродливый ≠ долг; стабильный+изолированный+дёшевый код остаётся.
Define cutover criteria and the rollback plan.
Определи критерии переключения и план отката.
Tap to reveal
Тапни, чтобы показать
Go: diffs within tolerance for N consecutive runs, SLA/freshness met at full load, consumers signed off, rollback tested. Rollback: keep legacy warm through a soak, one-flag facade flip, dual-write running so no data lost, defined triggers + owner. Rollback is a flag, not a restore.
Go: diff в пределах допуска N запусков подряд, SLA/свежесть выполнены на полной нагрузке, потребители подписались, откат протестирован. Откат: держи legacy тёплым через soak, переключение фасада одним флагом, dual-write работает (нет потери данных), определены триггеры + владелец. Откат — это флаг, а не восстановление.
When would you pick big-bang over incremental?
Когда выберешь big-bang вместо инкрементального?
Tap to reveal
Тапни, чтобы показать
Only when scope is small, schemas genuinely can't coexist, there's a hard external deadline, or running two systems costs more than the blast radius. Then de-risk with a full staging dress-rehearsal and a tested rollback. Never combine big-bang with re-architecting.
Только когда скоуп мал, схемы реально не могут сосуществовать, есть жёсткий внешний дедлайн, или работа двух систем стоит дороже, чем радиус поражения. Тогда снизь риск полной генеральной репетицией в staging и протестированным откатом. Никогда не комбинируй big-bang с переделкой архитектуры.
Role of CDC in a low-downtime migration?
Роль CDC в миграции с малыми простоями?
Tap to reveal
Тапни, чтобы показать
Snapshot at T → bulk backfill ≤ T → start CDC stream from T so the new system converges to live and the parallel-run diff stays meaningful right up to cutover. Prefer log/WAL-based CDC (captures deletes, low load); dedupe on PK+version; handle the snapshot/stream overlap.
Снимок в T → массовый backfill ≤ T → стартуй поток CDC с T, чтобы новая система конвергировала к live и parallel-run diff оставался значимым вплоть до переключения. Предпочитай CDC на базе log/WAL (захватывает удаления, малая нагрузка); дедуплицируй на PK+версии; обрабатывай пересечение снимка/потока.