Postgres и ClickHouse: сходные и различающиеся концепции
Пользователям, переходящим с OLTP-систем и привыкшим к ACID-транзакциям, следует учитывать, что ClickHouse сознательно идет на компромиссы и не поддерживает их в полном объеме ради производительности. При правильном понимании семантики ClickHouse может обеспечивать высокую надежность хранения данных и высокую пропускную способность записи. Ниже мы выделим несколько ключевых концепций, с которыми стоит ознакомиться перед началом работы с ClickHouse после Postgres.
Сегментирование и репликация — две стратегии масштабирования за пределы одного экземпляра Postgres, когда хранилище и/или вычислительные ресурсы становятся узким местом по производительности. Сегментирование в Postgres предполагает разделение большой базы данных на более мелкие и управляемые части, распределенные по нескольким узлам. Однако Postgres не поддерживает сегментирование нативно. Вместо этого его можно реализовать с помощью расширений, таких как Citus, при использовании которых Postgres становится распределенной базой данных, способной масштабироваться горизонтально. Такой подход позволяет Postgres обрабатывать более высокую частоту транзакций и более крупные наборы данных, распределяя нагрузку между несколькими машинами. Сегменты могут быть организованы на уровне строк или схемы, что обеспечивает гибкость для разных типов рабочих нагрузок, например транзакционных или аналитических. Сегментирование может существенно усложнять управление данными и выполнение запросов, поскольку требует координации между несколькими машинами и гарантий согласованности.
В отличие от сегментов, реплики — это дополнительные экземпляры Postgres, которые содержат все данные с основного узла или их часть. Реплики используются по разным причинам, включая повышение производительности чтения и сценарии HA (высокой доступности). Физическая репликация — это нативная возможность Postgres, которая предполагает копирование всей базы данных или значительной ее части на другой сервер, включая все базы данных, таблицы и индексы. Для этого по TCP/IP с основного узла на реплики передаются сегменты WAL. В отличие от нее, логическая репликация — это более высокий уровень абстракции, при котором передаются изменения на основе операций INSERT, UPDATE и DELETE. Хотя схожих результатов можно добиться и с помощью физической репликации, логическая репликация дает больше гибкости при выборе конкретных таблиц и операций, а также при преобразовании данных и поддержке разных версий Postgres.
В ClickHouse же сегменты и реплики — это два ключевых понятия, связанных с распределением данных и избыточностью. Реплики ClickHouse можно считать аналогом реплик Postgres, хотя репликация здесь не является строго синхронной и не предполагает основного узла. Сегментирование, в отличие от Postgres, поддерживается нативно.
Сегмент — это часть данных вашей таблицы. У вас всегда есть как минимум один сегмент. Сегментирование данных по нескольким серверам можно использовать для распределения нагрузки, если объем данных или вычислений превышает возможности одного сервера: в этом случае все сегменты используются для параллельного выполнения запроса. Вы можете вручную создавать сегменты для таблицы на разных серверах и вставлять данные непосредственно в них. В качестве альтернативы можно использовать distributed таблицу с ключом сегментирования, который определяет, в какой сегмент направляются данные. Ключ сегментирования может быть случайным или представлять собой результат работы хеш-функции. Важно, что сегмент может состоять из нескольких реплик.
Реплика — это копия ваших данных. В ClickHouse всегда есть как минимум одна копия ваших данных, поэтому минимальное число реплик равно одному. Добавление второй реплики ваших данных обеспечивает отказоустойчивость и потенциально дополнительные вычислительные ресурсы для обработки большего числа запросов (Parallel Replicas также можно использовать, чтобы распределить вычисления одного запроса и тем самым снизить задержку). Реплики реализуются с помощью движка таблицы ReplicatedMergeTree, который позволяет ClickHouse поддерживать синхронизацию нескольких копий данных на разных серверах. Репликация является физической: между узлами передаются только сжатые части, а не запросы.
Итак, реплика — это копия данных, которая обеспечивает избыточность и надежность (а также потенциально распределенную обработку), тогда как сегмент — это подмножество данных, которое позволяет реализовать распределенную обработку и балансировку нагрузки.
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 возможность грязного чтения — когда данные записываются в одну реплику, а затем потенциально ещё не реплицированные данные читаются из другой — возникает из-за модели репликации с согласованностью в конечном счёте, управляемой через Keeper. Эта модель делает упор на производительность и масштабируемость в распределённых системах, позволяя репликам работать независимо и синхронизироваться асинхронно. В результате недавно вставленные данные могут быть видны не сразу на всех репликах — в зависимости от задержки репликации и времени, которое требуется для распространения изменений по системе.
Напротив, модель потоковой репликации PostgreSQL обычно позволяет предотвращать грязное чтение за счёт вариантов синхронной репликации, при которых основной узел ждёт, пока как минимум одна реплика не подтвердит получение данных, прежде чем зафиксировать транзакцию. Это гарантирует, что после фиксации транзакции данные доступны как минимум ещё на одной реплике. В случае сбоя основного узла реплика обеспечит, что запросы будут видеть зафиксированные данные, тем самым поддерживая более строгий уровень согласованности.
Пользователям, которые только начинают работать с ClickHouse, следует учитывать эти различия, поскольку в средах с репликацией они будут заметны. Обычно модели согласованности в конечном счёте достаточно для аналитики по миллиардам, если не триллионам, точек данных — там, где метрики относительно стабильны или достаточно приблизительных оценок, поскольку новые данные постоянно вставляются с высокой скоростью.
При необходимости можно повысить согласованность чтений несколькими способами. Оба варианта приводят либо к усложнению системы, либо к дополнительным накладным расходам, что снижает производительность запросов и затрудняет масштабирование ClickHouse. Мы рекомендуем использовать эти подходы только в случае крайней необходимости.
Согласованная маршрутизация
Чтобы обойти некоторые ограничения согласованности в конечном счёте, можно направлять клиентов к одним и тем же репликам. Это полезно, когда несколько пользователей выполняют запросы к ClickHouse и результаты должны быть детерминированными от запроса к запросу. Хотя по мере вставки новых данных результаты могут меняться, обращение к одним и тем же репликам обеспечивает согласованное представление данных.
Этого можно добиться несколькими способами — в зависимости от вашей архитектуры и от того, используете ли вы ClickHouse OSS или ClickHouse Cloud.
ClickHouse Cloud использует одну копию данных в S3 с несколькими вычислительными репликами. Данные доступны каждому узлу-реплике, у которого есть локальный кэш на SSD. Поэтому для согласованных результатов пользователям достаточно обеспечить стабильную маршрутизацию на один и тот же узел.
Обращения к узлам сервиса ClickHouse Cloud проходят через прокси. HTTP-соединения и соединения по собственному протоколу маршрутизируются на один и тот же узел в течение всего времени, пока они остаются открытыми. В случае HTTP 1.1-соединений для большинства клиентов это зависит от окна Keep-Alive. Это можно настроить в большинстве клиентов, например в Node.js. Для этого также требуется настройка на стороне сервера: её значение должно быть выше, чем на стороне клиента, и в ClickHouse Cloud оно установлено на 10 с.
Чтобы обеспечить согласованную маршрутизацию между соединениями, например при использовании пула соединений или если соединения закрываются, вы можете либо использовать одно и то же соединение (что проще для собственного протокола), либо запросить доступ к sticky endpoints. Это даёт набор конечных точек для каждого узла в кластере, позволяя клиентам гарантировать детерминированную маршрутизацию запросов.
Обратитесь в службу поддержки, чтобы получить доступ к sticky endpoints.
В OSS достижение такого поведения зависит от топологии ваших сегментов и реплик, а также от того, используете ли вы для выполнения запросов distributed таблицу.
Если у вас только один сегмент и реплики (что встречается часто, поскольку ClickHouse обычно масштабируется вертикально), пользователи выбирают узел на уровне клиента и отправляют запрос напрямую в реплику, обеспечивая детерминированный выбор.
Хотя топологии с несколькими сегментами и репликами возможны и без distributed таблицы, в таких продвинутых развертываниях обычно есть собственная инфраструктура маршрутизации. Поэтому мы исходим из того, что развертывания более чем с одним сегментом используют distributed таблицу (distributed таблицы можно использовать и в развертываниях с одним сегментом, но обычно в этом нет необходимости).
В этом случае следует обеспечить согласованную маршрутизацию узлов на основе свойства, например session_id или user_id. Настройки prefer_localhost_replica=0, load_balancing=in_order должны быть заданы в запросе. Это гарантирует, что предпочтение будет отдаваться локальным репликам сегментов, а в остальных случаях реплики будут выбираться в порядке, указанном в конфигурации, — при условии, что у них одинаковое число ошибок; если ошибок больше, произойдет переключение с случайным выбором. В качестве альтернативы для такого детерминированного выбора сегмента также можно использовать load_balancing=nearest_hostname.
При создании distributed таблицы вы укажете кластер. Это определение кластера, заданное в config.xml, будет содержать список сегментов (и их реплик), что позволит пользователям управлять порядком их использования с каждого узла. Так можно обеспечить детерминированный выбор.
Последовательная согласованность
В редких случаях вам может потребоваться последовательная согласованность.
Последовательная согласованность в базах данных означает, что операции над базой данных выглядят так, как будто они выполняются в некотором последовательном порядке, и этот порядок одинаков для всех процессов, взаимодействующих с базой данных. Это означает, что каждая операция как будто вступает в силу мгновенно — в промежутке между её вызовом и завершением, — и существует единый согласованный порядок, в котором все процессы наблюдают эти операции.
С точки зрения пользователя это обычно означает необходимость записывать данные в ClickHouse и при чтении гарантированно получать последние вставленные строки.
Этого можно добиться несколькими способами (в порядке предпочтения):
- Читать/писать в один и тот же узел - Если вы используете собственный протокол или сеанс для записи/чтения по HTTP, то должны быть подключены к одной и той же реплике: в этом случае вы читаете напрямую с того же узла, в который выполняете запись, поэтому чтение всегда будет согласованным.
- Синхронизировать реплики вручную - Если вы пишете в одну реплику, а читаете из другой, перед чтением можно выполнить
SYSTEM SYNC REPLICA LIGHTWEIGHT.
- Включить последовательную согласованность - с помощью настройки запроса
select_sequential_consistency = 1. В OSS также необходимо указать настройку insert_quorum = 'auto'.
Подробнее о включении этих настроек см. здесь.
Использование последовательной согласованности создаёт дополнительную нагрузку на ClickHouse Keeper. В результате
это может привести к замедлению вставок и чтения. SharedMergeTree, используемый в ClickHouse Cloud как основной движок таблицы, при последовательной согласованности создаёт меньше накладных расходов и лучше масштабируется. В OSS этот подход следует использовать с осторожностью и отслеживать нагрузку на Keeper.
Поддержка транзакций (ACID)
Пользователи, переходящие с PostgreSQL, как правило, привыкли к его надежной поддержке свойств ACID (атомарность, согласованность, изоляция, долговечность), что делает его надежным выбором для транзакционных баз данных. Атомарность в PostgreSQL означает, что каждая транзакция рассматривается как единое целое: она либо полностью выполняется, либо полностью откатывается, без частичных обновлений. Согласованность обеспечивается за счет ограничений, триггеров и правил, гарантирующих, что все транзакции переводят базу данных в корректное состояние. PostgreSQL поддерживает уровни изоляции от Read Committed до Serializable, что позволяет точно управлять видимостью изменений, внесенных параллельными транзакциями. Наконец, долговечность достигается с помощью упреждающего журналирования (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 |
|---|
DATE | Date |
TIMESTAMP | DateTime |
REAL | Float32 |
DOUBLE | Float64 |
DECIMAL, NUMERIC | Decimal |
SMALLINT | Int16 |
INTEGER | Int32 |
BIGINT | Int64 |
SERIAL | UInt32 |
BIGSERIAL | UInt64 |
TEXT, CHAR, BPCHAR | String |
INTEGER | Nullable(Int32) |
ARRAY | Array |
FLOAT4 | Float32 |
BOOLEAN | Bool |
VARCHAR | String |
BIT | String |
BIT VARYING | String |
BYTEA | String |
NUMERIC | Decimal |
GEOGRAPHY | Point, Ring, Polygon, MultiPolygon |
GEOMETRY | Point, Ring, Polygon, MultiPolygon |
INET | IPv4, IPv6 |
MACADDR | String |
CIDR | String |
HSTORE | Map(K, V), Map(K,Variant) |
UUID | UUID |
ARRAY<T> | ARRAY(T) |
JSON | String, Variant, Nested, Tuple |
JSONB | String |
Последнее изменение 10 июня 2026 г.