For the complete documentation index, see llms.txt. This page is also available as Markdown.
Flink
Batch ingestion of data into Apache Pinot using Apache Flink.
Apache Pinot supports using Apache Flink as a processing framework to generate and upload segments. The Pinot distribution includes a PinotSink that can be integrated into Flink applications (streaming or batch) to directly write data as segments into Pinot tables.
The PinotSink supports offline tables, realtime tables, and upsert tables (full upsert only). Data is buffered in memory and flushed as segments when the configured threshold is reached, then uploaded to the Pinot cluster.
Requirements
Flink 2.2.0 or later – Uses the new Flink 2.x Sink API. Java 21 support is included.
Java 11+ – Flink 2.x requires a minimum of Java 11.
Maven Dependency
To use the Pinot Flink Connector in your Flink job, add the following dependency to your pom.xml:
For standard realtime tables without upsert, use the same approach as offline tables, but specify REALTIME as the table type:
Upsert Tables
Full Upsert Tables
Flink connector supports backfilling full upsert tables where each record contains all columns. The uploaded segments will correctly participate in upsert semantics based on the comparison column value.
Requirements:
Partitioning: Data must be partitioned using the same strategy as the upstream stream (e.g., Kafka)
Parallelism: Flink job parallelism must match the number of upstream stream/table partitions
Comparison Column: The values of the comparison column must have ordering consistent with the upstream stream. This ensures that Pinot can correctly resolve which record is the latest for a given key. See Pinot upsert comparison column docs for important considerations.
Example:
How Partitioning Works:
When uploading segments for upsert tables, Pinot uses a special segment naming convention UploadedRealtimeSegmentName that encodes the partition ID. The format is:
Example: flink__myTable__0__1724045187__1
Each Flink subtask generates segments for a specific partition based on its subtask index. The segments are then assigned to the same server instances that handle that partition for stream-consumed segments, ensuring correct upsert behavior across all segments.
Configuration Options:
You can customize segment generation using additional constructor parameters:
Partial Upsert Tables
WARNING: Flink-based upload is not recommended for partial upsert tables.
In partial upsert tables, uploaded segments contain only a subset of columns or an intermdiate row for a primary key. If the uploaded row is not in its final state and subsequent updates arrive via the stream, the partial upsert merger may produce inconsistent results between replicas. This can lead to data inconsistency that is difficult to detect and resolve.
For partial upsert tables, prefer stream-based ingestion only or ensure uploaded data represents the final state for each primary key.
Advanced Configuration
Segment Flush Control
Control when segments are flushed and uploaded:
Segment Naming
Customize segment naming and upload time for better organization:
Migration from Flink 1.x
Important: The connector now requires Flink 2.2.0 or later and Java 11+. The old PinotSinkFunction (based on Flink 1.x SinkFunction API) is deprecated and does not work with Flink 2.x.
new PinotSink<>(
recordConverter,
tableConfig,
schema,
segmentFlushMaxNumRecords, // Default: 500,000, number of rows per segment
executorPoolSize, // Default: 5, number of threads to use to upload segment
segmentNamePrefix, // Default: "flink"
segmentUploadTimeMs // Default: current time, upload time value to encode in segment name
)
// Same setup as previous examples...
long segmentFlushMaxNumRecords = 1000000; // Flush after 1M records
int executorPoolSize = 10; // Thread pool size for async uploads
srcRows.sinkTo(new PinotSink<>(
new FlinkRowGenericRowConverter(typeInfo),
tableConfig,
schema,
segmentFlushMaxNumRecords,
executorPoolSize
));
// Same setup as previous examples...
String segmentNamePrefix = "flink_job_daily";
Long segmentUploadTimeMs = 1724045185000L; // Group segments by upload run time
srcRows.sinkTo(new PinotSink<>(
new FlinkRowGenericRowConverter(typeInfo),
tableConfig,
schema,
DEFAULT_SEGMENT_FLUSH_MAX_NUM_RECORDS,
DEFAULT_EXECUTOR_POOL_SIZE,
segmentNamePrefix,
segmentUploadTimeMs
));
// This no longer works with Flink 2.x
srcRows.addSink(new PinotSinkFunction<>(...));
// Use this for Flink 2.2.0 and later
srcRows.sinkTo(new PinotSink<>(...));