SereneDB

ClickHouse vs SereneDB: one engine for ranked search and analytics

ClickHouse is a columnar database whose text index, generally available since version 26.2, matches tokens and phrases but does not rank results by relevance. SereneDB is an Apache 2.0 database that ranks matches with BM25 and aggregates them in the same PostgreSQL-compatible SQL query, over its own tables or over ClickHouse tables where they already are.

SereneDB
26.5 ms
ClickHouse
349 ms

median hot query time · the 70 queries both finish

SearchBench/ 1B OpenTelemetry logs · 92 queries · SereneDB 26.09.1 vs ClickHouse 26.8.2.7
92 vs 70queries answered13×faster median query1.3×faster ingest
View Full Benchmark →
SereneDBgeomean 1.11×load 5m 10s
ClickHousegeomean 9.60×load 6m 41s
fastest2×4×8×+not supportedhover a cell to see the query
Why teams switch

Why teams look for a ClickHouse alternative

Teams look past ClickHouse when log search has to rank results rather than filter them, when applications expect PostgreSQL, or when rows change after they land. SereneDB ranks with BM25, joins and aggregates in one PostgreSQL-compatible SQL engine.

01

> Relevance ranking in SQL

SereneDB ranks matches with BM25, TF-IDF or a language-model scorer in the statement that filters them. ClickHouse's text index does not rank matches by relevance, so 21 of SearchBench's 92 queries, the BM25 top-k ones, cannot be written in ClickHouse SQL.

02

> Fuzzy, phrase, synonym and geo search from the index

SereneDB's inverted index answers phrases with gaps and a slop, edit distance, n-grams, regular expressions, wildcards, synonyms, boosts, minimum-should-match and geo shapes, and highlights what matched. ClickHouse 26.10's text index serves LIKE and prefix patterns but no edit distance or phrase slop, and skips blocks for a regular expression only when the pattern holds whole words.

03

> PostgreSQL SQL, updates and transactions

SereneDB speaks PostgreSQL's dialect and wire protocol, so psql, the PostgreSQL drivers and Grafana's PostgreSQL data source connect as they are, and UPDATE and DELETE run in multi-statement ACID transactions. ClickHouse accepts PostgreSQL wire-protocol connections too, but parses the queries as ClickHouse SQL by default, and its multi-statement transactions are experimental.

Side by side

SereneDB vs ClickHouse, feature by feature

Both store data in columns and index text. They differ in whether search results can be ranked, which SQL dialect you write, and how rows change after they land.

CapabilitySereneDBClickHouse
StorageYes: Columnar row groups with automatic min-max zonemaps, plus inverted indexes for searchYes: MergeTree columnar parts sorted by the ORDER BY key, with a sparse primary index and skip indexes
Full-text searchYes: Inverted index with per-column text analysis: tokenizers, stemming, stop words and synonymsYes: Text index, generally available since 26.2: hasToken, hasAnyTokens, hasAllTokens and hasPhrase, with tokenizers, a preprocessor and stemming
Relevance rankingYes: BM25, TF-IDF and language-model scorers in SQL, with top-k pruningNo: No relevance ranking: the text index announcement says it implements neither TF-IDF nor BM25
Phrase and proximityYes: ts_phrase with a slop: the words within N positionsPartly: hasPhrase (since 26.4) needs the words in order with nothing between them; no slop
Fuzzy, regex and prefixYes: Edit distance, regular expressions, prefixes and wildcards from the indexPartly: LIKE, startsWith and endsWith from the index dictionary; a regular expression skips blocks only on whole words; no index for edit distance
HighlightingYes: ts_highlight marks the tokens the query matched and picks the best-scoring snippetsPartly: highlight() wraps the terms you pass in tags across the whole text, matched ASCII case-insensitively
SynonymsYes: Solr and WordNet synonym lists expand words in the indexed text and in the queryPartly: An experimental synonyms() function looks up a word in a plain or WordNet synonym file
Vector and hybrid searchYes: HNSW and IVF in the same inverted index; a text match and a vector order in one queryPartly: HNSW vector similarity index, generally available since 25.8; no text relevance score to blend with distance
SQL dialect and clientsYes: PostgreSQL dialect and wire protocol: psql, the PostgreSQL drivers, Grafana's PostgreSQL data sourcePartly: ClickHouse SQL; a PostgreSQL wire-protocol interface accepts psql, and the queries are ClickHouse SQL by default
JoinsYes: Inner, outer, semi, anti and lateral joins across tables, indexes, files and attached databasesYes: INNER, OUTER, SEMI, ANTI, ANY and ASOF; outer-join misses get the type's default unless join_use_nulls = 1
Updates, deletes and transactionsYes: UPDATE, DELETE and multi-statement ACID transactions with snapshot isolationPartly: Lightweight DELETE, and lightweight UPDATE (generally available in 26.10) on tables with block-number columns; BEGIN and COMMIT experimental
Kafka ingestYes: Kafka Connect's PostgreSQL sinks write to it over the PostgreSQL wire protocol; also COPY, INSERT, OTLP over HTTP or a subscription to a Postgres publicationYes: Kafka table engine
OpenTelemetry ingestYes: A built-in OTLP/HTTP receiver writes logs, traces and metrics to search tablesYes: Through the OpenTelemetry Collector's ClickHouse exporter
Data in placeYes: Indexes files and tables where they are: Parquet, CSV, JSON and Iceberg on S3 or disk, attached ClickHouse and PostgreSQLYes: Queries S3, Iceberg, Delta Lake and PostgreSQL in place through table functions; a text index needs a MergeTree table
LicenseYes: Apache 2.0Yes: Apache 2.0

