Перейти к основному содержанию
Перейти к основному содержанию

Приложение

Postgres и ClickHouse: эквивалентные и отличающиеся концепции

Пользователям, приходящим из OLTP-систем и привыкшим к ACID-транзакциям, следует учитывать, что ClickHouse сознательно идет на определённые компромиссы и не обеспечивает их в полном объёме в обмен на производительность. При правильном понимании семантика ClickHouse может обеспечивать высокие гарантии надёжности хранения данных и высокую пропускную способность по записи. Ниже мы выделяем ключевые концепции, с которыми пользователям стоит ознакомиться до начала работы с ClickHouse после Postgres.

Шарды и реплики

Шардинг и репликация — это две стратегии масштабирования за пределы одного экземпляра Postgres, когда хранилище и/или вычислительные ресурсы становятся узким местом производительности. Шардинг в Postgres подразумевает разбиение большой базы данных на меньшие, более управляемые части, распределённые по нескольким узлам. Однако Postgres не поддерживает шардинг нативно. Вместо этого шардинг может быть реализован с помощью расширений, таких как Citus, при котором Postgres становится распределённой базой данных, способной масштабироваться горизонтально. Такой подход позволяет Postgres обрабатывать более высокие скорости транзакций и более крупные наборы данных за счёт распределения нагрузки между несколькими машинами. Шарды могут быть основаны на строках или на схемах, что обеспечивает гибкость для различных типов нагрузок, таких как транзакционные или аналитические. Шардинг может вносить значительную сложность в управление данными и выполнение запросов, так как требует координации между несколькими машинами и обеспечения согласованности.

В отличие от шардов, реплики — это дополнительные экземпляры Postgres, которые содержат все или часть данных из первичного узла. Реплики используются по разным причинам, включая повышение производительности чтения и сценарии высокой доступности (HA, High Availability). Физическая репликация — это нативная функция Postgres, которая включает копирование всей базы данных или значительных её частей на другой сервер, включая все базы данных, таблицы и индексы. Это предполагает потоковую передачу сегментов WAL с первичного узла на реплики по TCP/IP. Напротив, логическая репликация — это более высокий уровень абстракции, при котором изменения передаются на основе операций INSERT, UPDATE и DELETE. Хотя в итоге можно добиться тех же результатов, что и при физической репликации, логическая репликация обеспечивает большую гибкость при выборе конкретных таблиц и операций, а также при выполнении преобразований данных и поддержке разных версий Postgres.

В отличие от этого, шарды и реплики в ClickHouse — это две ключевые концепции, связанные с распределением данных и избыточностью. Реплики ClickHouse можно считать аналогичными репликам Postgres, хотя репликация в ClickHouse обеспечивает eventual consistency (постепенную согласованность) и не предполагает наличия первичного узла. В отличие от Postgres, шардинг поддерживается нативно.

Шард — это часть данных вашей таблицы. Как минимум у вас всегда есть один шард. Распределение данных по нескольким серверам (шардирование) можно использовать для разделения нагрузки, если вы превышаете возможности одного сервера; при этом все шарды участвуют в выполнении запроса параллельно. Пользователи могут вручную создавать шарды для таблицы на разных серверах и вставлять данные напрямую в них. В качестве альтернативы можно использовать распределённую таблицу, в которой шардирующий ключ определяет, к какому шару направляются данные. Шардирующий ключ может быть случайным или представлять собой результат хеш-функции. Важно, что шард может состоять из нескольких реплик.

Реплика — это копия ваших данных. В ClickHouse всегда есть как минимум одна копия ваших данных, поэтому минимальное количество реплик равно одному. Добавление второй реплики данных обеспечивает отказоустойчивость и потенциально даёт дополнительные вычислительные ресурсы для обработки большего количества запросов (Parallel Replicas также можно использовать для распределения вычислений для одного запроса, тем самым снижая задержки). Репликация реализуется с помощью движка таблиц ReplicatedMergeTree, который позволяет ClickHouse поддерживать несколько копий данных в синхронизированном состоянии на разных серверах. Репликация является физической: между узлами передаются только сжатые части данных (parts), а не запросы.

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

ClickHouse Cloud использует одну копию данных, размещённую в S3, с несколькими вычислительными репликами. Данные доступны каждому узлу-реплике, и у каждого есть локальный SSD-кэш. Это опирается только на репликацию метаданных через ClickHouse Keeper.

Итоговая согласованность

