t1 in MongoDB to track customer orders:
t1 in ClickHouse will contain the following row:
Table schema
The replicated tables use this standard schema:_id: Primary key from MongoDBdoc: MongoDB document replicated as JSON data type_peerdb_synced_at: Records when the row was last synced_peerdb_version: Tracks the version of the row; incremented when the row is updated or deleted_peerdb_is_deleted: Marks whether the row is deleted
ReplacingMergeTree table engine
ClickPipes maps MongoDB collections into ClickHouse using theReplacingMergeTree table engine family. With this engine, updates are modeled as inserts with a newer version (_peerdb_version) of the document for a given primary key (_id), enabling efficient handling of updates, replaces, and deletes as versioned inserts.
ReplacingMergeTree clears out duplicates asynchronously in the background. To guarantee the absence of duplicates for the same row, use the FINAL modifier. For example:
Handling deletes
Deletes from MongoDB are propagated as new rows marked as deleted using the_peerdb_is_deleted column. You typically want to filter these out in your queries:
Querying JSON data
You can directly query JSON fields using dot syntax:Query
Result
^ operator:
Query
Result
Dynamic type
In ClickHouse, each field in JSON hasDynamic type. Dynamic type allows ClickHouse to store values of any type without knowing the type in advance. You can verify this with the toTypeName function:
Query
Result
dynamicType function. Note that it’s possible to have different data types for the same field name in different rows:
Query
Result
Query
Result
Query
Result
Query
Result
Field casting
Aggregation functions in ClickHouse don’t work with dynamic type directly. For example, if you attempt to directly use thesum function on a dynamic type, you get the following error:
CAST function or :: syntax:
Query
Result
Casting from dynamic type to the underlying data type (determined by
dynamicType) is very performant, as ClickHouse already stores the value in its underlying type internally.Flattening JSON
Normal view
You can create normal views on top of the JSON table to encapsulate flattening/casting/transformation logic in order to query data similar to a relational table. Normal views are lightweight as they only store the query itself, not the underlying data. For example:Refreshable materialized view
You can create Refreshable Materialized Views, which enable you to schedule query execution for deduplicating rows and storing the results in a flattened destination table. With each scheduled refresh, the destination table is replaced with the latest query results. The key advantage of this method is that the query using theFINAL keyword runs only once during the refresh, eliminating the need for subsequent queries on the destination table to use FINAL.
A drawback is that the data in the destination table is only as up-to-date as the most recent refresh. For many use cases, refresh intervals ranging from several minutes to a few hours provide a good balance between data freshness and query performance.
flattened_t1 directly without the FINAL modifier:
Incremental materialized view
If you want to access flattened columns in real-time, you can create Incremental Materialized Views. If your table has frequent updates, it’s not recommended to use theFINAL modifier in your materialized view as every update will trigger a merge. Instead, you can deduplicate the data at query time by building a normal view on top of the materialized view.
flattened_t1_final as follows: