Мадияр Хамзанов
Статья

Как мы переехали с PostgreSQL на ClickHouse — и госзакуп-аналитика начала летать

Вчера — опенсорс-эксперимент одного инженера в Яндексе. Сегодня — компания за $15 млрд. Секрет до смешного прост. Плюс — наша живая конфигурация на 781 млн строк.

ClickHouseData EngineeringPostgreSQLКазахстан14 мин
Мадияр Хамзанов
Мадияр Хамзанов
3 июля 2026

Расскажу простую историю с очень конкретным финалом. Я делаю агрегатор госзакупок Казахстана 10b.kz — мы парсим официальный портал госзакупок РК, планы, договоры, акты и казначейские платежи. Данных стало реально много: сотни миллионов строк. Сырьё лежало в PostgreSQL, и аналитика начала упираться в пол: каждый серьёзный отчёт — это полный скан и тяжёлый GROUP BY, который тянется секундами и минутами. Я перевёл всё на ClickHouse — и та же самая аналитика по таблице в 31 млн лотов стала отвечать за 0,08–0,5 секунды. Ниже — сначала на пальцах, в чём фокус, а потом — моя живая продовая конфигурация с точными цифрами.

781 млн
строк на одной VPS
192 ГиБ данных
0,08 с
агрегация 31,4 млн лотов
378 млн строк/сек
4.8×
сжатие на диске
355 → 74 ГиБ
16 vCPU
ноль шардов, одна машина
30 ГБ RAM

Коротко для тех, кто спешит

1

Одна недорогая VPS (16 vCPU, 30 ГБ RAM) держит 781 млн строк / 192 ГиБ данных о госзакупках РК — без всякого кластера и шардинга.

2

Аналитика по таблице в 31 млн лотов отвечает за 0,08–0,5 секунды. Пик сканирования — 378 млн строк/сек и 3,9 ГиБ/сек на одном сервере.

3

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

4

ClickHouse — это не замена PostgreSQL, а другой инструмент. PostgreSQL — для транзакций (OLTP). ClickHouse — для аналитики (OLAP): много чтения, агрегации, полные сканы.

1. Что это вообще такое, на пальцах

Представьте обычную базу вроде PostgreSQL как толстую тетрадь, где данные записаны по строкам: одна строка — один лот, и все его поля (номер, заказчик, сумма, дата, статус…) лежат рядом, одной записью. Удобно, когда нужно достать или поправить одну конкретную запись целиком. Но когда ты хочешь просто сумму по одной колонке за год, приходится пролистать каждую строку целиком — вместе со всеми ненужными полями.

ClickHouse хранит данные по колонкам: все суммы лотов лежат подряд в одном месте, все даты — в другом, все имена заказчиков — в третьем. Нужна сумма по одной колонке — ClickHouse читает только её, а не всю таблицу. Плюс есть бонус: значения одного типа, лежащие подряд, жмутся в разы лучше, чем перемешанные поля строки. Отсюда вся магия — меньше читаем с диска, значит, быстрее отвечаем. Это и называется колоночной базой данных (хороший разбор темы есть и у MotherDuck, и у Fivetran).

Конкретный пример из моей боевой таблицы лотов (31,4 млн строк, 5,9 ГиБ). Один и тот же GROUP BY: если запросу нужны только 2 числовые колонки, ClickHouse читает с диска 330 МиБ вместо 5,9 ГиБ — примерно в 18 раз меньше. Строчной базе в любом случае пришлось бы поднять строки целиком.

Column pruning

Сколько реально прочитано с диска

Один GROUP BY по таблице лотов (31,4 млн строк, логически 5,9 ГиБ). Меньше — лучше.

Первый запрос трогает 2 колонки из ~60 → 0,08 с. Второй тянет ещё и длинное строковое имя заказчика → 6,6 ГиБ и 0,46 с. Тот самый эффект «читаем только нужное».

Отсюда сразу две суперсилы. (1) Column pruning — читаем только те колонки, что нужны запросу. (2) Сжатие 4.8× — одинаковые по типу значения подряд ужимаются в разы: у меня 355 ГиБ логических данных занимают на диске 74 ГиБ. Меньше байт на диске = меньше I/O = быстрее ответ.