ClickHouse использует ClickHouse Keeper (реализацию ZooKeeper на C++, возможна также работа с самим ZooKeeper) для управления внутренним механизмом репликации, уделяя основное внимание хранению метаданных и обеспечению итоговой согласованности. Keeper используется для назначения уникальных последовательных номеров для каждой вставки в распределённой среде. Это критически важно для поддержания порядка и согласованности операций. Этот механизм также обрабатывает фоновые операции, такие как слияния и мутации, распределяя работу по ним и при этом гарантируя их выполнение в одном и том же порядке на всех репликах. Помимо метаданных, Keeper функционирует как полноценный центр управления репликацией, включая отслеживание контрольных сумм для хранимых частей данных, и выступает в роли распределённой системы уведомлений между репликами.

Процесс репликации в ClickHouse (1) начинается, когда данные вставляются в любую реплику. Эти данные в своём исходном виде (2) записываются на диск вместе с их контрольными суммами. После записи реплика (3) пытается зарегистрировать эту новую часть данных в Keeper, выделяя уникальный номер блока и записывая в лог сведения о новой части. Другие реплики, (4) обнаружив новые записи в логе репликации, (5) скачивают соответствующую часть данных через внутренний HTTP‑протокол, проверяя её по контрольным суммам, указанным в ZooKeeper. Этот метод гарантирует, что все реплики в итоге содержат согласованные и актуальные данные, несмотря на различную скорость обработки или возможные задержки. Кроме того, система способна обрабатывать несколько операций параллельно, оптимизируя процессы управления данными и обеспечивая масштабируемость системы и устойчивость к аппаратным различиям.

Итоговая согласованность

Обратите внимание, что ClickHouse Cloud использует оптимизированный для облака механизм репликации, адаптированный к архитектуре разделения хранилища и вычислений. Благодаря хранению данных в общем объектном хранилище данные автоматически доступны всем вычислительным узлам без необходимости физической репликации данных между узлами. Вместо этого Keeper используется только для обмена метаданными (какие данные и где находятся в объектном хранилище) между вычислительными узлами.

PostgreSQL использует иную стратегию репликации по сравнению с ClickHouse, в основном применяя потоковую репликацию, которая предполагает модель ведущего узла и узлов‑реплик, при которой данные непрерывно передаются с ведущего узла на один или несколько узлов‑реплик. Такой тип репликации обеспечивает практически актуальное состояние данных в реальном времени и может быть синхронным или асинхронным, предоставляя администраторам возможность балансировать между доступностью и согласованностью. В отличие от ClickHouse, PostgreSQL полагается на WAL (журнал предзаписи, Write-Ahead Logging) с логической репликацией и декодированием для потоковой передачи объектов данных и изменений между узлами. Такой подход в PostgreSQL более прост, но может не обеспечивать того же уровня масштабируемости и отказоустойчивости в высокораспределённых средах, которого ClickHouse достигает за счёт сложного использования Keeper для координации распределённых операций и обеспечения итоговой согласованности.

Последствия для пользователей

В ClickHouse возможность грязного чтения — когда пользователи записывают данные в одну реплику, а затем читают потенциально ещё не реплицированные данные с другой — возникает из‑за модели репликации с eventual consistency, управляемой через Keeper. Эта модель делает акцент на производительности и масштабируемости в распределённых системах, позволяя репликам работать независимо и синхронизироваться асинхронно. В результате недавно вставленные данные могут быть не сразу видны на всех репликах — это зависит от лага репликации и времени, необходимого для распространения изменений по системе.

Напротив, модель потоковой репликации PostgreSQL обычно позволяет предотвратить грязные чтения за счёт использования параметров синхронной репликации, при которых первичный узел ожидает, пока хотя бы одна реплика подтвердит получение данных перед фиксацией транзакции. Это гарантирует, что после фиксации транзакции есть гарантия, что данные доступны как минимум на одной реплике. В случае отказа первичного узла реплика обеспечит, что при выполнении запросов будут видны зафиксированные данные, тем самым поддерживая более строгий уровень согласованности.

Рекомендации

Пользователям, которые только начинают работать с ClickHouse, следует знать об отличиях, проявляющихся в реплицированных средах. Как правило, модель eventual consistency (гарантия окончательной согласованности) является достаточной для аналитики по миллиардам, а то и триллионам точек данных, где метрики либо достаточно стабильны, либо допускается использование оценок, поскольку новые данные непрерывно вставляются с высокой скоростью.

Существует несколько вариантов повышения согласованности при чтении, если это действительно необходимо. Оба варианта предполагают либо большую сложность, либо дополнительные накладные расходы — снижая производительность запросов и усложняя масштабирование ClickHouse. Мы рекомендуем использовать эти подходы только в случае крайней необходимости.

Консистентная маршрутизация