✓ supported · ~ supported with limits · ✕ not supported. ClickHouse facts from ClickHouse's documentation for 26.10 and runs on 26.10.2, SereneDB facts from its documentation and runs on 26.10.7. Last reviewed October 31, 2026.


Same question, two queries

The same question in ClickHouse SQL and SereneDB SQL

Each tab asks one question of the same logs in both dialects. The SereneDB SQL ran on SereneDB 26.10.7 and the ClickHouse SQL on ClickHouse 26.10.2, both on October 31, 2026.

Fuzzy match

ClickHouse — ClickHouse SQL
SELECT ts, service, message
FROM logs
WHERE arrayExists(
        t -> editDistance(t, 'conection') <= 1,
        splitByNonAlpha(lower(message)))
ORDER BY ts DESC
LIMIT 50;
SereneDB — SQL
SELECT ts, service, message
FROM logs
WHERE message @@ ts_levenshtein('conection', 1)
ORDER BY ts DESC
LIMIT 50;

ts_levenshtein('conection', 1) finds every word within one edit of the typo in the index's term dictionary. ClickHouse's text index does not serve edit distance, so its query compares every word of every row; EXPLAIN lists no skip index for it. Both return the same six rows.

BM25 ranking

ClickHouse — ClickHouse SQL
-- No relevance score: the text index finds
-- the rows, so they can only be sorted by a column
SELECT ts, service, message
FROM logs
WHERE hasAnyTokens(message, ['connection', 'reset', 'refused'])
ORDER BY ts DESC
LIMIT 10;
SereneDB — SQL
SELECT ts, service, message,
       BM25(logs.tableoid) AS score
FROM logs
WHERE message @@ ts_any(['connection', 'reset', 'refused'])
ORDER BY score DESC, ts DESC
LIMIT 10;

Both queries find the same seven rows. SereneDB orders them by BM25 relevance; ClickHouse has no score to order by, so its query sorts them by time.

Search ClickHouse in place

ClickHouse — the existing table
-- The table stays in ClickHouse as it is
CREATE TABLE logs (
    id      UInt64,
    ts      DateTime,
    service LowCardinality(String),
    level   LowCardinality(String),
    message String,
    INDEX message_idx message
        TYPE text(tokenizer = splitByNonAlpha,
                  preprocessor = lower(message))
) ENGINE = MergeTree
ORDER BY (service, ts);
SereneDB — SQL
CREATE SERVER ch FOREIGN DATA WRAPPER clickhouse_fdw
    OPTIONS (host 'clickhouse', port '9000');
CREATE VIEW ch_logs AS SELECT * FROM ch.default.logs;
CREATE TEXT SEARCH DICTIONARY words
    AS split_text(case := 'lower') WITH (frequency, position, norm);
CREATE INDEX ch_logs_idx ON ch_logs
    USING inverted (message words, service, ts)
    WITH (key_columns = 'id');

SELECT ts, service, message,
       BM25(ch_logs_idx.tableoid) AS score
FROM ch_logs_idx
WHERE message @@ ts_any(['connection', 'reset', 'refused'])
ORDER BY score DESC, ts DESC
LIMIT 10;

