Часть 13. LogHouse: квадриллион строк в трёх облаках

431 ПиБ и 1.59 квадриллиона строк на 33 геошардах в AWS, Azure и GCP. Разбираем геошардинг, async inserts с 24-кратным батчингом, суточные партиции и трёхуровневую иерархию Distributed-таблиц.
Опубликовано:

Адаптивный пересказ статьи A Quadrillion Rows across three Clouds: scaling LogHouse из блога ClickHouse. Текст мой, цифры — из оригинала.

Полезность этой истории не в самих числах, а в том, что решения на таком масштабе видны выпукло: там, где на маленькой инсталляции ошибка стоит пару процентов, здесь она стоит остановки конвейера.

Порядок величин для контекста:

Объём (несжатый)431 ПиБ
Строк1.59 квадриллиона
Геошарды / ячейки33 / 36 в 30+ регионах
Пик приёма80 ГиБ/с, 190 млн строк/с
Сжатие16× (431 ПиБ → 27 ПиБ на диске)
Хранение180 дней (часть — 365)

Любопытная деталь: 78% объёма и 93% строк — это телеметрия из системных таблиц (тот самый SysEx из части 12), а не логи приложений.

Запись: геошардинг

Данные пишутся в том же регионе, где родились. Это не про производительность, а про деньги: межрегиональный трафик в облаках стоит дорого, и на таких объёмах он бы доминировал в счёте.

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

Async inserts и размер кусков

Проблема мелких вставок, разобранная в части 6, на таком потоке становится определяющей. Решение — серверный батчинг: ClickHouse копит строки у себя, пока не сработает порог.

Эффект измеримый:

Это ровно та разница между «слияния не успевают, ловим too many parts» и «система работает».

Суточные партиции вместо месячных

Контринтуитивный момент. В части 5 общая рекомендация — не дробить партиции слишком мелко, обычно toYYYYMM(ts). Здесь пошли в другую сторону — на сутки, и вот почему:

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

Надёжность приёма

Неудавшиеся батчи OTel сначала копятся в памяти, а при затяжном сбое сливаются в S3. Формулировка результата точная: это создаёт задержку приёма, а не потерю данных. Подробно эта схема разобрана в части 11.

Чтение: три уровня Distributed

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

  flowchart TD
    Q["Запрос"] --> L1["Уровень 1: глобальная Distributed<br/>все регионы"]
    L1 --> L2A["Уровень 2: региональная Distributed<br/>ячейки региона A"]
    L1 --> L2B["Уровень 2: региональная Distributed<br/>ячейки региона B"]
    L2A --> L3A[("Уровень 3: SharedMergeTree<br/>данные")]
    L2B --> L3B[("Уровень 3: SharedMergeTree<br/>данные")]

Ключ к производительности глобального уровня — optimize_skip_unused_shards вместе со словарём, отображающим регион в идентификатор шарда. Благодаря этому запрос с фильтром по региону не идёт в ненужные ячейки.

Результат:

Что забрать себе

Даже на инсталляции в тысячу раз меньше работают те же принципы:

  1. Держите запись локальной к источнику данных — межрегиональный трафик дорог и на средних объёмах.
  2. Async inserts не опция, а необходимость для потоковой телеметрии: смотрите на итоговый размер кусков, а не на число вставок.
  3. Гранулярность партиций подбирайте под TTL. Цель — чтобы удаление старого было DROP PARTITION, а число активных партиций оставалось в сотнях.
  4. Прячьте топологию за Distributed-иерархией и обязательно включайте отсечение ненужных шардов — иначе каждый запрос идёт во все ячейки.
  5. Сбой приёма должен превращаться в задержку, а не в потерю.

На этом серия завершается. Начало — в части 1, практические основы эксплуатации — в части 10.

Начать поиск

Введите ключевые слова для поиска статей

↑↓
ESC
⌘K Горячая клавиша