2. Вчера опенсорс, сегодня компания за $15 млрд

Мне нравится эта история, потому что она про силу простой идеи. В 2009 году инженер-математик Алексей Миловидов в Яндексе затеял эксперимент: можно ли строить аналитические отчёты в реальном времени прямо из сырых, неагрегированных данных, которые к тому же постоянно доливаются. Три года он доказывал гипотезу — и в 2012-м ClickHouse пошёл в прод как движок Яндекс.Метрики, второй по величине системы веб-аналитики в мире.

В июне 2016-го проект открыли под лицензией Apache 2. В сентябре 2021-го из него выросла отдельная компания ClickHouse Inc. в Сан-Франциско (основатели — Aaron Katz, Alexey Milovidov и Yury Izrailevsky; стартовые $50 млн дали Index Ventures и Benchmark, среди участников был и Яндекс). А в январе 2026-го ClickHouse привлёк $400 млн при оценке $15 млрд (раунд Series D во главе с Dragoneer) — вдвое больше, чем годом раньше ($6,35 млрд в мае 2025). Всего компания подняла более $1 млрд.

2009
эксперимент в Яндексе
Алексей Миловидов
2016
open source, Apache 2
июнь
2021
компания ClickHouse Inc.
Сан-Франциско
$15 млрд
оценка 2026
Series D, Dragoneer

Мораль. «Секрет» ClickHouse — не хитрый алгоритм, а одна простая идея (колоночное хранение для аналитики), доведённая до совершенства и заточенная под скорость. Миловидов, кстати, до сих пор сооснователь и CTO — редкий случай, когда автор опенсорс-эксперимента остаётся техническим сердцем компании за $15 млрд.

3. Честно: это не замена PostgreSQL

Скажу прямо, потому что сам долго думал, что выбрать под свой агрегатор. Главное, что я понял: ClickHouse — это НЕ замена PostgreSQL, это другой инструмент под другую задачу. Это не «лучше/хуже», это «для разного».

PostgreSQL — OLTP

  • Для транзакций: заказы, платежи, регистрации.
  • Точечные вставки и апдейты, где важна консистентность каждой записи.
  • Достать/поправить одну строку целиком — это его стихия.
  • Строчное хранение — идеально для «одна операция = одна запись».

ClickHouse — OLAP

  • Для аналитики: дашборды, отчёты, поиск по истории.
  • Много чтения и агрегаций по сотням миллионов строк.
  • Полные сканы без предрасчёта и без индекса под каждый запрос.
  • Плохо подходит для частых точечных UPDATE/DELETE.

Разницу OLTP vs OLAP проще всего запомнить так: OLTP — это «много мелких операций над отдельными записями» (банкомат), OLAP — «одна тяжёлая операция над всей историей» (годовой отчёт). ClickHouse не нужен, если данных мало, и он не про частые точечные апдейты.

Вывод простой и честный: если данных реально много и нагрузка аналитическая — дашборды, отчёты, поиск по истории — пробуй ClickHouse, не пожалеешь. Для транзакций оставь PostgreSQL.
Мадияр Хамзанов, Data Engineering

4. Наша схема: 781 млн строк на одной VPS

Теперь как это выглядит у меня в бою. Мы парсим официальный API goszakup.gov.kz (плюс Самрук, казначейство, планы). Раньше сырьё было в PostgreSQL, и аналитика упиралась в полные сканы и тяжёлые GROUP BY. Сейчас всё в ClickHouse, организованном как многослойная (medallion) ELT-архитектура — сырьё очищается и превращается в витрины слой за слоем:

Слои medallion-архитектуры
СлойБазаЧто там
rawgoszakup_rawСырые выгрузки API как есть, дедуп через ReplacingMergeTree
stggoszakup_stgОчистка, типизация, нормализация
dmrtgoszakup_dmrtВитрины — широкие денормализованные таблицы
refsgoszakup_refs35 справочников (статусы, КАТО, ЕНС ТРУ, валюты)
aigoszakup_aiML-обогащение лотов (категоризация, маржа)

Ключевые таблицы — вот из чего складываются эти 781 млн строк и 192 ГиБ. Обратите внимание на движок: MergeTree для стабильных данных и ReplacingMergeTree там, где ETL постоянно доливает новые версии строк.

