Skip to content

perf: dump-then-scan JSON serialization for socket payloads (companion to #6116) - #6826

Closed
FarhanAliRaza wants to merge 33 commits into
reflex-dev:mainfrom
FarhanAliRaza:orjson-serialization-fixes
Closed

perf: dump-then-scan JSON serialization for socket payloads (companion to #6116)#6826
FarhanAliRaza wants to merge 33 commits into
reflex-dev:mainfrom
FarhanAliRaza:orjson-serialization-fixes

Conversation

@FarhanAliRaza

@FarhanAliRaza FarhanAliRaza commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Draft companion to #6116 (try-orjson), branched from its head (7f89aed).

Commit 1 adds test_process_event_wire: a benchmark of the full receive -> process -> serialize round trip (event JSON decoded via orjson_loads, processed through BaseStateEventProcessor, delta serialized via orjson_dumps_socket as socket.io's encoder invokes it), parametrized with 5-row and 500-row table deltas. No existing benchmark exercised the serialization path #6116 changes.

Commit 2 (pushed after commit 1's CodSpeed run completes, so the delta is measurable run-over-run) reworks the serialization strategy to dump-then-scan on both backends plus assorted fixes; details in the commit message.

Local walltime medians for the large-delta wire benchmark: 5.01ms before, 3.84ms after (-23%). This PR exists to confirm that delta under CodSpeed instrumentation; findings will be folded back into #6116.

Review in cubic

benedikt-bartscher and others added 30 commits February 7, 2026 00:56
…llback

Happy path uses native JSON.parse; the catch rewrites Python"s bare
Infinity/-Infinity/NaN tokens outside string literals before retrying,
so only payloads that actually contain specials pay the extra cost.
NaN has no JSON literal and becomes null (matches JSON.stringify).
…o null

Swap bare NaN for a sentinel string before JSON.parse and revive it back
to a real NaN so Python-side float(nan) round-trips to the frontend.
Apply orjson_dumps/orjson_loads helpers to the new reflex_base
package locations after upstream's restructuring.
benedikt-bartscher and others added 3 commits July 24, 2026 23:39
test_process_event_wire measures the full receive -> process -> serialize
round trip: raw event JSON decoded via orjson_loads (as socket.io's decoder
does), events processed through BaseStateEventProcessor with a real
StateManagerMemory, and each delta serialized through orjson_dumps_socket
the way App._setup_state wires socket.io's encoder. Parametrized with a
small (5-row) and large (500-row) table delta so JSON parse/dump cost is
part of the measured path, which no existing benchmark covered.
Serialization previously walked every payload with
_replace_non_finite_floats before every socket emit to protect NaN/Infinity
floats (orjson silently emits null for them) and to escape
sentinel-colliding strings. The walk dominated serialization cost (~95% on
a 290KB payload) and ran even in the stdlib fallback, making default
installs slower than before reflex-dev#6116.

Both backends now dump once and decide from the output bytes whether the
walk is needed at all:

- orjson: None, NaN and Infinity all serialize to exactly the token null,
  so output without null provably lost no non-finite floats; output without
  the sentinel prefix has no colliding strings. Only when either substring
  appears is the payload walked and re-dumped. The default= callback is
  split into a non-walking pass-1 variant and a walking pass-2 variant so
  StateUpdate-wrapped payloads keep the fast path.
- stdlib (no orjson): bare NaN/Infinity tokens are restored by the
  frontend's bare-token rewriter, so only a sentinel-prefix hit forces the
  walk. Restores default-install performance to parity with pre-reflex-dev#6116.

Frontend mirror of the same idea: passing a reviver to JSON.parse disables
the engine's native fast parser (measured 4-12x slower in Chromium), so
parseNonFiniteAwareJSON only applies the reviver when the payload actually
contains the sentinel prefix. The upload NDJSON parser gains the bare-token
rewriter retry it was missing.

Also: hoist the orjson import to module level (was per-call on the compile
hot path), make orjson_dumps fall back to stdlib for indent widths other
than 2 instead of silently coercing, and preserve kwargs in its TypeError
fallback.

Serialize-only medians vs stdlib baseline (1000x10 table delta): 1.91ms ->
431us with orjson, parity without. Browser decode of the same delta: 2.05ms
-> 506us.
@codspeed-hq

codspeed-hq Bot commented Jul 31, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

βœ… 26 untouched benchmarks
πŸ†• 2 new benchmarks
⏩ 8 skipped benchmarks1

Performance Changes

Mode Benchmark BASE HEAD Efficiency
πŸ†• Simulation test_process_event_wire[large_delta] N/A 31.9 ms N/A
πŸ†• Simulation test_process_event_wire[small_delta] N/A 3.6 ms N/A

Comparing FarhanAliRaza:orjson-serialization-fixes (ba68dff) with main (cce9772)

Open in CodSpeed

Footnotes

  1. 8 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports. ↩

@greptile-apps

greptile-apps Bot commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR introduces optional orjson-backed JSON handling and optimizes websocket payload serialization.

  • Adds dump-then-scan serialization with sentinel-based preservation of non-finite floats and colliding strings.
  • Wires the new JSON helpers into Socket.IO events, uploads, generated frontend files, and persisted JSON utilities.
  • Adds serialization regression coverage and end-to-end event-processing benchmarks.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains within the scope of the displayed follow-up threads.

Important Files Changed

Filename Overview
packages/reflex-base/src/reflex_base/utils/format.py Adds optional orjson loading/dumping and socket-specific serialization that preserves non-finite floats through frontend-compatible sentinels.
packages/reflex-base/src/reflex_base/.templates/web/utils/state.js Adds conditional sentinel revival and fallback rewriting for non-standard bare non-finite tokens.
packages/reflex-base/src/reflex_base/.templates/web/utils/helpers/upload.js Replaces JSON5 upload-stream parsing with the shared non-finite-aware JSON parser and fallback rewriter.
reflex/app.py Configures Socket.IO and event processing to use the new optional orjson-backed helpers.
packages/reflex-components-core/src/reflex_components_core/core/_upload.py Uses the new helpers for upload event arguments and streamed state-update serialization.
tests/units/utils/test_format_orjson.py Adds focused coverage for backend selection, custom serialization, non-finite values, sentinel collisions, and fallback behavior.
tests/benchmarks/test_event_processing.py Extends benchmarks to cover complete event decoding, processing, and wire serialization for small and large deltas.
pyproject.toml Adds orjson as an optional project extra and development dependency.

Reviews (2): Last reviewed commit: "perf: dump-then-scan JSON serialization ..." | Re-trigger Greptile

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants