Skip to content

Commit df4ad47

Browse files
slayerjainclaude
andauthored
fix(restheart-mongo): deterministic GraphQL app cache + expires_in noise (#142)
* fix(restheart-mongo): deterministic GraphQL app cache + expires_in noise The keploy restheart-mongo lane flaked on two recorded fields; both are app/JWT non-determinism, fixed the same way the lane already handles the ACL cache and the access_token JWT: 1. post-graphql-halpeople (mongo mock-miss -> 500 MongoSocketReadException): RESTHeart's GraphQL service caches gql-apps app definitions with a 60s time-to-revalidate (app-cache-ttr, Caffeine nanoTime ticker) — the exact same time-freeze x cache-ttl interaction already documented for /mongoAclAuthorizer. Under `keploy test --freezeTime` the ticker is frozen, so the app cache never revalidates and RESTHeart issues a different number/order of `find gql-apps` mongo queries than were recorded; the unmatched find has no mock so keploy's mongo proxy closes the socket and RESTHeart returns 500 instead of the recorded response. Fix: add /graphql/app-cache-enabled->false to RHO so gql-apps is read-through every request (identical mongo stream at record & replay), mirroring the existing /mongoAclAuthorizer/cache-enabled->false. 2. post-token (expires_in 872 vs 871): /tokens returns expires_in = seconds-until-exp computed at request time; it can differ by 1s across record/replay when the request straddles a second boundary, even under --freezeTime. The JWT itself (access_token) is already noised; add expires_in to globalNoise.body for the same reason. Signed-off-by: Shubham Jain <shubham@keploy.io> Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Signed-off-by: slayerjain <shubham@keploy.io> * fix(restheart-mongo): disable frozen /mongo metadata caches for deterministic replay RESTHeart's /mongo local-cache (db/collection _properties, 60s ttl) and schema-cache (JSON schemas, 60s ttl) are Caffeine caches ticking on System.nanoTime(), which --freezeTime freezes. The frozen ttl shifts which per-test window each 'find _properties' mongo query lands in between record and replay, so keploy's strict per-test mock pool misses -> mongo socket close -> 500 MongoSocketReadException. Observed on keploy/enterprise pipeline 5867 (record-pr-replay-stable): 13/306 flaked on get-root/post-users/delete-* legs that COMPAT_TEST_RETRIES could not absorb. Same frozen-ticker class as the ACL and GraphQL cache disables already in this file; makes _properties/schema reads read-through (deterministic, always correct, small perf cost). get-collection-cache left enabled (inert: needs ?cache, which flow.sh never sends). Signed-off-by: slayerjain <shubhamkjain@outlook.com> --------- Signed-off-by: Shubham Jain <shubham@keploy.io> Signed-off-by: slayerjain <shubham@keploy.io> Signed-off-by: slayerjain <shubhamkjain@outlook.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 6645b0d commit df4ad47

2 files changed

Lines changed: 52 additions & 1 deletion

File tree

restheart-mongo/docker-compose.yml

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ services:
2727
# --freezeTime keeps `exp` valid. Pinning the secret keeps
2828
# the bearer signature verifiable across record→replay
2929
# container restarts (deterministic key, deterministic JWT).
30-
RHO: '/mclient/connection-string->"mongodb://${RESTHEART_MONGO_IP:-172.36.0.10}:27017";/http-listener/host->"0.0.0.0";/core/log-level->"INFO";/jwtConfigProvider/key->"keploy-fixed-jwt-secret-for-deterministic-recordings";/mongoAclAuthorizer/cache-enabled->false'
30+
RHO: '/mclient/connection-string->"mongodb://${RESTHEART_MONGO_IP:-172.36.0.10}:27017";/http-listener/host->"0.0.0.0";/core/log-level->"INFO";/jwtConfigProvider/key->"keploy-fixed-jwt-secret-for-deterministic-recordings";/mongoAclAuthorizer/cache-enabled->false;/graphql/app-cache-enabled->false;/mongo/local-cache-enabled->false;/mongo/schema-cache-enabled->false'
3131
# Bound RESTHeart's JVM heap. RESTHeart 9.x's default uses
3232
# `MaxRAMPercentage=25` of cgroup memory, which on a typical
3333
# Woodpecker runner cgroup (~8GB) lands ~2GB heap. With three
@@ -59,6 +59,50 @@ services:
5959
# despite recorded 200. Disabling the cache makes ACL rules
6060
# read-through from mongo on every request — small perf cost,
6161
# but eliminates the time-freeze x cache-ttl interaction.
62+
#
63+
# Note on /graphql/app-cache-enabled->false in RHO:
64+
# RESTHeart's GraphQL service caches gql-apps app definitions with a
65+
# 60s time-to-revalidate (app-cache-ttr, Caffeine, nanoTime ticker) —
66+
# the SAME time-freeze x cache-ttl interaction as the ACL cache above.
67+
# flow.sh POSTs a "halpeople" GraphQL app to gql-apps and then fires
68+
# POST /graphql/halpeople queries. At record the wall clock advances,
69+
# so the app cache revalidates (re-reading gql-apps) at TTR
70+
# boundaries; under `keploy test --freezeTime` the ticker is frozen so
71+
# the cache never revalidates, producing a DIFFERENT number/order of
72+
# `find gql-apps` mongo queries than were recorded. The unmatched
73+
# find has no recorded mock -> keploy's mongo proxy closes the socket
74+
# -> RESTHeart returns 500 (MongoSocketReadException) instead of the
75+
# recorded response (the post-graphql-halpeople flakiness). Disabling
76+
# the app cache makes gql-apps read-through on every request, so the
77+
# mongo query stream is identical at record and replay.
78+
#
79+
# Note on /mongo/{local,schema}-cache-enabled->false in RHO:
80+
# The SAME time-freeze x cache-ttl interaction as the ACL and GraphQL
81+
# caches above, but for the mongo service's own Caffeine caches (default
82+
# ttls tick on the frozen System.nanoTime()).
83+
# - local-cache (db/collection _properties metadata, 60s ttl): this is
84+
# the confirmed culprit. Every request routes/validates via the target
85+
# collection's _properties — `GET /` (get-root) reads ALL collections'
86+
# _properties, and writes/deletes (post-users, delete-users,
87+
# delete-acl-*) read the target collection's. With local-cache on, that
88+
# `find _properties` mongo stream is served from cache at record (wall
89+
# clock warm) but the frozen ttl at replay shifts WHICH per-test window
90+
# each find lands in, so keploy's strict per-test mock pool has no
91+
# matching mock -> mongo proxy closes the socket -> 500
92+
# MongoSocketReadException. Observed on keploy/enterprise pipeline 5867
93+
# (record-pr-replay-stable): 13/306 failed — get-root-1..4,
94+
# post-users-1/2, delete-users-*, delete-acl-* — a different leg each
95+
# run (COMPAT_TEST_RETRIES=3 could not absorb it).
96+
# - schema-cache (JSON schemas, 60s ttl): same frozen-ticker class; the
97+
# sample bootstraps _schemas and posts a schema (flow.sh), so a
98+
# schema-validated write can shift its `_schemas` find the same way.
99+
# Disabled proactively to close the class on a path the sample exercises.
100+
# (get-collection-cache is left enabled: it only activates on ?cache
101+
# requests, which flow.sh never sends, so it is inert here.)
102+
# Disabling makes _properties/schema reads read-through on every request,
103+
# so the mongo query stream is identical at record and replay. Read-through
104+
# is always correct (only a small perf cost), so this cannot regress
105+
# behaviour.
62106
depends_on:
63107
mongo:
64108
condition: service_healthy

restheart-mongo/keploy.yml.template

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,3 +61,10 @@ test:
6161
client_ip: []
6262
latencyMs: []
6363
access_token: []
64+
# RESTHeart's /tokens endpoint returns expires_in = seconds until
65+
# the JWT `exp`, computed as (exp - now) at request time. Even under
66+
# --freezeTime the recorded vs replayed value can differ by 1s when
67+
# the request straddles a second boundary (observed 872 vs 871 on
68+
# post-token), so the countdown must be treated as noise just like
69+
# the access_token JWT itself.
70+
expires_in: []

0 commit comments

Comments
 (0)