Serve replicator-mode flag checks only once the cache is ready - #135
Open
ryanechternacht wants to merge 3 commits into
Open
ryanechternacht wants to merge 3 commits into
ryanechternacht wants to merge 3 commits into
Conversation
2 of 3 tasks
In replicator mode, check_flag and check_flag_with_entitlement read the shared cache with no readiness gate, while check_flags with keys gated on is_connected(). The health poll also called raise_for_status(), so a 503 from /ready never had its body read and its cache_version was dropped. - Health poll reads the JSON body on any status, sets readiness from the ready field, records any non-empty cache_version, and on a failed poll sets not ready and keeps the last cache_version. - Add DataStreamClient.is_cache_ready(). is_connected() is unchanged and documented as reporting replicator readiness in replicator mode. - Single, bulk and the client-mode credit lease check share one gate, AsyncSchematic._get_flag_check_datastream(): while the cache is not ready they take the API path. - README describes the readiness behavior. Depends on SchematicHQ/schematic-replicator#143. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The test from #134 expected check_flag to read the cache while the replicator is not ready. Flag checks now skip the cache until it is ready, so the test checks the API path while not ready and the tracked usage from the cache once the replicator reports ready. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
ryanechternacht
force-pushed
the
ryan/replicator-cache-ready-gate
branch
from
September 30, 2026 17:56
c624fed to
79d63bd
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Depends on SchematicHQ/schematic-replicator#143. Do not merge before it. #143 changes
/readysoready: true(200) means the cache is complete for itscache_version, and a replicator still loading answers 503 withready: falseand thecache_versionit is loading.Problem
In replicator mode (async client only; the sync client has no DataStream), single and bulk flag checks were gated differently:
check_flag/check_flag_with_entitlementevaluated from the replicator cache with no readiness gate, so they could serve from a partially loaded cache.check_flagswith keys only used the cache whends.is_connected()was true, which in replicator mode is replicator readiness.The health poll also called
raise_for_status()before reading the body, so a 503 from/readywas treated as a failed poll and itscache_versionwas never recorded.Change
DataStreamClient._check_replicator_health) reads the JSON body whatever the HTTP status, sets readiness from thereadyfield, and recordscache_versionfrom any response that has a non-empty one. A failed poll (connection error, timeout, unparseable body) sets not ready and keeps the last knowncache_version.get_replicator_cache_version_asyncalso reads the body on a 503 now.DataStreamClient.is_cache_ready()is new. In replicator mode it returns the replicator readiness above; outside replicator mode it returns true, since websocket mode fills and fetches its own cache.is_connected()is unchanged, with a docstring saying that in replicator mode it reports replicator readiness and pointing tois_cache_ready().AsyncSchematic._get_flag_check_datastream(), returns the DataStream client only whenis_cache_ready()is true.check_flag_with_entitlement(and socheck_flag),check_flagswith keys, and the client-mode credit lease path incheck()all use it (see the divergence note below). While the cache is not ready they take the existing API path, which falls back to the flag default if the API fails. Once it is ready they evaluate from the cache exactly as before, including the existing API fallback when evaluation errors (for example, flag not in cache).check_flagskeeps its existing extrais_connected()check there; in replicator mode that value equalsis_cache_ready().Parity with Go
The Go counterpart is SchematicHQ/schematic-go#240, and this matches it.
is_cache_ready()returns replicator readiness in replicator mode and true otherwise, so websocket mode is unchanged. One shared helper (_get_flag_check_datastream(), which returns the datastream only if one is configured andis_cache_ready()is true) replaces the datastream check at both the single and bulk flag-check branches, like Go'suseDataStreamCache().One divergence:
check()withusagein client credit-lease mode also goes through the gate. It is a credit-aware flag check that evaluates the flag and company from the DataStream cache, so while the replicator cache is not ready it runs as a plain check through the API and takes no lease. Go'scheckWithClientLeaseinclient/check.gostill reads the cache directly. This is documented in the README.Like Go,
prewarmis left ungated. It is not a flag check: it resolves a company ID through the DataStream (_resolve_company_id_with_wait) to acquire leases early, and still reads the cache without waiting for readiness.Overlap with #134
#134 changes the
trackgate (_update_company_metrics). This PR does not touchtrack, and the two branches merge cleanly.Tests
New
tests/custom/test_replicator_cache_ready.pyrunsAsyncSchematicin replicator mode againstRedisCacheover fakeredis, seeded with JSON under the versioned keys the replicator writes, and a scripted/readyendpoint viahttpx.MockTransport:check_flagspy is never called), return the API's values, and return flag defaults when the API fails. The same holds before any health response and after readiness is lost.{"ready": false, "cache_version": "vX"}sets not ready and records vX. An unreachable URL and an unparseable body set not ready and keep the previouscache_version. An emptycache_versionkeeps the last one. The health-changed callback fires on transitions only.A lease-client test in
tests/custom/test_client.pychecks thatcheck()with usage runs as a plain API check and acquires no lease while the cache is not ready. The existing 503 health test was updated to the new semantics.poetry run mypy .passes, andpoetry run pytest -n auto .passes (774 passed, 3 skipped).🤖 Generated with Claude Code