arunkumarucet opened a new pull request, #17485:
URL: https://github.com/apache/pinot/pull/17485

   ### Problem
   During profiling of high-throughput real-time ingestion, identified that 
JSON message decoding in `RealtimeSegmentDataManager.consumeLoop()` was 
consuming significant CPU cycles. The flame graph showed that 
`JsonUtils.bytesToJsonNode` and `JsonUtils.jsonNodeToMap` were hot paths during 
stream consumption.
   
   The current implementation uses a two-step JSON parsing approach:
   `// Step 1: Parse bytes to JsonNode (intermediate tree representation)`
   `JsonNode message = JsonUtils.bytesToJsonNode(payload, offset, length);`
   `// Step 2: Convert JsonNode to Map`
   `Map<String, Object> from = JsonUtils.jsonNodeToMap(message);`
   
   This is inefficient because:
   1. It creates an intermediate JsonNode tree structure with node objects 
(ObjectNode, ArrayNode, TextNode, etc.)
   2. It then traverses that tree again to extract values into a Map<String, 
Object>
   3. The intermediate objects create additional memory allocations
   <img width="1722" height="484" alt="image" 
src="https://github.com/user-attachments/assets/f0eeef59-5799-4584-9eac-2a8c2bb981b6";
 />
   
   ### Solution
   Added a new `JsonUtils.bytesToMap()` method that parses JSON bytes directly 
to Map<String, Object> in a single pass, skipping the intermediate JsonNode 
representation:
   
   `// New: Direct parsing to Map`
   `Map<String, Object> jsonMap = JsonUtils.bytesToMap(payload, offset, 
length);`
   
   This leverages Jackson's `ObjectReader.readValue()` method which can 
deserialize directly to a target type without building the tree model first.
   
   ### Unit Tests Added
   Comprehensive test coverage verifying functional equivalence between old and 
new approaches:
   - Equivalence tests: 45 different JSON payloads via `@DataProvider` 
comparing both approaches
   - Edge cases tested:
       - Empty objects/arrays
       - Null handling
       - All primitive types (int, long, float, double, boolean, string)
       - Large numbers (Long.MAX_VALUE, scientific notation)
       - Unicode/UTF-8 encoding (Chinese, Japanese, Korean, emoji)
       - Escape sequences (quotes, backslash, newline, tab, unicode escapes)
       - Nested structures (5+ levels deep)
       - Mixed-type arrays
       - Offset/length partial array parsing
       - Error handling (malformed JSON, incomplete JSON)
       - Stress tests (100 fields, deeply nested arrays)
   
   ### Benchmark Results
   
   Payload Type | Old Approach (ops/s) | New Approach (ops/s) | Improvement
   -- | -- | -- | --
   small (~100 bytes) | 1,456,586 | 1,994,718 | +36.9%
   medium (~500 bytes) | 614,918 | 945,561 | +53.8%
   large (~2KB) | 185,895 | 257,569 | +38.6%
   nested (~1KB) | 316,107 | 525,349 | +66.2%
   
   ### Key findings:
   1. 35-55% improvement on small to medium payloads (most common in streaming 
scenarios)
   2. 35-40% improvement even on large 2KB payloads
   3. Consistent improvement across all tested payload structures
   
   ### Real-World Impact
   For a Pinot server ingesting JSON messages from Kafka:
   1. Reduced CPU usage in the JSON parsing hot path by ~35-50%
   2. Lower GC pressure due to fewer intermediate object allocations
   3. Higher sustainable ingestion throughput before CPU saturation


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to