Резюме Flashcards
(43 cards)
Почему именно gpfdist / external tables, как вышли на 350 MB/s и что стало новым bottleneck? — YuMoney
Мы взяли gpfdist, потому что это самый быстрый и простой способ массово заливать файлы в Greenplum: каждый из 42 сегментов напрямую «сосёт» свой поток HTTP, минуя master. На двух ETL-серверах крутится 16 gpfdist-процессов, а таблица разбивается на 32 файла. 40-гигабитная сеть полностью забивается — спид-тест показал ~350 MB/s, поэтому 1,4 ТБ истории ушли за час небольшим.
После этого улучшения нехватка уже не в CPU и не в сети, а в дисковых IOPS сегмент-узлов — SSD кипят, CPU скучает. Чтобы ускоряться дальше, надо ставить NVMe-диски или масштабировать кластeр, а не крутить ещё gpfdist-процессов.
Как планировали cut-over; сколько длилось финальное переключение? — YuMoney
Мы держали «двойную запись» (dual-write) почти три недели для всех таблиц: все 200 ключевых таблиц одновременно попадали и в ClickHouse, и в новый Greenplum. Инкременты летели через Debezium → Kafka → UPSERT-джобу, так что отставания почти не было.
Что именно переключали. У нас не одна таблица, а целый пул отчётных витрин. Когда каждая из них неделю подряд проходила DQ-тесты без расхождений, мы включали dual-write и шли к следующей. Финальный шаг. В ночь на 24 февраля 2024 в 02:17 я сменил целевой пул у Application Load Balancer: вместо ClickHouse-backend указал Greenplum-backend. Сколько длилась «темнота». Последний «догоняющий» пакет на 11 742 строки залился за 24 секунды; пока перекатывался ALB, отчёты были недоступны ~40 секунд. SLA 99 % мы не нарушили, бизнес даже не заметил. Что было бы, если что-то пошло не так. Возврат DNS-записи + выключение dual-write — одна команда, меньше минуты на откат.
Какие проверки целостности автоматизированы и где исполнялись? — YuMoney
После каждой выгрузки в STG автоматически срабатывает проверка в GitLab CI — джоба dq_smoke. Она гоняется на каждый merge request и проверяет:
количество строк (row_count), контрольную сумму CRC32 каждой партиции, минимум и максимум по шести ключевым числовым колонкам.
Если отклонение хотя бы одной метрики превышает 0,01 %, MR помечается как «красный», а результат проверки пишется в JUnit-отчёт — это позволяет сразу увидеть проблему прямо в CI-интерфейсе.
Уже в проде, по расписанию, в Airflow работает таска dq_post_cut. Она делает всё то же самое, что и dq_smoke, но дополнительно считает sha256_agg на случайной выборке 1 % строк — это помогает ловить случайные ошибки, не видимые на агрегациях.
Если где-то есть рассинхрон или нарушение, срабатывает метрика dq_mismatch_total в Prometheus, по которой настроен алерт в Slack.
Этот шаблон проверок — с hash’ами, row count’ами и метриками — я reuse’ил и в других местах: в банке это были AWR-хэши, в YuMoney — CRC + hash. Главное — придерживаться одинакового формата отчётов, чтобы Grafana могла строить единую тепловую карту и визуально подсвечивать зоны проблем.
Что делали с ClickHouse-типами (LowCardinality, DateTime64…) при миграции? — YuMoney
Сгенерировал маппинг в ноутбуке ch2gp_types.ipynb: LowCardinality(String) → text + ENCODING compresstype=lz4, DateTime64(3) → timestamp(3) with time zone; Enum в CH превратили в SMALLINT + lookup-таблицу. Скрипт-генератор Avro-схемы (для Debezium) автоматически применял те же правила — так CDC и bulk-load оставались совместимыми.
Как измеряли реальное улучшение time-to-DB и какие метрики сохранили? — YuMoney
Говорим цифрами, а не ощущениями.
Метрика загрузки — gp_load_seconds: сколько реально длится bulk-insert через gpfdist. Метрика для бизнеса — bi_dashboard_latency_seconds: p95 времени ответа CFO-дашборда. Что увидели: до миграции p95 = 155 с; через неделю после cut-over — 68 с (-56 %). Чтобы не откатиться назад график закрепили («pin») на борде SRE; если p95 снова ползёт вверх, PagerDuty звонит раньше, чем CFO заметит.
Где в оркестраторе живут проверки pandera? — YuMoney
Во всех landing-пайплайнах Airflow сначала срабатывает FileSensor, как только приходит новый файл. Следом запускается отдельная Python-таска validate_raw (это функция с декоратором @task, внутри группы validate). В ней pandera проверяет, что в загруженном датафрейме колонки и типы корректны. Для бэк-филлов вызывают тот же модуль из скрипта, чтобы не писать проверку заново.
Как синхронизируете pandera-контракты при изменениях схемы? — YuMoney
Ночной Spark-джоб schema_diff проверяет, не изменилась ли схема у датасетов в Datalake (чаще всего Parquet-файлы от landing- и CDC-пайплайнов).
Снимаем «фактическую» схему свежего датасета через spark.read.parquet().schema. Конвертируем эту Spark-схему в pandera.DataFrameSchema. Сравниваем её с YAML-контрактом этого датасета из Git (этот YAML — общий источник правды, из него downstream-пайплайны строят валидации и модели). Дифф классифицируем: Safe — добавили nullable-колонку, увеличили длину строки, добавили колонку в конец: скрипт сам создаёт MR с обновлённым YAML, CI зелёный. Breaking — удалили колонку, переименовали, поменяли тип: CI падает, нужен ручной апрув архитектора.
Таким образом мы автоматизировали контроль схем и синхронизацию контрактов: при безопасных изменениях всё летит само, при потенциально ломающих — сигнализируем человеку. Это снижает количество сюрпризов в проде и минимизирует ручные шаги.
Покрываем и landing (где схемы приходят через Spark), и CDC (где Avro-схемы берём из Schema Registry). В downstream-слоях (Airflow, dbt) никто не пишет валидацию руками — всё читается из YAML.
Что важно
DAG-ов мы не трогаем — они всегда подтягивают YAML на старте. Обновляется именно контракт, не код. Safe/Breaking определяется строго: даже rename — это breaking (иначе downstream не поймёт, что колонку переименовали). Если producer случайно откатил код — на следующее утро schema_diff это тоже заметит и предложит MR в обратную сторону. Если partition ещё не долетел, возможны false breaking-сигналы — решаем через выборку sample-файлов или кэш Parquet footer.
Какие правила валидации чаще всего ловили ошибки и как они влияли на SLA 99,3 %? — YuMoney
Топ-3 триггера: (1) purchase_dt вне диапазона 2020-01-01…today() — поймали 173 строки 31-11-2023; (2) amount < 0; (3) NULL в payment_id (PK). До внедрения было в среднем 5-6 SLA-miss/месяц; после — 1-2 (-60 %), что дало итоговые 99,3 % SLA за Q4-2023.
Чем мониторите сам SLA? — YuMoney
В коде имеется Airflow SLA_miss_callback → StatsD. Дальше Prometheus собирает sla_miss_total и dag_run_latency_seconds; Grafana дашборд «DAG Health» строит p95 и rate( ). Alert «rate > 0,001 / 5 мин» шлётся в Slack #airflow-alerts и поднимает on-call.
Инцидент, когда pandera спасла отчёт: что случилось и как вы починили? — YuMoney
12 октября 2023 в 04:12 (MSK) упал DAG landing_cashback_2023: на этапе валидации Pandera не обнаружила колонку currency_cd. Причиной оказался breaking change: внутренний cashback-сервис, выгружающий nightly-файл в NFS, внёс схему без этого поля.
Поскольку пайплайн напрямую писал данные в Greenplum, а currency_cd участвовала в расчётах сумм, нельзя было допустить, чтобы в витрины ушли строки с NULL вместо валюты.
Что сделал дежурный:
Восстановил поле вручную — добавил колонку currency_cd = 'RUB' в raw-файл (значение было фиксированное для всего датасета); Перезалил исправленный файл в папку, откуда его забирает DAG; Инициировал backfill DAG-а на конкретную дату (одна партиция), чтобы перегнать только проблемный кусок; Проверил, что данные успешно прогрузились в Greenplum, и NULL не утекли в BI-витрины.
Результат: CFO-отчёт успел пересчитаться к 07:30, SLA не нарушен. Pandera сработала как предохранитель: остановила пайплайн до появления ошибок в проде, а ручное восстановление поля позволило избежать простоев.
Как выбирали распределяющий ключ и проверяли до выката? — YuMoney
Задача: убедиться, что данные в Greenplum ложатся ровно, без «жирных» сегментов и потери JOIN-производительности.
Сняли факты о данных На DEV-кластер выгрузили 1 % реальных строк и с помощью gp_toolkit.gp_stats_missing() собрали кардинальность по всем кандидат-колонкам. Дополнительно прогнали скрипт gp_skew_check.sql (считает % строк и % байт на каждый сегмент). Отфильтровали кандидатов Правило № 1: ключ должен участвовать в 99 % основных JOIN-ов (подсмотрели в DataHub lineage). Правило № 2: кардинальность ≥ 100 × число сегментов, чтобы избежать дублей на одном узле. Прогоны «на скорость» Создали три копии таблицы transactions_sample с тремя разными DISTRIBUTED BY (...). Прогнали типовое BI-джойны EXPLAIN ANALYZE × 10. Смотрели qt_total_time и skew_rows — победил вариант DISTRIBUTED BY (account_id) (скошенность < 6 %). Генеральная репетиция На STG загрузили 100 ГБ той же таблицы, снова проверили skew — не выше 8 %. Только после этого посадили ключ в DDL-миграцию Flyway V20240215\_\_transactions.sql.
Какие партиции (range/list) и как это влияло на pruning в палмне при разрботке? — YuMoney
На базовом слое исторических данных мы используем секционирование по диапазону: PARTITION BY RANGE (txn_month) — с шагом в месяц от января 2022 до декабря 2024. Это нужно потому, что типовые запросы (например, от финконтроля или аудиторов) всегда идут по периоду. Такой подход позволяет физически читать только нужные месяцы, а не весь объём — вместо 24 миллиардов строк читаются ровно те, что нужны.
Для «горячего» квартала — октябрь, ноябрь, декабрь 2024 — добавили поддерево: внутри диапазона txn_month = 2024-10 … 12 сделали дополнительное секционирование по регионам (sub-partition by LIST(region_cd)). Причина — в Новый год нагрузка возрастает вдвое, а BI-дашборды чаще строятся по регионам.
В реальности это дало сильный прирост: EXPLAIN по живым запросам показывает pruning 92–95 % блоков, а p95-латентность запроса упала с 2,4 секунды до 0,9.
Перед выкладкой мы это проверяли руками: брали пять настоящих BI-запросов и гоняли EXPLAIN (ANALYZE, VERBOSE), чтобы посмотреть, сколько секций реально читается. Порог в 90 % pruning стал для нас зелёным флажком: если его не добивались — секционирование дорабатывали.
Как без блокировок добавляете партиции и собираете статистику? — YuMoney
Добавление новой секции
На STG создаём пустую таблицу-«скорлупу» p202508 LIKE parent INCLUDING DEFAULTS. ALTER TABLE parent ATTACH PARTITION p202508 FOR VALUES IN ('2025-08') — операция чисто в системных каталогах, доли секунды, только ACCESS SHARE-лок на родителя. Если нужно пересыпать данные (реже), используем EXCHANGE PARTITION … WITHOUT VALIDATION, так же без длинного ACCESS EXCLUSIVE.
Статистика
Greenplum 7 умеет incremental analyze: ANALYZE ROOTPARTITION parent PARTITION p202508; — считает только свежую часть, не трогая 2-ТБ историю. Job analyze_nightly в Airflow собирает stats на все новые секции перед 06:00, так что утренние отчёты уже опираются на актуальный план.
Результат
Окно «maintenance» для месячной секции — < 1 мин, без блокировок отчётов. BI-запросы сразу бьют в актуальную статистику, планы не «прыгают» на следующий день.
Resource queues / workload management при разработке гринпалма? — YuMoney
В Greenplum мы использовали встроенный механизм управления ресурсами — resource queue. Мы завели две очереди: etl_q — под тяжёлые ночные загрузки (до 4 одновременных запросов, 70 % оперативной памяти сегмента), и bi_q — под дневную BI-аналитику (до 32 лёгких запросов, 25 % памяти). Эти очереди жёстко привязаны к ролям пользователей, и каждый запрос от Airflow-worker-а поступает именно от нужной роли.
Дополнительно мы включили параметр gp_resqueue_priority, благодаря которому Greenplum может временно понижать приоритет bulk-запросов из etl_q, если в кластер параллельно поступают BI-запросы из bi_q. Это гарантирует, что даже в пике ночью важные отчёты (например, CFO) не будут ждать конца загрузки.
Чтобы исключить «просачивание» тяжёлых DAG-ов за рамки очередей, мы синхронизировали лимиты и на уровне Kubernetes: каждому Airflow-worker-у через LimitRange выставлены ресурсы, строго соответствующие очереди Greenplum, под которую он работает. Таким образом, и в базе, и на уровне pod-а действует один и тот же предел — и никаких накладок.
Наконец, за загрузкой очередей мы следили через Prometheus: метрика pg_resq_active_total показывает, сколько запросов в каждой очереди активно. Если etl_q стабильно упирается в потолок, это явный сигнал — пора масштабировать кластер или пересматривать режим загрузки.
Как замеряли эффективность схемы после запуска проекта клика в гп? — YuMoney
После выката завели два дэшборда:
“Query Time” — p95 и p99 для пяти «тяжёлых» запросов (те же, что крутили в EXPLAIN до релиза). “Skew & Hit Ratio” — gp_stat_database + pg_statio_all_tables: доля блоков, прочитанных не со «своего» сегмента, и средний skew по 15-минутному окну.
Пороговые алерты: если p95 > 1 с или skew > 15 %, PagerDuty дёргает дежурного. За первый месяц p95 держался < 900 мс, skew прыгал до 12 % только на конец квартала — значит выбор распределяющего ключа и под-лист-партиций был удачный.
Почему Patroni, а не Stolon/Crunchy? — YuMoney
Мы выбрали Patroni по ряду прагматичных причин, протестировав в DEV-среде и сравнив с альтернативами — Stolon и Crunchy Postgres Operator.
Во-первых, по времени failover Patroni оказался самым быстрым: 6–8 секунд против 12–15 у Stolon и 10–12 у Crunchy. Это критично для SLA.
Во-вторых, Patroni не требовал поднимать ничего нового в инфраструктуре. Он использует уже развёрнутый у нас Etcd — то самое key-value-хранилище, которое мы и так держим для Vault и Flink. Stolon требовал бы тащить в кластер либо Consul, либо Etcd с нуля. Crunchy вообще работает через свой Custom Resource Definition (CRD) и требует установки отдельного контроллера и операторского чарта — ещё одной точки отказа.
Третье — наблюдаемость. У Patroni из коробки есть REST API, метрики health и история — и всё это легко читается через Prometheus-exporter. У Stolon API сильно примитивнее, у Crunchy — нужный bundle приходится докручивать вручную.
Наконец, сам стек. Patroni — это Python + YAML, что знакомо нашим DevOps. Stolon — Go с кастомной логикой конфигурации. Crunchy — огромный Helm-чарт и довольно тяжёлая модель, не подходящая для лёгкой кастомизации.
В итоге: Patroni дал нам быстрый failover (<10 с), не потребовал ни одного нового сервиса в кластере и хорошо вписался в уже существующую инфраструктуру — поэтому и выбрали его.
Bootstrap кластера: secrets, WAL, init SQL? — YuMoney
Всё параметризовано в values.yaml чарта pg-single: * Kubernetes-secret c superuser_password & replicator_password создаётся Helm-template-ом;. * WAL-архив указываем wal_gcs_bucket = «yumoney-pg-wal» — Patroni сам пушит через wal-e; * init-SQL — список файлов в initScripts:; Helm запускает post-install hook, который монтирует их в /docker-entrypoint-initdb.d.
Что значит «кластер под ключ»? — YuMoney
Когда мы говорим «PostgreSQL-кластер под ключ», это буквально один Helm-релиз helm install svc-payments pg-single и через ~15 минут команда получает:
Patroni-кластер (мастер + реплика, health-API сразу готов). pgBouncer c настроенным пулом. Prometheus-экспортёр и alert-rule «checkpoint > 2 с». Готовый ServiceMonitor — метрики тут же появляются в Grafana.
Никаких ручных действий DBA; единственное, что девы заполняют в values.yaml, — имя сервиса и размер PVC.
Обновления без downtime в хелме релизовали? — YuMoney
Minor-upgrade Postgres: Patroni делает switchover. Сначала патчится реплика, она становится мастером, старая нода перекатывается и возвращается репликой. Клиенты сидят за pgBouncer и теряют лишь секундную паузу на реконнект.
Helm-чарт приложения: используем strategy RollingUpdate + maxUnavailable=0. Kubernetes выводит pod из Traffic, ждёт, пока pgBouncer отдаст последние соединения, только потом сносит. Пользовательские транзакции не обрываются.
Так мы перешли с Postgres 14.4 → 14.8 и с Airflow 2.5 → 2.6, не нарушив SLA 99 %.
Параметры pgBouncer и влияние на латентность? — YuMoney
pool_mode: transaction — после коммита соединение сразу возвращается в пул; это экономит ~25 мс на каждый «короткий» BI-запрос.
default_pool_size: 40, reserve_pool_size: 10 — на сервис приходится ≤ 50 активных серверных коннектов, Postgres не душится на max_connections. server_lifetime: 900 — раз в 15 минут соединение перераз-открывается, чтобы не разрастались temp tables. query_wait_timeout: 5 s — если пул забит дольше пяти секунд, клиент сразу получает ошибку, а не зависший дэшборд.
Нагрузочный тест Locust показал: cold-latency с 28 мс упала до 6 мс, а пиковый TPS вырос ~×1.7, потому что Postgres тратит время на исполнение, а не на hand-shake.
Зачем Flume перед Kafka? — YuMoney
Flume — это «привратник» на самом сервере платёжного шлюза. Он:
умеет хвостить syslog-файл строчки-за-строчкой и буферить их, если сеть падает; прямо на лету маскирует PAN-ы («5100 **** 1234») обычным RegexInterceptor — никакого Java-кода; добавляет служебные headers (service=fraud, env=prod), по которым Kafka потом раскидывает данные по партициям.
Мы тем самым:
снимаем нагрузку с приложения — ему не нужно знать про Kafka-протокол; выносим PCI DSS-маску как первый шаг конвейера, чтобы «чистые» номера никогда не покидали доверенный узел; получаем надёжную «канал-файл» модель Flume: при сети-0 пакеты лежат на диске и догоняют позже.
Spark Streaming: micro-batch или Structured, как подобрали параметры? — YuMoney
Взяли Structured Streaming (Spark 3.4) в режиме micro-batch; непрерывный режим отсеяли сразу — нужны оконные функции, а они там недоступны.
trigger = 300 s — каждые пять минут пишем кусок, чтобы вписаться в KPI «тревога ≤ 10 мин». window = 1 day / slide = 5 min, watermark = 10 min — поздняя транзакция может отстать, но не больше десяти минут. spark.sql.shuffle.partitions = 600 и динамическая аллокация: стартуем с 6 executors, под наплывом растём до 60.
Подбирали цифры на тестовом кластере: гнали реальный трафик сутки, смотрели lag в Kafka и загрузку CPU. Когда lag стабильно держался < 80 k msg, а кластер ночью сворачивался до 5 executors, параметры зафиксировали.
Exactly-once до HDFS при retry Spark + дедуп в модели? — YuMoney
Чекпоинт (checkpointLocation=/user/hdfs/fraud_cp) сохраняет offset’ы Kafka — при падении поток продолжает с точного места.
Пишем в Partitioned Parquet и полагаемся на «rename atomic» — Spark сперва пишет во временную папку, потом переименовывает. Если драйвер упал до rename, данных «как бы нет» и повторная запись не даёт дублей.
Перед записью делаем dropDuplicates([‘txn_id’,’window’]), а при заливке в Greenplum используем MERGE … ON txn_id — так дупликаты не проходят даже если Spark перезапустили трижды.
Как считали +18 % прироста на проекте спарка? — YuMoney
Две метрики в Prometheus:
fraud_flag_total — сколько операций поток пометил подозрительными. fraud_confirmed_total — сколько из них служба ИБ действительно признала мошенничеством.
Мы сравнили отношение
fp_rate = 1 – (fraud_confirmed_total / fraud_flag_total)
на 30-дневном окне до включения Bloom-filter-правил и на 30-дневном окне после. Разница в среднем –18 %. Скрипт лежит в Jupyter-ноуте fp_rate_eval.ipynb, результаты автоматически сохраняются в Confluence-страницу релиза.