В прошлом уроке мы установили: Postgres heap — это row store, со всеми вытекающими ограничениями для OLAP. Но Postgres extensible. В версии 12 (2019 год) появился
В этом уроке смотрим, что есть и когда что использовать.
До 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 для быстрой записи.
Hot chunks (свежие, write-heavy) — обычный heap. Cold chunks (старые, read-heavy) — compressed columnar. Postgres planner маршрутизирует запрос по диапазону времени.
Когда брать 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-требований.
Смотрим в Postgres, что доступно
Pglite — это сборка Postgres для браузера, без расширений. Поэтому Citus columnar и Timescale в sandbox не загрузить. Зато можно посмотреть, как устроены extensions API и пощупать pg_extension:
Список доступных типов хранилищ (table access methods). В default-сборке только heap. В реальной БД с citus_columnar здесь появится 'columnar'.
Какие расширения уже установлены / доступны к установке. В реальной production-БД к этому списку обычно добавляются pg_stat_statements, pgcrypto, citext, и сам columnar/timescale если они в pg_available_extensions.
В 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.
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) посередине.
В следующем уроке — критерии принятия этого решения подробнее.
Совместимость и ограничения
Все колумнарные расширения для 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.
- В следующем уроке — конкретное дерево решения.