Learning Platform
Урок 15.04 · 24 мин
Продвинутый
Citus columnarHydraTimescaleDBpg_lakehouseTable access methodPostgres extensions

В прошлом уроке мы установили: Postgres heap — это row store, со всеми вытекающими ограничениями для OLAP. Но Postgres extensible. В версии 12 (2019 год) появился

Table Access Method API
, который позволяет создавать таблицы с не-heap storage. Этого хватило, чтобы Postgres-экосистема обросла column store расширениями.

В этом уроке смотрим, что есть и когда что использовать.

До TAM API column store расширения для Postgres существовали как forks: cstore_fdw (от Citus, FDW-вариант, 2014), Greenplum AOC (часть форка). Расширений, работающих в обычном PG без модификации binary, не было. TAM API сменил это: теперь column store можно подключить как plain extension через CREATE EXTENSION columnar.

Citus columnar (Hydra)

Изначально часть Citus (горизонтальное масштабирование Postgres от Microsoft). В 2022 году выделено как открытое расширение citus_columnar. С 2023 года Hydra (стартап) форкнул это и развивает под именем Hydra: ставка на «open-source ClickHouse, встроенный в Postgres».

Что даёт:

  • CREATE TABLE … USING columnar. Таблица создаётся с альтернативным AM. Внутри — chunked column store, по умолчанию stripe size = 150 000 строк.
  • Compression: PGLZ или ZSTD на уровне chunk. На реальных данных compression 5-10x.
  • Vectorized executor (в Hydra активно дорабатывается; в стоковом citus_columnar — гибридный путь).
  • Совместимость с Postgres: триггеры, RLS, FK, индексы (B-tree поверх columnar), все стандартные join’ы.

Ограничения:

  • INSERT only, без UPDATE/DELETE по умолчанию. В Hydra добавили DML, но это требует переписывания целых stripes. Не для write-heavy.
  • Нет vacuum-friendly tuple visibility. MVCC реализован через snapshot id’ы стрипов; долгие транзакции блокируют compaction.
  • Index scan ограничены: некоторые типы индексов не поддерживаются.

Когда брать:

  • Data warehouse внутри Postgres-стека, где ты не хочешь второй движок и второй pipeline.
  • Append-only логи / events с редкими апдейтами.
  • Запросы типа TPC-H с aggregation по большому окну.

Когда не брать:

  • Write-heavy с частыми апдейтами. Хочешь UPDATE — это не для тебя.
  • Когда нужна предсказуемая latency point lookup. Columnar делает их в 5-20x медленнее.

Дополнительный пример из реальной практики: на 100M событий с 10 колонками, переход с обычной orders на orders USING columnar даёт типично:

  • Размер таблицы: с 12 GiB heap до 1-2 GiB columnar (compression 6-10x).
  • Aggregation SELECT date_trunc('day', t), count(*) FROM orders GROUP BY 1 ускоряется в 5-15 раз.
  • Single-row lookup WHERE id = 42 замедляется в 10-30 раз (если нет индекса; с индексом — медленнее в 2-5x).

То есть это инструмент конкретной формы: аналитика побеждает, OLTP проигрывает. Поэтому типичный паттерн использования — держать две таблицы: orders (heap, hot) и orders_archive (columnar, cold), и периодически переливать старые данные INSERT INTO orders_archive SELECT * FROM orders WHERE placed_at < NOW() - INTERVAL '90 days'; DELETE FROM orders WHERE ....

TimescaleDB

Расширение для time-series, изначально от Timescale. Не «честный column store» в смысле «отдельное хранилище», а гибрид:

  • Hypertable — таблица, поделённая на chunks по времени (один chunk = один период, обычно 1 день или 1 неделя).
  • Old chunks compress’ятся columnar-style. Команда compress_chunk() (или automatic policy) переводит chunk в columnar layout: значения колонки в одной строке как массив, dictionary encoding на text-колонках, delta+RLE на timestamp.

После compression chunk занимает 5-20x меньше места, и запросы по time-range используют columnar scan. Свежие chunks остаются row-based для быстрой записи.

TimescaleDB: гибридный row + column

Hot chunks (свежие, write-heavy) — обычный heap. Cold chunks (старые, read-heavy) — compressed columnar. Postgres planner маршрутизирует запрос по диапазону времени.

Chunk[2024-01-01..02] rowhot, INSERTs идут сюда
Chunk[2023-12-30..31] rowhot
Chunk[2023-12-29..30] coldcompressed columnar
Chunk[2023-12-28..29] coldcompressed columnar
политика compressionadd_compression_policy('events', INTERVAL '7 days')

Когда брать TimescaleDB:

  • Time-series workload по определению (метрики, ивенты, IoT).
  • Свежие данные пишутся фастом, старые — большой архив, читаемый аналитикой.
  • Хочется continuous aggregates: материализованные view, пересчитываемые инкрементально.

Когда не брать:

  • Нет временного измерения / сложно chunk’овать. TimescaleDB без partition key по времени — это сильно слабее.
  • Нужен полноценный columnar OLAP на все данные (не только cold).

pg_lakehouse / pg_analytics

Молодое направление (2024-2025). Расширения, которые не хранят данные в Postgres, а делают так, что Postgres напрямую читает Parquet-файлы из S3/MinIO/локального диска.

  • pg_analytics (от ParadeDB) — основан на DataFusion (Apache Arrow). Регистрирует foreign table, под которым Parquet-файлы.
  • pg_lakehouse — аналогично, чуть другой API. В 2024 объединились с ParadeDB.
  • Альтернатива — postgres_fdw + DuckDB extension, или clickhouse_fdw для proxy на ClickHouse.

Что это даёт:

  • Postgres работает как query gateway к Parquet datalake. Запрос идёт через PG, исполнение делегируется DataFusion (vectorized C++/Rust).
  • Не дублируется storage. ETL пишет в S3, и Postgres сразу видит таблицу.
  • Не теряется SQL-совместимость с приложениями.

Ограничения:

  • Производительность зависит от размера датасета и сети. На локальном Parquet работает на скорости ClickHouse; на удалённом S3 — ограничено пропускной способностью.
  • Не все Postgres-фичи доступны: индексы поверх Parquet нельзя, FK тоже.
  • UPDATE/DELETE на Parquet чаще всего невозможны (или работают как rewrite).

Когда брать:

  • Уже есть data lake в S3 (Parquet/Iceberg/Delta), но хочется SQL через Postgres-API.
  • Аналитика — основной use-case, не транзакции.
  • Хочется federated query: join’ить «горячий» heap-table с «холодным» Parquet.

Foreign Data Wrapper подход

Альтернатива table access method — это FDW (Foreign Data Wrapper). FDW регистрирует «внешнюю таблицу», и каждое чтение через неё делегируется в callback расширения. Postgres сам не хранит данные.

  • clickhouse_fdw — FDW к ClickHouse-серверу. PG выполняет планирование и delegate-aggregation, а ClickHouse делает тяжёлую часть. Можно JOIN heap_table TO clickhouse.events в одном SQL.
  • postgres_fdw + DuckDB extension — DuckDB подключается к Postgres как foreign tables (или наоборот). Удобно для миграционного периода: продакшен на PG, аналитика читает с того же PG, но через DuckDB-движок.
  • parquet_fdw — читает Parquet файлы как обычную таблицу.
  • file_fdw (core PG) — читает CSV/TSV как таблицу. Для серьёзной аналитики слабовато, но хорош для прототипов.

FDW vs TAM trade-off: FDW проще написать (не нужно реализовывать aminsert, amindexscan и десяток других интерфейсов AM), но он не интегрирован с MVCC и transaction system Postgres. Для read-only аналитики FDW часто достаточно.

Что есть ещё

  • Apache AGE — для графов, не про columnar.
  • zedstore — research-проект 2019 от Greenplum: column store как AM, заброшен.
  • ZHEAP — попытка переписать heap под другую MVCC (in-place updates). Шла к мейнстриму, потом застопорилась. Не про columnar.
  • Greenplum / Cloudberry — это форки PG с встроенным columnar AOC. Уже не «расширение», а отдельный продукт.