Чтобы преодолеть некоторые ограничения модели окончательной согласованности, можно настроить систему так, чтобы клиенты всегда направлялись на одни и те же реплики. Это полезно в случаях, когда несколько пользователей выполняют запросы к ClickHouse и результаты должны быть предсказуемыми и повторяемыми от запроса к запросу. Хотя результаты могут отличаться по мере вставки новых данных, должны опрашиваться одни и те же реплики, что обеспечивает последовательное представление данных.

Этого можно добиться несколькими способами в зависимости от вашей архитектуры и того, используете ли вы ClickHouse OSS или ClickHouse Cloud.

ClickHouse Cloud

ClickHouse Cloud использует одну копию данных в S3 с несколькими вычислительными репликами. Данные доступны каждому узлу‑реплике, у которого есть локальный SSD‑кэш. Для обеспечения согласованных результатов пользователям нужно лишь обеспечивать согласованную маршрутизацию запросов на один и тот же узел.

Взаимодействие с узлами сервиса ClickHouse Cloud происходит через прокси. Соединения по HTTP и native‑протоколу будут направляться на один и тот же узел на тот период, пока соединения остаются открытыми. В случае соединений HTTP 1.1 от большинства клиентов это зависит от окна Keep-Alive. Это может быть настроено в большинстве клиентов, например в Node.js. Также требуется серверная настройка, значение которой должно быть выше клиентского; в ClickHouse Cloud оно установлено в 10 с.

Чтобы обеспечить согласованную маршрутизацию между соединениями, например при использовании пула соединений или при истечении срока их действия, пользователи могут либо гарантировать использование одного и того же соединения (проще для native‑протокола), либо запросить предоставление sticky endpoints. Это обеспечивает набор эндпоинтов для каждого узла в кластере, что позволяет клиентам добиваться детерминированной маршрутизации запросов.

Обратитесь в службу поддержки для получения доступа к sticky endpoints.

ClickHouse OSS

Достижение такого поведения в OSS зависит от топологии ваших шардов и реплик, а также от того, используете ли вы Distributed table для выполнения запросов.

Когда у вас только один шард и реплики (распространено, так как ClickHouse масштабируется вертикально), пользователи выбирают узел на уровне клиента и обращаются напрямую к реплике, обеспечивая детерминированный выбор.

Хотя топологии с несколькими шардами и репликами возможны и без Distributed table, такие продвинутые развертывания обычно имеют собственную маршрутизационную инфраструктуру. Поэтому мы предполагаем, что развертывания с более чем одним шардом используют Distributed table (distributed tables могут использоваться и с одношардовыми развертываниями, но обычно в этом нет необходимости).

В этом случае пользователи должны обеспечить консистентную маршрутизацию запросов на узлы на основе некоторого свойства, например session_id или user_id. Настройки prefer_localhost_replica=0, load_balancing=in_order должны быть заданы в запросе. Это гарантирует, что любые локальные реплики шардов будут использоваться в приоритетном порядке, а остальные реплики будут выбираться в порядке, указанном в конфигурации, при условии, что у них одинаковое количество ошибок; при большем числе ошибок будет происходить отказоустойчивое переключение с использованием случайного выбора. В качестве альтернативы для такого детерминированного выбора шарда также может использоваться load_balancing=nearest_hostname.

При создании Distributed table пользователи указывают кластер. Это определение кластера, задаваемое в config.xml, перечисляет шарды (и их реплики), что позволяет пользователям контролировать порядок их использования с каждого узла. Благодаря этому пользователи могут обеспечить детерминированный выбор.

Последовательная согласованность

В исключительных случаях пользователям может потребоваться последовательная согласованность.

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

С точки зрения пользователя это обычно проявляется как необходимость записать данные в ClickHouse и при чтении данных гарантировать, что будут возвращены самые недавно вставленные строки. Этого можно добиться несколькими способами (в порядке предпочтения):

  1. Чтение/запись на один и тот же узел — Если вы используете нативный протокол или сеанс для выполнения записи/чтения через HTTP, вы должны быть подключены к одной и той же реплике: в этом случае вы читаете непосредственно с узла, на который выполняете запись, и тогда чтение всегда будет согласованным.
  2. Ручная синхронизация реплик — Если вы записываете на одну реплику, а читаете с другой, вы можете выполнить SYSTEM SYNC REPLICA LIGHTWEIGHT перед чтением.
  3. Включить последовательную согласованность — с помощью настройки запроса select_sequential_consistency = 1. В OSS также должна быть указана настройка insert_quorum = 'auto'.

Подробности по включению этих настроек см. здесь.

