Badhusha Muhammed
04/15/2026, 9:14 AMRicha Kumari
04/15/2026, 1:55 PMRicha Kumari
04/16/2026, 9:55 AMSoon
04/17/2026, 6:00 PMWHERE TEXT_MATCH(indexedString3, '/.*nav#primary-menu.*/') and its not matching a row with nav#primary-menu. My theory this is due to that special character # and that standard analyzer breaks them up into tokens like nav and primary-menu and never keep them as a whole. Would be greatly helpful if someone could confirm my theory! Thanks in advance!Shubham
04/29/2026, 6:18 AMpiby
04/30/2026, 9:16 AMWHERE my_timestamp_col > '2025-11-02T00:00:00+01:00‘
This syntax works but it takes a very long time. I am assuming either Pinot is implicitly casting my_timestamp_col to string and doing string comparison or indexes are getting ignored due to type mismatch.
When we convert our datetime string to TIMESTAMP type exactly, our query performance improves significantly.
However, I’m curious which of these three approaches is recommended for filtering TIMESTAMP columns, especially regarding the Multi-Stage Engine:
Option A (ANSI SQL CAST):
WHERE my_timestamp_col > CAST('2025-11-02T00:00:00+01:00' AS TIMESTAMP)
Option B (Explicit Functions):
WHERE my_timestamp_col > toTimestamp(fromIso8601('2025-11-02T00:00:00+01:00'))
Option C (Standard Literal - This does not support timezones - UTC only ):
WHERE my_timestamp_col > TIMESTAMP '2025-11-02 00:00'
Our specific questions:
Performance: Is any one of these more "index-friendly" than the others?
Timezones: Does CAST (Option A) reliably handle the +01:00 offset, or should we stick to fromIso8601 (Option B) for anything non-UTC?
Engine Preference: With the move toward the Multi-Stage Engine, is there a preferred syntax for long-term compatibility? (we only use multi stage engine)
Would love to hear your thoughts on the "cleanest" way to handle this in production. Thanks!piby
04/30/2026, 3:06 PMWhen multistage is enabled
query > SELECT * FROM my_table LIMIT 10;
useMultiStage=true
usePhysicalOptimizer=true
DatabaseError: {'errorCode': 150,
'message': 'SQLParsingError: Error composing query plan: No enum constant '
'org.apache.pinot.spi.config.table.TableType.my_table. No '
'enum constant '
'org.apache.pinot.spi.config.table.TableType.my_table'}
useMultistageEngine=false
usePhysicalOptimizer=false
DatabaseError: {'errorCode': 305,
'message': '3 segments unavailable '
'[my_table__2__3373__20260430T0524Z, '
'my_table__2__3380__20260430T1225Z, '
'my_table__2__3180__20260422T0357Z, '], with routing policy: '
'balanced [realtime], balanced [offline]'}Rajat
05/25/2026, 11:03 AMStrategy | Non-null incoming | Null incoming (nullHandlingEnabled: true)
-----------+--------------------+-----------------------------------------
IGNORE | Keeps existing | Keeps existing
| (never updates |
| after first |
| insert)
-----------+--------------------+-----------------------------------------
OVERWRITE | Takes new | Framework preserves existing
| | (null skipped before merger)
If this is correct, then:
1. There's no strategy combination that lets a single PARTIAL table support our dual-writer pattern AND propagate intentional NULLs from source. The two requirements collide on how "incoming null" is interpreted.
2. IGNORE is unusable for any column that ever needs to update — feels less like "ignore" and more like "freeze after first insert."
3. OVERWRITE + nullHandlingEnabled: true is effectively "OVERWRITE_NON_NULL" — useful, but it means partial-upsert can't intentionally clear a column to null.
Questions:
1. Is the table above the documented intended behavior, or am I missing a config knob?
2. Is there a known pattern in Pinot for "update on non-null AND clear to null when source genuinely cleared"? (Other than nullHandlingEnabled: false, which loses Avro null semantics globally.)
3. Any plan for a null-aware variant strategy in upcoming releases?
We've reverted to FULL upsert for our prod path. Just want to make sure I'm not under-using PARTIAL.
Thanks 🙏
C.C: @Mayank @Xiang Fu @JackieHassan Ait Brik
05/27/2026, 12:37 PMUpsertCompactMergeTask with validDocIdsType=SNAPSHOT vs IN_MEMORY.
We have a REALTIME upsert table on Pinot 1.4.0 with:
"upsertConfig": {
"mode": "FULL",
"snapshot": "ENABLE",
"enableSnapshot": true
}
At the beginning of May, we updated this table config to add UpsertCompactMergeTask.
Since then, the task has worked partially: we reduced the table from around 20K segments to around 3K segments.
However, a lot of old completed segments still cannot be merged. From what we see, the task is not able to retrieve their validDocIds using SNAPSHOT.
When triggering the task manually, the controller logs show something like:
Retrieved validDocIds metadata for 15 segments from 96 server requests
Finished generating 0 tasks configs
But we still have many old completed segments from 2023 to 2025 that look like they should be compactable.
I tested the validDocIdsMetadata endpoint on one old segment:
validDocIdsType=SNAPSHOT -> []
validDocIdsType=IN_MEMORY -> returns metadata correctly
validDocIdsType=IN_MEMORY_WITH_DELETE -> []
Example with `IN_MEMORY`:
{
"segmentName": "daily_activity__2__0__20230427T1137Z",
"totalValidDocs": 4,
"totalInvalidDocs": 0,
"totalDocs": 4,
"segmentCrc": "2205091866",
"validDocIdsType": "IN_MEMORY",
"serverStatus": "GOOD"
}
So it looks like the upsert state exists in memory on the realtime servers, but many historical segments do not have validDocIds snapshots.
From the docs, I understand that:
• SNAPSHOT is the default validDocIdsType for UpsertCompactMergeTask
• SNAPSHOT requires enableSnapshot=true
• snapshots are created/refreshed during the normal upsert / segment commit lifecycle
• there does not seem to be a documented command/API to backfill validDocIds snapshots for all already-committed historical segments
My questions are:
1. Is this expected behavior for old completed segments that existed before we added the compaction task / before snapshots were generated for them?
2. Is there any supported way to force Pinot to generate validDocIds snapshots for all existing completed segments?
3. If not, is the recommended approach to run UpsertCompactMergeTask temporarily with:"validDocIdsType": "IN_MEMORY"
to compact the remaining historical backlog, and then switch back to SNAPSHOT for steady state?
4. Are there any risks or caveats with using IN_MEMORY for this remaining backlog, assuming the servers are stable and the table is not being restarted/rebalanced?
Thanks!Badhusha Muhammed
05/27/2026, 6:06 PMvalidDocIds snapshot was missing from many segments.
Assumption
1. Dataset: We are running a 10 TB Upsert table containing millions of rows with a high-cardinality primary key.
2. Restart Behavior: When a server hosting an Upsert table restarts, it must rebuild the in-memory Upsert Metadata Manager (the hash map tracking PrimaryKey -> RecordLocation).
3. Segments with Snapshots: The server can efficiently skip entire segments if their maximum timestamp falls outside the metadata TTL, keeping heap usage low.
4. Segments without Snapshots: The server is forced to load the entire segment into the heap to determine which records are valid. A background TTL filter job (running , lets say every 10 minutes) is responsible for cleaning up expired keys.
Now i feel like the 4th point is causing this issue of high heap ,
If yes ( 4th point is correct )
Is there any way we can create snapshot for segments thats are missing them..?
If no ( 4th point is wrong )
Can you please let me know, what other reason can be there ..?
@Xiang Fu, @MayankSenthil Maruthaiappan
05/28/2026, 7:54 PM30 different segments of a table. I use timestamp for the segment partitioning and also setup rangeindexcolumn on the same column 'report_date'. for example select name, total_sales from product_sales where report_date > '2024-01-20' and report_date < '2024-01-27'. I see that no index has been used for this because numEntriesScannedInFilter is not zero. Lets say, I have 100k rows per segment for 30days worth of data then takes about `504ms f`or the query. Anyone knows how to optimize or tune any of the stuff?dstawiarz
05/29/2026, 12:15 PMpiby
06/02/2026, 8:02 AMClaudia Asanza
06/03/2026, 9:58 PMRaghavendra M
06/15/2026, 12:45 PMjava.lang.reflect.InvocationTargetException
at jdk.internal.reflect.GeneratedMethodAccessor11.invoke(Unknown Source)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:569)
at org.apache.helix.messaging.handling.HelixStateTransitionHandler.invoke(HelixStateTransitionHandler.java:350)
at org.apache.helix.messaging.handling.HelixStateTransitionHandler.handleMessage(HelixStateTransitionHandler.java:278)
at org.apache.helix.messaging.handling.HelixTask.call(HelixTask.java:97)
at org.apache.helix.messaging.handling.HelixTask.call(HelixTask.java:49)
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635)
at java.base/java.lang.Thread.run(Thread.java:840)
Caused by: java.lang.OutOfMemoryError: Cannot reserve 132127 bytes of direct buffer memory (allocated: 64424466414, limit: 64424509440)
am actually using spark3 to push data to pinot OFFLINE table.
any help here?Badhusha Muhammed
06/17/2026, 3:32 AMsamirun shaik
06/25/2026, 8:07 AMpiby
06/26/2026, 10:56 AMstream.kafka.isolation.level: read_committed (and confirming the table
picks it up): does not help — segment still gets stuck
- Only workaround: delete table, set ``auto.offset.reset=smallest``, backfill. Works until the next segment rolls — then a new segment gets stuck again at a commit boundary.
It only affects transactional topics. Non-transactional topics are fine.
Do you have any suggestions for me?Mannoj
07/10/2026, 7:57 AMdetail mode (-XX:NativeMemoryTracking=detail). Looking at the VM.native_memory.detail output, the memory is heavily concentrated in the Other section, while the heap looks completely normal:
> Other (malloc=41940616KB #435) (~40 GB)
For comparison, we checked our Elasticsearch servers under similar loads, and their off-heap/buffer usage remains minimal (only a few MBs out of 30GB available):
JSON
{
"count": 243,
"used_in_bytes": 23122953,
"total_capacity_in_bytes": 23122951
}
When checking the Pinot Server logs during these crashes, we see
java.lang.OutOfMemoryError exceptions being thrown during segment completion protocols:
Plaintext
INFO [ServerSegmentCompletionProtocolHandler] [table_name__partition__sequence__timestamp] Controller response {"streamPartitionMsgOffset":null,"buildTimeSec":-1,"isSplitCommitType":true,"status":"PROCESSED"} for http://<controller-host>:9000/segmentStoppedConsuming?reason=java.lang.OutOfMemoryError&streamPartitionMsgOffset=29329220173&instance=Server_<server-host>_8098&name=table_name__partition__sequence__timestamp
Right after these OOM errors occur, the affected segments transition into an
OFFLINE or ERROR state.
It feels like mmap is either not being effectively utilized, or there is a major native memory leak associated with the "Other" category during data consumption/segment allocation.
A few questions for the community:
1. Has anyone seen Apache Pinot allocations balloon specifically in the NMT "Other" category (which usually points to direct malloc calls from JNI/native code or specific system allocations)?
2. Could this be related to a misconfiguration in how Pinot utilizes memory-mapped files (mmap) versus direct memory allocation for real-time segments?
3. We are using Pinot 1.4. Considering that it uses below setting by default.
# Use off-heap for consuming segments (keep true)
pinot.server.instance.realtime.alloc.offheap=true
# Use MMAP (not direct) for that off-heap allocation (key setting)
pinot.server.instance.realtime.alloc.offheap.direct=false
4. Its not MMAP is not in use at all. It is but major workload is going to direct mem.
PID="`pgrep -f "pinot-server.conf"`" ; grep -E "VmRSS|RssAnon|RssFile" /proc/$PID/status
VmRSS: 81,275,588 kB ≈ 81.28 GB (≈ 77.51 GiB)
RssAnon: 67,669,292 kB ≈ 67.67 GB (≈ 64.53 GiB)
RssFile: 13,605,948 kB ≈ 13.61 GB (≈ 12.98 GiB)
5. What are the best triage steps or configurations to tune to prevent segment consumption from blowing up native memory within minutes?
Any guidance or pointers would be greatly appreciated!Priyanshu Soni
07/22/2026, 2:03 PMprasanna
07/28/2026, 9:27 AMRishabh Sharma
07/30/2026, 5:57 AMHi all — a couple of security questions :
1. What tools are people using to scan the Pinot libraries/dependencies for vulnerabilities?
2. Are there any known open/unresolved critical vulnerabilities in Pinot 1.5.1 specifically? If so, are there recommended mitigations or a target release for fixes?
Trying to assess 1.5.1 for a production deployment. Any pointers to a security advisory page or process for reporting/tracking CVEs would also be appreciated. Thanks!
@Xiang Fu, @Mayank
Priyanshu Soni
07/31/2026, 9:34 AMSyed Meraj Ashraf
08/26/2026, 4:28 AMPriyanshu Soni
09/04/2026, 5:43 AMRishabh Sharma
09/04/2026, 1:44 PMGET /tables/{tableName}/consumingSegmentsInfo repeatedly until it returned no consuming segments, but this did not reliably prevent the issue.
Questions:
1. Is there a definitive API or mechanism to confirm that all Kafka consumers for a table have fully stopped after the table is deleted?
2. Is there a recommended sequence or waiting strategy to ensure consumers are fully torn down before proceeding to delete Kafka topics?
3. Are there any known race conditions in the table deletion flow where consumers can briefly restart or reconnect and recreate a deleted Kafka topic?Dhanesh Dhakrey
09/08/2026, 6:46 AMsessions table associated with our events table, which receives around 100M events/day. This results in roughly 100M rows/day in the sessions table as well. Real versions of the row are only a fraction of the data (~5%).
Conventionally, we would keep only a few weeks of session data, but our requirement is to retain around a year or more of historical data. The challenge is that, with upsert tables, the primary keys remain in heap memory, so retaining a large number of documents eventually puts significant pressure on heap.
I’m considering the following approach:
1. Keep only a small number of days of data in the realtime upsert table for recent data, while retaining roughly a year of historical data in an offline table.
2. Query the Pinot realtime table itself to extract the data. Since it is an upsert table, the query should return the deduplicated/latest version of each primary key.
3. Build and push this cleaned/deduplicated data into the offline table.
4. Verify the counts and data consistency between the realtime query results and the offline table.
5. Only after successful verification, delete the realtime segments that are completely covered by the offline data.
6. The final setup would have clean historical data in offline storage + recent data in realtime, with a hybrid table serving both.
The goal is to effectively get much longer retention from the upsert table without keeping all historical primary-key metadata in heap.
I’d really appreciate your thoughts on whether this is a viable approach in Pinot, or if there is a more native/recommended way to handle this use case.Badhusha Muhammed
09/10/2026, 8:17 AMphani madani
09/14/2026, 4:15 AMDhanesh Dhakrey
09/16/2026, 10:19 AMevents table has around 3*B events per month*, so I’m wondering whether this type of multi-stage query is realistically expected to work at this scale, and if so, what the recommended approach would be.
The query is essentially doing the following:
• Finding each user’s first APP_INSTALL event.
• Looking at their sessions in the following 7 days.
• Deduplicating sessions using ROW_NUMBER() because, for a given (session_id, platform), there can be multiple rows and we need to select the row with the highest event_count.
• Identifying users who were active on exactly 3 distinct days and had exactly 3 sessions during those 7 days.
• Finally, scanning their sessions again to find their last usage timestamp.
I’m using the multi-stage engine:
SET useMultistageEngine = true;
and the query currently times out even with:
OPTION(timeoutMs=90000);
My main questions are:
1. Can Pinot efficiently handle this type of query at this scale, or am I approaching it in the wrong way?
2. Would adding more/heavier indexes help here, or are the expensive parts mainly caused by the GROUP BY, window function, joins, COUNT(DISTINCT ...), and repeated scans of sessions?
3. What is the recommended way to plan indexing for a query like this? Should I first use something like EXPLAIN / query plan mode to understand which operators are expensive and then decide which columns/indexes to optimize?
4. Are there specific indexes or table/data-model changes that would make these joins and aggregations more efficient in Pinot?
5. Would it be better to pre-aggregate or maintain some of these metrics rather than calculating them at query time?
I’m particularly interested in understanding the right process for optimizing Pinot queries:
query plan → identify the expensive operators/scans → determine whether indexing can help → benchmark the change, rather than blindly adding indexes.
Here is the full query for reference:
SET useMultistageEngine = true;
WITH first_install AS (
SELECT
user_id,
MIN(event_timestamp) AS first_install_timestamp,
MIN(event_date_ist) AS first_install_date
FROM events
WHERE event_type = 'APP_INSTALL'
AND user_id IS NOT NULL
AND event_year = 2026
AND event_month = 9
GROUP BY user_id
),
ranked_sessions AS (
SELECT
s.*,
ROW_NUMBER() OVER (
PARTITION BY s.session_id, s.platform
ORDER BY s.event_count DESC
) AS rn
FROM sessions s
WHERE s.session_id IS NOT NULL
),
dedup_sessions AS (
SELECT
session_id,
platform,
user_id,
property_id,
event_count,
page_view_count,
min_timestamp,
max_timestamp,
min_date_ist
FROM ranked_sessions
WHERE rn = 1
),
sessions_7d AS (
SELECT
fi.user_id,
fi.first_install_timestamp,
fi.first_install_date,
s.session_id,
s.platform,
s.min_timestamp,
s.max_timestamp,
s.min_date_ist
FROM first_install fi
JOIN dedup_sessions s
ON fi.user_id = s.user_id
WHERE s.min_timestamp >= fi.first_install_timestamp
AND s.min_timestamp < fi.first_install_timestamp + 604800000
),
qualified_users AS (
SELECT
user_id,
MIN(first_install_timestamp) AS first_install_timestamp,
MIN(first_install_date) AS first_install_date,
COUNT(DISTINCT min_date_ist) AS active_days_7d,
COUNT(DISTINCT session_id) AS session_count_7d
FROM sessions_7d
GROUP BY user_id
HAVING COUNT(DISTINCT min_date_ist) = 3
AND COUNT(DISTINCT session_id) = 3
),
last_usage AS (
SELECT
s.user_id,
MAX(s.max_timestamp) AS last_used_timestamp
FROM dedup_sessions s
JOIN first_install fi
ON s.user_id = fi.user_id
GROUP BY s.user_id
)
SELECT
q.user_id,
q.first_install_date,
DATETIMECONVERT(
lu.last_used_timestamp,
'1:MILLISECONDS:EPOCH',
'1:DAYS:SIMPLE_DATE_FORMAT:yyyy-MM-dd tz(Asia/Kolkata)',
'1:DAYS'
) AS last_used_date,
q.active_days_7d,
q.session_count_7d
FROM qualified_users q
LEFT JOIN last_usage lu
ON q.user_id = lu.user_id
ORDER BY q.first_install_date
OPTION(timeoutMs=90000);