Skip to content

Run track's optimistic metric update when the datastream is not connected - #115

Merged
bpapillon merged 1 commit into
mainfrom
ryan/replicator-track-offline
Sep 30, 2026
Merged

bpapillon merged 1 commit into
mainfrom
ryan/replicator-track-offline

Conversation

@ryanechternacht

Copy link
Copy Markdown
Member

Problem

track sends the event to the API and then optimistically bumps the cached company metrics (DataStreamClient.updateCompanyMetrics) so local flag checks see the usage before the server's update arrives. That local update was gated on dataStreamClient.isConnected(). In replicator mode isConnected() is the replicator's ready field, so as soon as the replicator reported ready: false (Schematic unreachable, account closed) usage stopped counting locally, even though the replicator keeps serving its Redis cache.

Findings

Why the gate exists: it came in with the initial datastream/replicator port in #60, which has no description and no review discussion of the gate. It mirrors the Go SDK, where it was introduced in schematic-go#73 ("optimistic metric updates", no description) as c.datastreamConnected, back when the only datastream mode was the direct WebSocket, and later rewritten to IsConnected() in schematic-go#82 without any stated reason. Nothing in the history points to a correctness requirement.

Checked for correctness risks:

  • Double counting: none. The bump is a read-modify-write of the cached company. Server updates replace metric values instead of adding to them. Full company messages overwrite the entity and partial messages upsert metrics by key (EntityMerge.upsertMetrics). Once the server has counted the event, its value replaces the local one.
  • Missing cache: updateCompanyMetrics already returns without doing anything if the company is not cached, and track still requires non-empty company keys.
  • The track event is enqueued to the API before the local update, and that is unchanged.

Change

Remove the isConnected() condition, so the update runs whenever a datastream client is configured. This applies to both modes:

  • Replicator mode is the motivating case. Flag checks keep reading the replicator's cache while it is not ready, so the counts in that cache should keep moving.
  • WebSocket mode: while the socket is down, flag checks go to the API, so the bump only matters once the connection is back and the cached company is read again. Counting during the gap keeps that cached value closer to the truth than freezing it. The server's next update replaces it either way. Gating only in WebSocket mode would add a special case for no correctness benefit.

Test

New TrackOptimisticUpdateTest:

  • Replicator mode, never ready: track bumps the matching cached metric (10 + 5 = 15) and leaves others alone.
  • WebSocket mode, not connected: same result.
  • Replicator mode, company not cached: track is a no-op for the cache and does not throw.

The first two fail on main and pass with this change. ./gradlew compileJava spotlessCheck test passes on JDK 11 (250 tests).

🤖 Generated with Claude Code

…cted

track only bumped the cached company metrics when the datastream client
reported connected. In replicator mode that meant usage stopped counting
locally as soon as the replicator reported ready: false, even though it
keeps serving its Redis cache and flag checks keep reading from it. The
gate is removed so the local update runs whenever a datastream client is
configured, in both replicator and WebSocket mode. The track event is
still sent to the API either way.

The server's company updates replace metric values rather than adding to
them, so the local bump cannot double count, and updateCompanyMetrics is
already a no-op for a company that is not cached.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@bpapillon
bpapillon merged commit eedb9fa into main Sep 30, 2026
5 checks passed
@bpapillon
bpapillon deleted the ryan/replicator-track-offline branch September 30, 2026 15:29
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.

2 participants