Сначала цифры. Таблица events в PostgreSQL 17, 600 млн строк, растёт на 40 млн в месяц. Сама таблица 210 ГБ, индексы ещё 140. Запросы почти всегда за последние 30 дней по одному аккаунту, p95 сейчас 380 мс, полгода назад было 90. Автовакуум по ней идёт почти без перерыва. Данные старше года нужны только для годового отчёта. Вижу два пути: партиционирование по месяцам в самом Postgres или увозить события в ClickHouse, он у нас уже стоит для аналитики. Кто делал первое на живой таблице такого размера и сколько это заняло?
Если события пишутся один раз и потом читаются только агрегатами, это прямо задача для ClickHouse. Месяц таких событий там сжимается в единицы гигабайт, выборка по аккаунту за 30 дней занимает десятки миллисекунд. Но если из Rails по ним нужны точечные выборки с джойнами к основным таблицам, будет больно.
Партиционирование на живой таблице делается без простоя, но не одним ALTER. Создаёте новую партиционированную таблицу, пишете в обе, переносите историю пачками по месяцам, потом переключаете чтение и меняете имена местами. Новые партиции создавайте заранее кроном, иначе первого числа вставки упадут. Начало примерно такое:
CREATE TABLE events_new (LIKE events INCLUDING DEFAULTS)
PARTITION BY RANGE (created_at);
CREATE TABLE events_2026_03 PARTITION OF events_new
FOR VALUES FROM ('2026-03-01') TO ('2026-04-01');
Первичный ключ придётся сделать составным, с created_at, это ограничение партиционирования. Rails с 7.1 составные ключи понимает, но все find по одному id придётся пересмотреть. Зато старые месяцы потом уходят через DETACH и DROP за секунду, а не DELETE на сутки.
Сделали гибрид. В Postgres партиции по месяцам, держим 13 месяцев, всё старше раз в месяц уезжает в ClickHouse и отцепляется. Перенос истории шёл 9 ночей, по месяцу-полтора за ночь, в самую тяжёлую реплика отставала на 40 секунд, не больше. Итог: p95 по аккаунту за 30 дней 38 мс вместо 380. Основная таблица теперь 75 ГБ вместо 210, автовакуум по свежей партиции укладывается в минуты. Годовой отчёт в ClickHouse строится за 4 секунды, раньше его запускали на ночь. Из неприятного: два места в коде делали find по id без даты, нашли по ошибкам на стейдже до прода.