Pick a path
A frequent wrong turn: the Debezium message decoder does not parse MongoDB envelopes. Its
dbz.source accepts only mysql and postgres. Ingest Debezium’s MongoDB output with the plain JSON decoder instead — see Debezium into Kafka.Direct Change Streams (CDC)
The StarTree MongoDB CDC connector opens MongoDB Change Streams straight from Pinot servers. No Kafka, Kinesis, or Pulsar sits in between.What you need first
- MongoDB Atlas, a replica set, or a sharded cluster. Standalone
mongodprocesses do not serve Change Streams. - MongoDB Server 6.0 or later if you want a current-state upsert table, because that mode requires Change Stream post-images.
- Network reachability and credentials on both Pinot controllers and servers. The identity needs
findandchangeStreamon every source namespace. - An oplog retention window longer than your worst-case Pinot outage. The oplog is the only replay buffer. If it ages out, recovery means rebuilding the table from a snapshot, not resuming.
- A partition plan. This is the part teams underestimate — read the next section before anything else.
The partition plan is the real design work
MongoDB Change Streams have no Kafka-style durable partitions, so the connector cannot discover them. You supply a manifest of physical, disjoint collections, and each one becomes one Pinot partition group:_id has to land in the same collection for that key’s lifetime. A common way to guarantee that is to encode the partition into the immutable _id at creation time:
hash(_id) % currentPartitionCount. Growing the count moves existing keys and breaks upsert partition affinity.
Cursor load multiplies. Each Pinot replica opens its own cursor per partition, so plan for source partitions x Pinot replicas. On a sharded deployment, a cursor opened through mongos fans out to every shard regardless of shard key, so plan closer to source partitions x Pinot replicas x MongoDB shards.
Minimum stream config
stream.mongodb.topic.name is only a logical stream name — it does not identify a MongoDB namespace. Prefer connectionStringEnvironmentVariable or a connectionRef over an inline connectionString, because table configs can be logged or exposed.
Pick an output mode:
-
change_stream(default) keeps the lossless MongoDB event envelope withfullDocumentnested inside it. -
full_documentlifts document fields to the top level and adds reserved__mongodb_cdc_*metadata columns. Use this for a current-state upsert table. It needsfullDocument=requiredand post-images enabled on every source collection:
Debezium into Kafka
If Debezium already streams your MongoDB collections into Kafka, ingest that topic as a normal Kafka stream. The one thing to get right is the decoder. The StarTree Debezium message decoder handles MySQL and Postgres only. For MongoDB, useJSONMessageDecoder and unpack the envelope with ingestion transforms.
Debezium’s MongoDB connector puts the changed document in after as a JSON string, not a nested object, so read fields out of it with jsonPathString:
ExtractNewDocumentState SMT lifts the document fields to the top level of the value, but it only preserves the deleted document’s _id if you ask it to — both of the delete.tombstone.handling.mode settings below are required:
rewrite keeps deletes as real records carrying a __deleted boolean instead of emitting null-valued tombstones, and rewrite-with-id copies id from the record key into the payload as _id. Without both, a delete still reaches Pinot with no primary key and cannot remove the existing row.
Records then arrive as flat documents, so JSONMessageDecoder needs no jsonPath transforms at all: _id becomes a normal column you can name in primaryKeyColumns, __deleted is the deleteRecordColumn, and __ts_ms serves as the upsert comparison column. add.fields prefixes what it adds with __ by default, configurable via add.fields.prefix.
Also set the connector’s capture.mode to a full-document mode such as change_streams_update_full, so updates carry the whole document rather than a partial update description. Without it, updates arrive as patches that Pinot cannot merge.
mongodump BSON files
Apache Pinot reads BSON natively, so mongodump output loads without conversion. Use this for backfills, periodic snapshots, or any table that does not need to be live.
Dump a collection and stage the files where your ingestion job can read them:
className is optional — Pinot resolves dataFormat: 'bson' to BSONRecordReader on its own — but naming it explicitly makes the spec self-documenting.
How BSON types land in Pinot
Because
Timestamp loses sub-second ordering, do not use an oplog ts field as an upsert comparison column.