Ключевые таблицы
ТаблицаСтрокРазмерДвижок
goszakup_raw.plans_all42,0 млн11,3 ГиБReplacingMergeTree
goszakup_dmrt.lots31,4 млн5,9 ГиБMergeTree
goszakup_stg.trd_app52,5 млн4,4 ГиБMergeTree
goszakup_stg.contract_all14,4 млн4,5 ГиБReplacingMergeTree
goszakup_stg.treasury_pay11,5 млн2,8 ГиБReplacingMergeTree
goszakup_stg.acts32,7 млн2,0 ГиБMergeTree

Как ClickHouse заменил UPSERT-ы из PostgreSQL. Сырой слой на ReplacingMergeTree — это замена INSERT ... ON CONFLICT: ETL просто пишет новые версии строк по ключу, а ClickHouse сам схлопывает дубли при фоновом слиянии. Никаких блокировок и гонок на апдейтах — загрузка идемпотентна.

5. Железо и конфигурация

Никакого кластера. Одна машина. Вот вся правда о железе:

CPU
16 vCPU
AMD EPYC-Genoa @ 2.0 GHz
RAM
30 ГиБ
ClickHouse ограничен 18,4 ГиБ, остальное — под Python-ETL
Диск
600 ГБ SSD
ext4, занято 407 ГБ данными ClickHouse

ОС — Ubuntu 24.04 LTS, ClickHouse 25.12 под systemd, аптайм на момент замеров — 4 недели. Главный нюанс: сервер «горячий» — рядом на этой же машине молотит Python-ETL, который тоже хочет память. Поэтому я подрезал ClickHouse, чтобы он не съел всё под кэши и не подрался с ETL за RAM:

config.d/memory.xml
1<!-- /etc/clickhouse-server/config.d/memory.xml -->2<clickhouse>3    <!-- не забирать под сервер больше 60% RAM: рядом работает ETL -->4    <max_server_memory_usage_to_ram_ratio>0.6</max_server_memory_usage_to_ram_ratio>5 6    <!-- кэш засечек (primary index marks) -->7    <mark_cache_size>5368709120</mark_cache_size>        <!-- 5 ГиБ -->8 9    <!-- кэш распакованных блоков -->10    <uncompressed_cache_size>8589934592</uncompressed_cache_size>  <!-- 8 ГиБ -->11</clickhouse>
/etc/clickhouse-server/config.d/memory.xml

И ещё одна деталь, которая и даёт «полёт»: max_threads = auto(16). По умолчанию каждый запрос параллелится на все 16 ядер — один GROUP BY раскладывается на 16 потоков и молотит данные векторно, блоками. Отсюда и сотни миллионов строк в секунду на одной VPS.

6. Два козыря: сжатие и column pruning

Козырь №1 — сжатие 4.8×. 355 ГиБ логических данных превращаются в 74 ГиБ на диске. Колоночное хранение + кодек LZ4: значения одного типа лежат подряд и жмутся кратно лучше, чем перемешанные поля строки. Меньше читать с диска = быстрее запросы. Это ровно то, за что колоночные базы обгоняют строчные на аналитике.

МетрикаЛогическиНа диске
Объём данных ClickHouse355 ГиБ74 ГиБ
Коэффициент сжатия4.8×
КодекLZ4 (колоночный)

Козырь №2 — column pruning (читается только нужное). Один и тот же GROUP BY по таблице лотов (31,4 млн строк, 5,9 ГиБ), но с разным набором колонок:

2 из ~60 колонок
Суммы по годам (2 числовые колонки)
330 МиБ · 0,08 с
Запрос трогает только 2 колонки из ~60 — остальные с диска даже не читаются.
+ строка
Топ заказчиков (+ длинное строковое имя)
6,6 ГиБ · 0,46 с
Добавили в GROUP BY тяжёлую строковую колонку — и объём чтения вырос в 20 раз.

Строчный PostgreSQL в обоих случаях поднимал бы строки целиком — а значит, «дешёвый» запрос на 2 колонки стоил бы ему столько же, сколько «дорогой». В этом и вся разница.

7. Живые бенчмарки

Все замеры — на боевом сервере, во время работающего ETL. Никаких синтетических датасетов, только продовые таблицы. Начнём с классики — годовая динамика по 31,4 млн лотов:

ClickHouse — годовая динамика по лотам
1SELECT2    toYear(tender_publish_date_day) AS y,3    count()                         AS lots,4    round(sum(lot_amount) / 1e9, 1) AS bln_tenge5FROM goszakup_dmrt.lots6GROUP BY y7ORDER BY y DESC;8 9-- 2025: 3 869 168 лотов / 15 750 млрд ₸10-- 2024: 3 694 027 лотов / 15 910 млрд ₸11-- 2023: 3 910 433 лота  / 12 150 млрд ₸12-- ⏱ 0,08 с · 31,4 млн строк · 378 млн строк/сек · 3,9 ГиБ/сек

Дальше — топ-10 заказчиков по сумме лотов. Тут в GROUP BY уходит длинная строка (имя заказчика), поэтому чтение тяжелее, но всё равно доли секунды:

ClickHouse — топ-10 заказчиков
1SELECT2    customer_name_ru,3    round(sum(lot_amount) / 1e9, 0) AS bln_tenge4FROM goszakup_dmrt.lots5GROUP BY customer_name_ru6ORDER BY bln_tenge DESC7LIMIT 10;8 9-- АО «НК ҚазАвтоЖол» ................. 3 805 млрд ₸10-- КГУ «Управление строительства11--     г. Алматы» ..................... 2 538 млрд ₸12-- МЧС РК ............................. 1 584 млрд ₸13-- ⏱ 0,46 с · 31,4 млн строк · 69 млн строк/сек

А вот полнотекстовый поиск честным полным сканом — ILIKE '%компьютер%' по 31,4 млн названий лотов, без всякого индекса:

ClickHouse — полнотекстовый скан
1SELECT count()2FROM goszakup_dmrt.lots3WHERE lot_name_ru ILIKE '%компьютер%';4 5-- 191 310 совпадений6-- ⏱ 0,32 с · полный скан 31,4 млн строк · 98 млн строк/сек

Сводка по пяти боевым запросам — агрегация, top-N по строке, сумма платежей, JOIN двух больших таблиц и полнотекстовый скан:

ЗапросСтрокВремяСтрок/сек
Q1 · агрегация по годам31,4 млн0,08 с378 млн
Q2 · top-N по строке31,4 млн0,46 с69 млн
Q3 · sum платежей11,5 млн0,17 с66 млн
Q4 · JOIN 14,4М × 11,5М26 млн0,27 с95 млн
Q5 · ILIKE полный скан31,4 млн0,32 с98 млн
Пропускная способность

Пропускная способность по запросам

Млн строк в секунду на одной VPS (16 vCPU), боевые таблицы. Больше — лучше.

Q3 — сумма всех казначейских выплат (11,5 млн платежей → 43,9 трлн ₸) за 0,17 с. Q4 — JOIN 14,4 млн договоров с 11,5 млн платежей за 0,27 с.

Про «холодный» кэш. При самом первом запуске те же запросы шли ~1 секунду (данные ещё не в кэше). После прогрева — десятые доли секунды. Но и в том, и в другом случае это интерактивная аналитика без предрасчёта и без индексов под каждый запрос. Для сравнения: такой же полный скан и GROUP BY в строчной базе на этих объёмах тянулся бы секундами и минутами.

8. Почему это вообще работает — 4 причины

1
Колоночное хранение
Читаем 330 МиБ вместо 5,9 ГиБ — только нужные столбцы, а не строки целиком.
2
Векторизованное выполнение
Запрос идёт блоками на всех 16 ядрах → сотни млн строк/сек на одной машине.
3
Сжатие 4.8×
Меньше I/O, больше данных влезает в кэш — 355 ГиБ ужимаются до 74 ГиБ.
4
MergeTree под ELT
ReplacingMergeTree даёт идемпотентную загрузку без UPSERT-ов и блокировок.

Именно четвёртый пункт и был для меня главной причиной ухода с PostgreSQL в самом пайплайне: ReplacingMergeTree заменил всю возню с INSERT ... ON CONFLICT и блокировками на апдейтах. А общий выигрыш по скорости на аналитике для колоночных баз хорошо известен — они и считаются самыми быстрыми под аналитику именно за счёт трёх множителей: сжатие в 5–10 раз, сокращение I/O на 90%+ на широких таблицах и векторизованное выполнение.

