Skip to content

Avoid full payload reships for restyles and unchanged columns#189

Draft
Alek99 wants to merge 1 commit into
mainfrom
agent/restyle-without-reship
Draft

Avoid full payload reships for restyles and unchanged columns#189
Alek99 wants to merge 1 commit into
mainfrom
agent/restyle-without-reship

Conversation

@Alek99

@Alek99 Alek99 commented Jul 22, 2026

Copy link
Copy Markdown
Member

Summary

  • add strict, JSON-only restyle APIs for constant mark paint/opacity/stroke and constant scatter size across Chart, FigureWidget, and Reflex
  • content-address every encoded column and send only columns absent from each mount's immediately preceding manifest
  • cache complete Reflex payload encodes by figure version and 64 px width bucket, bounded to eight entries
  • reuse unchanged immutable direct-tier WebGL buffers with reference-counted teardown

Mutable decimated tiers, density textures, retained samples, and drill subsets remain generation-local. Sparse payload resolution is transactional; malformed or missing deltas preserve the last good manifest. Cache state is mount-addressed, cleared on unsubscribe/disconnect, and released on trace teardown or WebGL context loss.

Verification

  • pytest -q: 2227 passed, 66 skipped in 85.01s
  • focused real-Chromium restyle/manifest/GPU lifecycle probes plus live Socket.IO sparse/cache tests: 17 passed
  • pytest -q benchmarks/test_codspeed_restyle.py --codspeed: 2 passed
  • ruff check .: passed
  • ruff format --check .: 343 files already formatted
  • tsc -p js/tsconfig.json --noEmit: passed
  • node js/build.mjs --check: shipped bundles fresh
  • git diff --check: passed

The browser probes verify that direct restyles preserve payload and WebGL buffer identity, density restyles preserve textures/sample buffers, invalid messages fail closed, sparse manifests reconstruct transactionally, direct-tier buffers are reused with balanced references, mutable tiers are excluded, and context teardown clears cached handles.

Local 200k-point benchmark

Measurement Restyle / cached path Full payload path
median control-plane time 1.331 µs 1.194 ms
serialized bytes 81 B 1,600,000 B
tracemalloc peak 999 B 1,802,626 B
style-only sparse column bytes 0 B 1,600,000 B
one changed column 800,000 B 1,600,000 B
same-width-bucket cache hit 0.004 ms 1.763 ms first build

That is an 897.1× median control-plane speedup for the JSON restyle in this local run, while eliminating all 1.6 MB of binary retransmission for style-only updates.

Closes #163

@codspeed-hq

codspeed-hq Bot commented Jul 22, 2026

Copy link
Copy Markdown

Merging this PR will degrade performance by 34.47%

⚠️ Different runtime environments detected

Some benchmarks with significant performance changes were compared across different runtime environments,
which may affect the accuracy of the results.

Open the report in CodSpeed to investigate

❌ 12 regressed benchmarks
✅ 85 untouched benchmarks
🆕 2 new benchmarks
⏩ 1 skipped benchmark1

Warning

Please fix the performance issues or acknowledge them on CodSpeed.

Performance Changes

Benchmark BASE HEAD Efficiency
test_animation_keyed_payload_100k 5.4 ms 13 ms -58.41%
test_animation_plain_payload_100k 3.3 ms 7.1 ms -53.29%
test_first_payload_scatter_continuous_channels 8.6 ms 16.2 ms -46.76%
test_first_payload_scatter_medium 5.6 ms 9.4 ms -40.59%
test_build_scatter_medium_raw 5.6 ms 9.4 ms -40.28%
test_build_scatter_medium_pyplot 6.4 ms 10.2 ms -37.23%
test_first_payload_scatter_small 1.4 ms 1.8 ms -21.59%
test_build_line_small_raw 1.5 ms 1.9 ms -20.49%
test_build_styled_panel_raw 2.3 ms 2.9 ms -20.08%
test_first_payload_scatter_categorical_color 17.3 ms 21.6 ms -19.76%
test_first_payload_heatmap_core_2d 1.6 ms 2 ms -18.63%
test_build_line_small_pyplot 2 ms 2.4 ms -16.3%
🆕 test_restyle_full_split_payload_comparator_200k N/A 11.4 ms N/A
🆕 test_restyle_json_only_200k N/A 262.7 µs N/A