Что под капотом у Citus columnar

Чуть подробнее про устройство одного представителя — самого распространённого в PG-мире:

  • Stripe — главная единица хранения, по умолчанию 150 000 строк. Внутри stripe — N column files (по одному на колонку таблицы). Это даёт column store layout внутри stripe.
  • Chunk — внутренняя группа значений на колонке внутри stripe (обычно 10 000 значений). На chunk-уровне применяется compression (PGLZ или ZSTD) и хранится metadata (min/max, null count) для chunk pruning.
  • Metadata catalog — две внутренние таблицы: columnar.stripe (информация о stripes) и columnar.chunk_group (статистики chunks). По ним planner может пропускать стрипы, не пересекающиеся с WHERE.
  • Index support — обычные B-tree индексы на columnar table работают, но через scan + сравнение, без Index Scan через item pointer (его нет в columnar). На selective queries индексы помогают, на full scan — нет.

Это даёт column store с compression, chunk-pruning, и vectorized scan, прозрачно встроенный в Postgres SQL.

Дерево принятия решения

Когда хватит расширения, а когда нужен отдельный движок

Грубая эвристика. Реальные ответы зависят от деталей: размер команды, существующего стека, latency-требований.

< 100 GiB cold data + OLTP во главеоставайся на Postgres, +Citus columnar для отчётов
time-series workloadTimescaleDB compressed chunks
data lake в S3 + хочешь SQL через PGpg_analytics / pg_lakehouse
> 1 TiB, real-time OLAP, низкая latency на дашбордахClickHouse / Snowflake — отдельный stack
embedded аналитика в сервисеDuckDB — в процесс. Не требует server.

Смотрим в Postgres, что доступно

Pglite — это сборка Postgres для браузера, без расширений. Поэтому Citus columnar и Timescale в sandbox не загрузить. Зато можно посмотреть, как устроены extensions API и пощупать pg_extension:

Список доступных типов хранилищ (table access methods). В default-сборке только heap. В реальной БД с citus_columnar здесь появится 'columnar'.

PostgreSQL

Какие расширения уже установлены / доступны к установке. В реальной production-БД к этому списку обычно добавляются pg_stat_statements, pgcrypto, citext, и сам columnar/timescale если они в pg_available_extensions.

PostgreSQL

В production эти команды покажут, что может быть установлено в твоей БД. Если в выводе есть columnar — значит, DBA уже подготовил почву под columnar. Дальше CREATE EXTENSION columnar; и CREATE TABLE events_history (...) USING columnar;.

И для понимания order of magnitude — посмотрим на размер реальной таблицы и представим, что был бы columnar:

Размер heap-таблицы orders. На реальном columnar той же таблицы было бы в 5-10x меньше из-за compression. На 100M строк это разница 50 GiB vs 5-10 GiB.

PostgreSQL

Continuous aggregates: тихий герой

Отдельно стоит упомянуть continuous aggregates (TimescaleDB) и materialized views with incremental refresh (PG 18 future / pg_ivm extension). Это не column store в строгом смысле, но решают похожую задачу: пред-агрегировать данные, чтобы дашборды не сканировали миллиарды строк каждый раз.

  • TimescaleDB CA: создаёшь CREATE MATERIALIZED VIEW ... WITH (timescaledb.continuous). Под капотом — обычная materialized view + триггеры на инвалидацию + background refresh. Дашборд читает CA вместо raw, и запрос превращается из «10 минут на 1B строк» в «50 мс на 10K агрегированных».
  • pg_ivm — экспериментальное расширение incremental view maintenance: пересчитывает materialized view delta’ами, а не FULL REFRESH.

Часто continuous aggregates + индекс на полученной таблице решают аналитическую задачу без перехода на column store. Это первое, что стоит попробовать перед миграцией.

Граница, когда уйти на отдельный движок

Когда расширения уже мало. Симптомы:

  • Аналитический запрос трогает > 100 GiB сырых данных и должен ответить за секунды (а не минуты).
  • Дашборд должен иметь interactive latency (< 200 мс) при concurrent users > 10.
  • Аналитика — это основная нагрузка, а не side-quest.
  • Возникает необходимость в distributed execution на десятки нод.