SereneDB reaches ClickHouse over its native protocol through a foreign server and indexes the table where it is: the rows stay in ClickHouse and SereneDB keeps the postings, keyed on the unique id column. The index is a snapshot; REINDEX INDEX ch_logs_idx or the reindex_interval option brings in new rows.


Migration

Move search off ClickHouse in three steps

Run SereneDB next to ClickHouse, search the ClickHouse tables where they are, compare the results on the same data, then move queries and dashboards. Indexing external data covers the statements.

  1. Start SereneDB

    Run it next to ClickHouse and connect with psql or any PostgreSQL driver.

    $ docker run -d --name serenedb -p 7890:7890 -e POSTGRES_PASSWORD=<password> serenedb/serenedb
  2. Connect ClickHouse and index it in place

    A foreign server reaches ClickHouse over its native protocol. Index a view of a table as in the last tab above, or copy the rows with CREATE TABLE … AS SELECT.

    $ psql -h localhost -p 7890 -U postgres -c "CREATE SERVER ch FOREIGN DATA WRAPPER clickhouse_fdw OPTIONS (host '<clickhouse-host>', port '9000', user '<user>', password '<password>')"
  3. Port queries and dashboards

    Rewrite hasToken, hasAnyTokens and hasAllTokens as @@ matches, add BM25 where results should be ranked, rebuild dashboards on Grafana's PostgreSQL data source, and move traffic once the results match.

    $ psql -h localhost -p 7890 -U postgres -c "SELECT count(*) FROM ch_logs_idx WHERE message @@ 'timeout'"

FAQ

Questions from ClickHouse users

Go to docs →

No. ClickHouse's text index, generally available since 26.2, matches tokens and phrases but does not rank them: the announcement of its general availability says it implements neither TF-IDF nor BM25, and 26.10 adds no relevance scoring. SereneDB ranks matches with BM25 or another scorer in SQL, for example ORDER BY BM25(logs.tableoid) DESC.

ClickHouse's text index filters rows but does not rank them by relevance, serves no edit-distance matching, and matches phrases only without a slop. Multi-statement transactions are experimental, and lightweight UPDATE, generally available since 26.10, needs the table's block-number columns enabled. SereneDB serves ranked, fuzzy, regex and phrase search from one inverted index and changes rows in ACID transactions.

SereneDB combines both: PostgreSQL's dialect, wire protocol and clients, with a columnar engine and BM25 search. On SearchBench's 92 log search and analytics queries over 1B logs, SereneDB 26.09.1 finished all 92 within 60 seconds, ClickHouse 26.8.2.7 finished 70, and PostgreSQL 18.4 with tsvector and a GIN index finished 12.

Yes. Lightweight DELETE hides rows that later merges remove, and lightweight UPDATE, generally available since 26.10, works on tables with block-number columns enabled and is meant for up to about 10% of a table. ALTER TABLE … UPDATE and DELETE are asynchronous mutations that rewrite data parts and cannot be rolled back, and multi-statement transactions are experimental. In SereneDB, UPDATE and DELETE run in ACID transactions with snapshot isolation, and the search index sees the change within about a second.

On SearchBench, SereneDB. Over 1B OpenTelemetry logs, SereneDB 26.09.1 had a 13× lower median hot query time than ClickHouse 26.8.2.7 on the 70 queries both finish (26.5 ms against 349 ms) and answered the 22 that ClickHouse cannot express. ClickHouse was faster on 5 of the 70, and on cold runs SereneDB's lead is 3.3×. ClickHouse 26.10 has not been measured yet.

Yes. SereneDB connects to ClickHouse over its native protocol with CREATE SERVER and builds an inverted index over a view of a ClickHouse table. The rows stay in ClickHouse, SereneDB stores the postings, and one SQL query ranks and aggregates the matches. The index is a snapshot that REINDEX or the reindex_interval option refreshes, keyed on a unique column named with key_columns.

Yes. SereneDB is licensed under Apache 2.0 and its source is on GitHub. The ClickHouse server is licensed under Apache 2.0 too.

SearchBench measured all three engines on the same 92 queries over 1B OpenTelemetry logs. SereneDB answered all 92 and runs the BM25 search and the aggregation in one SQL query; its median query time was 13× lower than ClickHouse 26.8.2.7 on the 70 queries both finish and 3.0× lower than Elasticsearch 9.5 on the 88 both finish. The Elasticsearch comparison covers SereneDB against Elasticsearch.