Tip

Investigate this regression by commenting @codspeedbot fix this regression on this PR, or directly use the CodSpeed MCP with your agent.


Comparing agent/restyle-without-reship (8143417) with main (2a7d898)

Open in CodSpeed

Footnotes

  1. 1 benchmark was skipped, so the baseline result was used instead. If it was deleted from the codebase, click here and archive it to remove it from the performance reports.

@greptile-apps

greptile-apps Bot commented Jul 22, 2026

Copy link
Copy Markdown

Greptile Summary

This PR adds lightweight restyling and reuse across the payload and rendering paths. The main changes are:

  • Strict JSON-only restyle APIs for charts, widgets, and Reflex.
  • Content-addressed columns with per-mount sparse payload delivery.
  • Width-bucketed payload caching for Reflex subscribers.
  • Reference-counted reuse of immutable WebGL buffers.

Confidence Score: 4/5

Concurrent restyles and payload builds can leave stale payloads cached for new subscribers.

Payload construction is serialized with entry.lock, while canonical restyle mutation and cache clearing bypass that lock.

An in-flight pre-restyle build can repopulate the cache after invalidation in python/reflex-xy/reflex_xy/registry.py.

T-Rex T-Rex Logs

What T-Rex did

  • Reproduced a deterministic stale payload cache race by pausing a real split-payload build after it encoded the pre-restyle navy constant for cache key (1, 64).
  • Observed that Registry.restyle changed the canonical constant to tomato, kept version 1, and cleared the payload cache, and that releasing the in-flight build restored navy under (1, 64) while the canonical remained tomato.
  • Compared pre-change and post-change runtime behavior and confirmed the post-change pattern with 2-buffer initial payload, 1-buffer changed-column delta, 0-buffer style update, one shared encode, and mount-b-only delivery after mount-a unsubscribe, while the focused pytest run completed successfully with 3 tests passing.

View all artifacts

T-Rex Ran code and verified through T-Rex

Important Files Changed

Filename Overview
python/reflex-xy/reflex_xy/registry.py Adds payload-cache invalidation and canonical restyling, but does not serialize them with payload construction.
python/reflex-xy/reflex_xy/namespace.py Adds per-mount sparse manifests, ordered delivery, and width-bucketed payload caching.
python/xy/_figure.py Adds strict transactional validation and mutation for constant style and scatter-size restyles.
python/xy/_payload.py Adds stable content hashes to encoded logical columns.
js/src/00_header.ts Adds transactional resolution and validation of sparse column manifests.
js/src/50_chartview.ts Adds reference-counted GPU buffer reuse for immutable direct-tier columns.
js/src/54_kernel.ts Adds fail-closed client restyle validation and live GPU state updates.

Reviews (1): Last reviewed commit: "Avoid full payload reships for restyles" | Re-trigger Greptile

Comment on lines +319 to +324
message = entry.figure.restyle_message(trace, style, **kwargs)
# Same full-payload version (existing clients need not advance their
# message guard), but a later subscriber must build a spec containing
# the new constants rather than reuse a pre-restyle cached spec.
with self._mutex:
entry.payload_cache.clear()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Restyle Can Restore Stale Payloads

restyle_message() mutates the figure and clears its cache without entry.lock, while _cached_payload() builds and stores payloads under that lock. If a subscription is encoding during a restyle, the in-flight build can insert the old appearance after this clear under the unchanged (version, bucket) key, so later subscribers receive stale styles. The mutation and cache invalidation must be serialized with payload construction.

Artifacts

Repro: deterministic stale payload cache race harness

  • Contains supporting evidence from the run (text/x-python; charset=utf-8).

Repro: successful execution showing stale pre-restyle constants returned to a later subscriber

  • Keeps the command output available without making the summary code-heavy.

View artifacts

T-Rex Ran code and verified through T-Rex

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.

No restyle path or per-column caching: any change re-ships the whole payload

1 participant