awesome-everything EN
↑ Обратно к восхождению

Data engineering

OLTP vs OLAP: чтение запросов и планов

Суть Читайте реальные планы запросов, DDL и конфиг пайплайна по юниту OLTP vs OLAP, прогнозируйте стоимость скана на row vs column store и выбирайте фикс с наибольшим рычагом.
Высота — путь к senior
НольJuniorMiddleSenior
Ты на senior-высоте — в орбите
◷ 14 min

План запроса и DDL — там, где разделение нагрузок проявляет себя. Прочитайте каждый сниппет, спрогнозируйте поведение на row vs column store и выберите фикс, который senior делает первым.

Цель

Отработайте цикл, который вы запускаете на каждом ревью архитектуры данных: прочитать запрос, спрогнозировать просканированные байты и потянуться к смене layout или пайплайна — а не к костыльному индексу — прежде чем аналитическая нагрузка отравит транзакционную.

Сниппет 1 — EXPLAIN на row store

EXPLAIN (ANALYZE, BUFFERS)
SELECT country, SUM(total)
FROM orders                       -- 40M строк, 50 колонок, row-store Postgres
WHERE created_at >= now() - interval '90 days'
GROUP BY country;

Seq Scan on orders  (cost=0.00..2.1M rows=38_000_000)
  Buffers: shared read=4_900_000   -- ~38 GB протянуто через кэш
Planning Time: 0.2 ms
Execution Time: 31_482 ms
Викторина

Планировщик выбрал Seq Scan и прочитал ~38 GB, хотя запросу нужны только country и total. О чём это говорит и какой фикс правильный?

Сниппет 2 — разделение схемы

-- OLTP: сильно нормализовано ради дешёвых консистентных одиночных записей
CREATE TABLE orders (
  id            bigint PRIMARY KEY,
  user_id       bigint REFERENCES users(id),
  country_id    smallint REFERENCES countries(id),
  total_cents   bigint,
  created_at    timestamptz
);

-- OLAP target: денормализованный широкий факт, country свёрнут в низкокардинальную колонку
CREATE TABLE orders_fact (
  order_id    UInt64,
  user_id     UInt64,
  country     LowCardinality(String),   -- dictionary-encoded
  total_cents Int64,
  created_at  DateTime
) ENGINE = MergeTree ORDER BY (created_at, country);
Викторина

Почему OLTP-таблица нормализована с FK country_id, а OLAP-факт денормализует country в колонку LowCardinality(String)?

Сниппет 3 — конфиг пайплайна

# Коннектор Debezium: стримит изменения WAL Postgres в Kafka, приземляет в склад
connector.class: io.debezium.connector.postgresql.PostgresConnector
plugin.name: pgoutput
table.include.list: public.orders,public.users,public.countries
snapshot.mode: initial          # один бэкфилл, затем стрим изменений
# downstream sink батчит в orders_fact каждые 30s
Викторина

Этот CDC-пайплайн стримит WAL в склад с 30s батчем sink. Что он даёт и какой компромисс надо принять?

Сниппет 4 — ловушка write-amplification

-- Инженер «чинит» медленные дашборды, записывая orders прямо в ClickHouse,
-- по одному INSERT на заказ, из горячего пути checkout:
INSERT INTO orders_fact (order_id, user_id, country, total_cents, created_at)
VALUES (918273, 4412, 'DE', 4999, now());   -- вызывается ~5000×/s, по строке за раз
Викторина

Почему проталкивание одиночных INSERT из горячего пути checkout прямо в column store даёт обратный эффект?

Итог

Читаешь артефакты — и layout говорит тебе стоимость: Seq Scan, читающий десятки GB на row store, означает агрегат по всей таблице, который не починит ни один индекс — фикс — column store, делающий pruning. Нормализованный OLTP и денормализованный низкокардинальный OLAP-факт противоположны по дизайну, а не баг. CDC покупает изоляцию ценой ограниченного лага, и сам по себе CDC не делает сканы быстрыми — это делает колоночный target. А одиночные записи в column store патологичны: держи записи в row store, батчи их в column store. Диагностируй по плану и layout, чини layout, затем проверь, что просканированные байты упали.

Продолжить восхождение ↑OLTP vs OLAP: разделите нагрузку
хоткеи развернуть
поиск
K
пред. пьеса
k
след. пьеса
j
тиры
t
это меню
?
sources3
expand
  1. 01
  2. 02
  3. 03

Trademarks belong to their respective owners. Editorial reference only.