9. Как повторить у себя — просто скопируй промпт

Не обязательно быть дата-инженером. Если у тебя есть данные в PostgreSQL (или обычные csv/excel-файлы) и тормозящая аналитика — просто открой Claude Code (или Cursor) в папке проекта и скопируй промпт ниже. Агент сам поставит ClickHouse, сам перельёт туда данные и покажет, стало ли быстрее. От тебя — один клик «Копировать» и один ответ на вопрос агента, где лежат данные.

Промпт для Claude Code / Cursor
1Ты — мой дата-инженер. Объясняй всё ПРОСТО, шаг за шагом, без жаргона.2Задача: ускорить мою аналитику, переехав на ClickHouse. Сделай всё сам.3 4ШАГ 0 — ДАННЫЕ.5Мои данные лежат здесь: <PostgreSQL-строка подключения ИЛИ путь к csv/parquet/xlsx>.6Если я не указал — поищи сам в текущей папке и стандартных местах (Desktop,7Downloads) csv/parquet/xlsx или локальную базу Postgres. Если ничего не нашёл —8просто спроси меня, где данные и что я обычно по ним считаю.9 10ШАГ 1 — ПОСТАВЬ CLICKHOUSE ЛОКАЛЬНО.11Установи ClickHouse самым простым способом для моей ОС12(на Linux/Mac: curl https://clickhouse.com/ | sh, затем ./clickhouse server;13на Windows — через Docker: docker run -d --name ch -p 8123:8123 -p 9000:900014clickhouse/clickhouse-server). Проверь, что сервер поднялся, и покажи мне команду15подключения (clickhouse-client).16 17ШАГ 2 — ПЕРЕЛЕЙ ДАННЫЕ.18Создай таблицу с движком MergeTree под мои данные (сам подбери типы колонок и19разумный ORDER BY — обычно это дата или id). Если данные обновляются по ключу —20используй ReplacingMergeTree вместо UPSERT. Залей мои данные в ClickHouse.21 22ШАГ 3 — ЗАМЕРЬ «БЫЛО / СТАЛО».23Возьми 3-4 моих типичных аналитических запроса (агрегации, GROUP BY, суммы по24периодам, топ-N, полнотекстовый поиск). Прогони их и в старой базе (если это25Postgres), и в ClickHouse. Замерь время каждого. Покажи размер данных на диске26до и после (у ClickHouse он будет в разы меньше из-за сжатия).27 28ШАГ 4 — ВЕРДИКT (просто и по-человечески):29  • Насколько быстрее стало (во сколько раз), таблицей «запрос / было / стало».30  • Сколько сэкономили места на диске.31  • Стоит ли мне переезжать на ClickHouse ПОЛНОСТЬЮ или держать его рядом с32    Postgres только под аналитику. Ответь прямо: ДА или НЕТ и почему.33 34ВАЖНО помни: ClickHouse — для аналитики (много чтения, отчёты, дашборды), а НЕ35для транзакций и частых точечных апдейтов — их оставь Postgres. Если данных мало36(меньше пары миллионов строк) — честно скажи, что ClickHouse мне пока не нужен.

Ничего не сломается: агент ставит ClickHouse отдельно и копирует данные, а не переносит — твой PostgreSQL остаётся на месте. Хочешь так же, как у меня — в проде на VPS — попроси агента поставить ClickHouse через официальный apt/systemd-способ и настроить max_server_memory_usage_to_ram_ratio под объём RAM сервера.

Итог

Одна VPS за копейки, 16 ядер, ноль шардов — и интерактивная аналитика по сотням миллионов строк госзакупок РК. Для аналитической нагрузки (много чтения, агрегации, полные сканы, редкие точечные апдейты) ClickHouse обходит классический PostgreSQL не на проценты, а в разы — и ещё экономит диск за счёт сжатия.

Но помню и повторю: это OLAP-инструмент. Для транзакций, точечных апдейтов и консистентности каждой записи оставляй PostgreSQL. А если данных реально много и нагрузка аналитическая — бери ClickHouse, накатывай MergeTree-схему слоями, и твоя аналитика тоже полетит. Проверено на 781 млн строк.