Использование последовательной согласованности приводит к повышенной нагрузке на ClickHouse Keeper. В результате операции вставки и чтения могут выполняться медленнее. В SharedMergeTree, используемом в ClickHouse Cloud в качестве основного движка таблиц, последовательная согласованность создаёт меньшие накладные расходы и масштабируется лучше. Пользователям OSS следует применять этот подход с осторожностью и измерять нагрузку на Keeper.

Транзакционная поддержка (ACID)

Пользователи, мигрирующие с PostgreSQL, могли привыкнуть к его надежной поддержке свойств ACID (Atomicity, Consistency, Isolation, Durability), что делает его устойчивым вариантом для транзакционных баз данных. Атомарность в PostgreSQL гарантирует, что каждая транзакция рассматривается как единое целое, которое либо полностью выполняется, либо полностью откатывается, предотвращая частичные обновления. Согласованность обеспечивается за счет применения ограничений, триггеров и правил, которые гарантируют, что все транзакции с базой данных приводят ее к корректному состоянию. В PostgreSQL поддерживаются уровни изоляции от Read Committed до Serializable, что позволяет тонко управлять видимостью изменений, вносимых параллельными транзакциями. Наконец, долговечность (Durability) достигается с помощью механизма write-ahead logging (WAL), который гарантирует, что после фиксации транзакции она сохраняется даже в случае сбоя системы.

Эти свойства типичны для OLTP-баз данных, выступающих в роли единого источника истины.

Несмотря на мощь такого подхода, он имеет присущие ему ограничения и затрудняет масштабирование до петабайтных (PB) объемов данных. ClickHouse идет на компромисс по отношению к этим свойствам, чтобы обеспечить быстрые аналитические запросы в масштабе при сохранении высокой пропускной способности записей.

ClickHouse обеспечивает свойства ACID при ограниченных конфигурациях — в самом простом случае при использовании нереплицированного экземпляра движка таблиц MergeTree с одним разделом. Пользователям не следует ожидать наличия этих свойств вне этих случаев и необходимо убедиться, что они не являются обязательным требованием.

Сжатие

Колоночное хранилище ClickHouse означает, что степень сжатия часто будет значительно выше по сравнению с Postgres. Ниже показано, как отличаются требования к хранилищу для всех таблиц Stack Overflow в обеих базах данных:

SELECT
    schemaname,
    tablename,
    pg_total_relation_size(schemaname || '.' || tablename) AS total_size_bytes,
    pg_total_relation_size(schemaname || '.' || tablename) / (1024 * 1024 * 1024) AS total_size_gb
FROM
    pg_tables s
WHERE
    schemaname = 'public';
SELECT
        `table`,
        formatReadableSize(sum(data_compressed_bytes)) AS compressed_size
FROM system.parts
WHERE (database = 'stackoverflow') AND active
GROUP BY `table`
┌─table───────┬─compressed_size─┐
│ posts       │ 25.17 GiB       │
│ users       │ 846.57 MiB      │
│ badges      │ 513.13 MiB      │
│ comments    │ 7.11 GiB        │
│ votes       │ 1.28 GiB        │
│ posthistory │ 40.44 GiB       │
│ postlinks   │ 79.22 MiB       │
└─────────────┴─────────────────┘

Дополнительную информацию об оптимизации сжатия и оценке его эффективности можно найти здесь.

Сопоставление типов данных

В следующей таблице показаны эквивалентные типы данных ClickHouse для типов Postgres.

Тип данных PostgresТип ClickHouse
DATEDate
TIMESTAMPDateTime
REALFloat32
DOUBLEFloat64
DECIMAL, NUMERICDecimal
SMALLINTInt16
INTEGERInt32
BIGINTInt64
SERIALUInt32
BIGSERIALUInt64
TEXT, CHAR, BPCHARString
INTEGERNullable(Int32)
ARRAYArray
FLOAT4Float32
BOOLEANBool
VARCHARString
BITString
BIT VARYINGString
BYTEAString
NUMERICDecimal
GEOGRAPHYPoint, Ring, Polygon, MultiPolygon
GEOMETRYPoint, Ring, Polygon, MultiPolygon
INETIPv4, IPv6
MACADDRString
CIDRString
HSTOREMap(K, V), Map(K, Variant)
UUIDUUID
ARRAY<T>ARRAY(T)
JSON*String, Variant, Nested, Tuple
JSONBString

* Продакшн-поддержка JSON в ClickHouse находится в разработке. В настоящее время пользователи могут либо сопоставлять JSON с типом String и использовать JSON-функции, либо сопоставлять JSON напрямую с Tuple и Nested, если структура предсказуема. Подробнее о JSON читайте здесь.