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

   ## Summary
   
   Ingestion and query-time JsonPath extraction goes through Jayway
   (`parse(json).read(path)`), which builds a full Jackson DOM of the document 
and only then walks to
   the field — once per row per derived column, a major ingestion CPU cost. 
This adds a streaming
   Jackson-based extractor for **simple linear paths** (`$` followed only by 
`.key`, `['key']`,
   `[int]`), used by both re-parse call sites:
   
   - `JsonFunctions.jsonPath` (and `jsonPathString/Long/Double/Array/Exists`, 
which route through it)
   - `JsonExtractScalarTransformFunction` (ingestion `transformConfigs`)
   
   Everything else falls back to Jayway: wildcards, deep scan (`..`), filters, 
unions, slices,
   negative indices, non-`String`/`byte[]` input, and any document whose first 
non-whitespace character
   is not `{` or `[` (bare scalars, `null`, empty, whitespace, BOM, plain text).
   
   ## Feature flags — both default **OFF**
   
   | Key | Effect |
   |---|---|
   | `pinot.jsonpath.fastpath.enabled` | Gate the streaming fast path. |
   | `pinot.jsonpath.fastpath.earlyExit.enabled` | Also stop at the leaf 
instead of scanning the whole root value. No effect unless the above is on. |
   
   Pure kill-switch back to Jayway. Server-local compute only — no wire format, 
segment format, or
   config-schema change, so mixed-version safe with no migration.
   
   ## Semantics
   
   The default (full-scan) path is **byte-for-byte identical** to Jayway as 
Pinot configures it
   (`JacksonJsonProvider` + `JacksonMappingProvider` + `SUPPRESS_EXCEPTIONS`), 
including duplicate-key
   last-wins and raising `InvalidJsonException` on a document malformed 
anywhere inside its root value.
   Content *after* the root value is ignored, exactly as 
`ObjectMapper.readValue` ignores it.
   
   **Early exit** trades two behaviors for speed when the field appears early, 
since the tail is never
   read: (1) duplicate keys resolve to the *first* occurrence; (2) a document 
malformed strictly after
   the addressed field no longer raises. Both inputs are RFC-8259-undefined or 
corrupt, and the switch
   is off by default.
   
   ## Correctness gate
   
   `StreamingJsonPathExtractorTest` is a **differential test with the 
production Jayway fallback as the
   oracle**: the same `jsonPath*` entry points run flag-off vs flag-on and must 
agree, over a
   hand-picked matrix (missing paths, invalid JSON, null/empty/whitespace/BOM, 
type mismatches, nested
   arrays, arrays of objects, deep nesting, unicode/surrogates, 
escaped/dotted/duplicate keys,
   big/precise numbers) **plus 60k randomized fuzz iterations**, the `byte[]` 
overloads (incl. multibyte
   and invalid-UTF-8), and the `USE_BIG_DECIMAL_FOR_FLOATS` context. Early-exit 
divergences are
   asserted explicitly. `JsonFunctionsFastPathTest` and 
`JsonExtractScalarTransformFunctionFastPathTest`
   re-run the existing corpora through the fast path.
   
   ## Performance
   
   JMH (`BenchmarkJsonPathExtraction`, Apple M, ~688-byte nested payload, 4×5s 
warmup / 6×5s measure);
   the `fastPathEnabled=false` arm is the current Jayway baseline, measured in 
the same run:
   
   | `jsonPathString`, one column | ops/s | vs Jayway |
   |---|---:|---:|
   | Jayway (today) | 458k | 1.0x |
   | Streaming, **exact parity** (full scan) | 836k | **1.8x** |
   | Streaming, early exit, field early | 5.09M | **11.1x** |
   | Streaming, early exit, field last | 813k | 1.8x |
   
   | 4 derived columns from one document | ops/s | vs Jayway |
   |---|---:|---:|
   | Jayway (4 parses) | 106k | 1.0x |
   | Streaming, one pass, all 4 paths | 763k | **7.2x** |
   
   1.8x is the Jackson lexing floor: exact parity requires reading the whole 
root value, so a bare
   `nextToken()`-to-EOF loop (~907 ns) is already close to the full extract 
(~919 ns). The larger wins
   come from early exit (undefined-input tradeoff) and from single-pass 
multi-path extraction, which
   must walk the document anyway and so gets exact parity for free — the right 
target for
   `transformConfigs` pulling N columns from one source.
   
   ## Testing
   
   `pinot-common`: 575 tests green (incl. the differential + fuzz). 
`pinot-core`: 218 green
   (`JsonExtractScalarTransformFunctionTest` + its fast-path subclass, 
`JsonExtractScalarTest`,
   `JsonExtractIndexTransformFunctionTest`). spotless / checkstyle / license 
clean.
   
   ## Notes for reviewers
   
   - `earlyExit` changes results, so it must be flipped **cluster-wide, not 
mid-rolling-restart** — a
     half-restarted cluster would have servers disagree within one 
scatter-gather. `enabled` alone is
     result-preserving and rolls normally.
   - The single-pass multi-path `extract` is included and benchmarked but not 
yet wired into
     `jsonExtractScalar` (which would need sibling transform functions to share 
one pass over the source
     column); that is the follow-up where the 7x lands in ingestion.
   


-- 
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