В этих случаях даже Citus columnar упирается в архитектуру Postgres (volcano, shared buffer manager, WAL для каждой записи). Дешевле взять ClickHouse или Snowflake под аналитику и оставить Postgres под транзакции. Это и есть классический OLTP+OLAP split с CDC-репликацией (Debezium / native CDC из PG 16) посередине.

В следующем уроке — критерии принятия этого решения подробнее.

Проверка знанийKnowledge check
У команды растёт OLAP-нагрузка на Postgres: каждое утро строится cohort-анализ за 30 дней (~200M событий), занимает 25 минут и блокирует часть БД. Какие варианты по возрастанию усилия и эффекта ты бы предложил, и где остановишься, если ответ должен укладываться в 30 секунд?
ОтветAnswer
Лестница вариантов: 1. Profile + индексы + увеличить work_mem (1-2 дня работы). Если узкое место — отсутствие BRIN-index по occurred_at, можно срезать с 25 до 5-10 минут. Если запрос упирается в CPU (volcano-overhead) — не поможет. 2. Continuous aggregates через materialized view + refresh by cron (3-5 дней). Если cohort-анализ можно частично преагрегировать, упадёт до 1-2 минут. 3. TimescaleDB hypertable + compress old chunks (1-2 недели). Если данные time-series, compression + columnar scan дадут 5-15x. Получишь ~2-5 минут. 4. Citus columnar / Hydra для events table (2-3 недели). Vectorized + column store даст 10-30x. Получишь ~30-120 секунд. 5. CDC в ClickHouse + переписать запрос на ClickHouse SQL (1-2 месяца, отдельный stack). 30 секунд — реалистичная цель только тут. Если ответ должен быть < 30 секунд, остановишься на ClickHouse. Если можно жить с 1-2 минутами — TimescaleDB / Citus columnar дают тот же результат без поддержки второго движка. Если 5 минут — индексы + materialized view. Выбор зависит от ROI команды на инфраструктуру.

Совместимость и ограничения

Все колумнарные расширения для Postgres имеют общие ограничения, о которых важно знать заранее:

  • MVCC через snapshot id, а не xmin/xmax — нет HOT updates, дольше TX могут блокировать compaction.
  • WAL для columnar данных — есть, но другой формат; backup/restore работает через стандартные tools, но performance в восстановлении может быть хуже.
  • Statistics для planner’а — собираются ANALYZE’ом, но distribution-аналитика на columnar блоках ограничена. Planner иногда выбирает плохой join order.
  • Foreign keys, triggers, RLS — поддерживаются, но overhead может расти из-за columnar layout.
  • pg_dump / pg_restore — работают, но восстановление columnar таблицы — это re-encoding всех стрипов, что дольше heap-restore в 2-3x.

Перед production deploy любого columnar расширения нужно протестировать backup/restore, миграцию, и edge cases с триггерами.

Чек-лист

  • Table Access Method API (PG 12+) позволяет расширениям подключать альтернативное хранилище таблиц.
  • Citus columnar / Hydra — column store как AM. Compression 5-10x, vectorized scan, append-friendly. Не для write-heavy.
  • TimescaleDB — гибрид: hot chunks heap, cold chunks compressed columnar. Идеален для time-series.
  • pg_lakehouse / pg_analytics — query gateway к Parquet/Iceberg в S3 через Postgres. Federated query без дублирования storage.
  • Все расширения отлично для bounded OLAP внутри Postgres-стека.
  • Граница ухода на отдельный движок (ClickHouse / Snowflake): > 100 GiB hot OLAP, interactive latency на дашбордах, аналитика как primary workload.
  • В следующем уроке — конкретное дерево решения.
Проекции: автоматическая оптимизация Memory Layout в Apache Arrow

Проверьте понимание

Результат: 0 из 0
Концептуальный
Вопрос 1 из 4. Что такое Table Access Method API в Postgres и зачем он появился?

Закончили урок?

Отметьте его как пройденный, чтобы отслеживать свой прогресс

Войдите чтобы оценить урок

Прогресс модуля
0 из 5