Skip to content

feat: split shape count by indexability#4125

Merged
icehaunter merged 1 commit intomainfrom
ilia/pr/xtxrywmtsuux
Apr 15, 2026
Merged

feat: split shape count by indexability#4125
icehaunter merged 1 commit intomainfrom
ilia/pr/xtxrywmtsuux

Conversation

@icehaunter
Copy link
Copy Markdown
Contributor

@icehaunter icehaunter commented Apr 14, 2026

Summary

Add sync-service telemetry for indexed vs unindexed shape counts so we can correlate stack behavior with how shapes are routed in ShapeLogCollector.

Motivation

Some stacks have very large shape cardinality, and total shape count alone does not explain routing cost well enough. Splitting shapes by indexability makes it easier to understand when work is dominated by indexed routing versus other_shapes scans.

Implementation

  • emit electric.shapes.indexed_shapes.count
  • emit electric.shapes.unindexed_shapes.count
  • classify shapes using the same routing rules as the filter layer
  • maintain cached counts in ShapeStatus and emit all three shape metrics (total, indexed, unindexed) from one O(1) snapshot rather than scanning shapes during each telemetry tick

Verification

  • added filter classification tests for indexed vs unindexed cases
  • added shape-status tests for maintained counts across add/remove
  • added telemetry test covering the emitted total/indexed/unindexed/active metrics

No linked issue; this change is operational telemetry to improve observability for large stacks.

@claude
Copy link
Copy Markdown

claude bot commented Apr 14, 2026

Claude Code Review

Summary

The PR is in excellent shape. Since the previous review, the last remaining suggestion (the count_shapes_by_indexability dead code) has been cleaned up - the function is gone from both production code and tests. Three inline comments from @alco were posted after the previous review; two are already resolved by the current code, and one performance/visibility question remains open.

What's Working Well

  • Dead code removed: count_shapes_by_indexability is fully gone from ShapeCache, ShapeStatus, and the test file. The test now uses shape_counts/1 directly, which is cleaner.
  • Metric naming already correct: @alco's suggestion to emit the new counts as measurements of the existing total_shapes event is exactly what the current implementation does - one :telemetry.execute call with count, count_indexed, and count_unindexed measurements, giving metric paths like electric.shapes.total_shapes.count_indexed.
  • shape_cached_as_indexed? naming: @alco's preferred name is already the current name. No change needed.
  • Test quality: The telemetry test in stack_supervisor_test.exs correctly patches both shape_counts and active_consumer_count and asserts all three new measurements in a single assert_receive.

Issues Found

Suggestions (Nice to Have)

rebuild_shape_routing_state/1 has no tracing span - @alco's open question

File: packages/sync-service/lib/electric/shape_cache/shape_status.ex:447

This function iterates every shape in SQLite via ShapeDb.reduce_shapes at startup (initialize/1) and on lock handover (refresh/1). @alco flagged the visibility gap: for a stack with 100k shapes there is currently no way to observe how long this scan takes in production, since rebuild_shape_routing_state has no OpenTelemetry.with_child_span wrapper.

populate_shape_meta_table (the analogous startup scan for the ETS meta table) also lacks a span, so this is a pre-existing gap - but the new rebuild_shape_routing_state scan means total startup I/O from SQLite is roughly twice what it was before. Worth wrapping in a span or adding a Logger.debug with monotonic timing so slow restarts are diagnosable.


Issue Conformance

No linked issue, but the PR description is thorough and the implementation matches it exactly. Operational telemetry with no observable behavior change for clients.

Previous Review Status

All items resolved:

  1. O(N) scan per telemetry tick - Fixed (ETS incremental counters).
  2. Missing PR description - Fixed.
  3. Missing changeset file - Fixed.
  4. count_shapes_by_indexability dead code - Fixed; function fully removed.
  5. @alco metric naming concern - Already implemented correctly (single event, three measurements).
  6. @alco naming concern for shape_cached_as_indexed? - Already the current name.

One open question remains: the tracing visibility of rebuild_shape_routing_state at startup for large stacks (see Suggestions above).


Review iteration: 3 | 2026-04-15

@codecov
Copy link
Copy Markdown

codecov bot commented Apr 14, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 89.20%. Comparing base (0bbb377) to head (f9fb78b).
⚠️ Report is 7 commits behind head on main.
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #4125   +/-   ##
=======================================
  Coverage   89.20%   89.20%           
=======================================
  Files          25       25           
  Lines        2520     2520           
  Branches      641      638    -3     
=======================================
  Hits         2248     2248           
  Misses        270      270           
  Partials        2        2           
Flag Coverage Δ
packages/experimental 87.73% <ø> (ø)
packages/react-hooks 86.48% <ø> (ø)
packages/start 82.83% <ø> (ø)
packages/typescript-client 94.30% <ø> (ø)
packages/y-electric 56.05% <ø> (ø)
typescript 89.20% <ø> (ø)
unit-tests 89.20% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@icehaunter icehaunter force-pushed the ilia/pr/xtxrywmtsuux branch from 5c6e7b6 to da236bd Compare April 15, 2026 08:44
@icehaunter icehaunter self-assigned this Apr 15, 2026
@icehaunter icehaunter marked this pull request as ready for review April 15, 2026 08:52
:ok
end

defp rebuild_shape_routing_state(stack_id) do
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

What is the performance of going through all shapes in shapedb? Is it trivial enough for, say, 100k shapes, such that we don't need to add a tracing span around it for visibility?

Comment thread packages/sync-service/lib/electric/shape_cache/shape_status.ex Outdated
Comment thread packages/sync-service/lib/electric/stack_supervisor/telemetry.ex Outdated
@icehaunter icehaunter force-pushed the ilia/pr/xtxrywmtsuux branch from da236bd to f9fb78b Compare April 15, 2026 09:42
@icehaunter icehaunter merged commit 59a96b8 into main Apr 15, 2026
47 checks passed
@icehaunter icehaunter deleted the ilia/pr/xtxrywmtsuux branch April 15, 2026 10:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants