Skip to content

Two related tidy-ups to query-cache dependent-table handling.#3792

Merged
rbygrave merged 1 commit into
masterfrom
feature/optimize-dependantTables
Jun 23, 2026
Merged

Two related tidy-ups to query-cache dependent-table handling.#3792
rbygrave merged 1 commit into
masterfrom
feature/optimize-dependantTables

Conversation

@rob-bygrave

Copy link
Copy Markdown
Contributor

This is a replacement for #3150

1. Read dependentTables from the query plan (single source of truth)

OrmQueryRequest accumulated query-cache dependentTables into a field via
addDependentTables(...), fed from per-CQuery dependentTables() helpers. The
CQueryPlan already owns this set, so the copying is redundant.

  • putToQueryCache(...) now reads dependentTables directly from the request's CQueryPlan at cache-put time.
  • Removed the OrmQueryRequest.dependentTables field and addDependentTables(Set).
  • Removed the now-unused dependentTables() helpers from CQuery, CQueryRowCount and CQueryFetchSingleAttribute (and their Set imports).

This is behaviour-preserving: each CQuery is built with exactly the plan stored
under request.queryPlanKey, so request.queryPlan().dependentTables() is the
same set the per-CQuery helpers returned.

2. Fix a query-plan trim race that could null the plan at put time

A freshly built CQueryPlan was stored in the plan cache with
lastQueryTime == 0, making it immediately eligible for trimQueryPlans
(runs every 60s; TTL default 300s) during its first execution. If the trim
fired mid-execution, request.queryPlan() could return null at put time — and
a cache entry with null dependentTables is never invalidated by table
modifications (TableModState.isValid), i.e. latent stale data.

  • CQueryPlanStats now initialises lastQueryTime to construction time, so a new plan is only trim-eligible after a genuine TTL idle. trimQueryPlans is the sole consumer of lastQueryTime(), so this is safe.
  • putToQueryCache(...) skips the put when the plan is null (fail safe rather than caching an un-invalidatable entry) for the residual pathological case (a single query running past the TTL on first execution).

Tests

  • Added testFindSingleAttributeOnDependent and testFindListOnDependent to TestQueryCacheTableDependency, asserting cache-hit then dependent-table invalidation for the single-attribute and findMany paths (the count path was already covered).
  • All cache/query tests pass: 69 in ebean-test org.tests.cache.**, 929 in ebean-core cache + query packages.

 This is a replacement for #3150

 **1. Read `dependentTables` from the query plan (single source of truth)**

 `OrmQueryRequest` accumulated query-cache `dependentTables` into a field via
 `addDependentTables(...)`, fed from per-CQuery `dependentTables()` helpers. The
 `CQueryPlan` already owns this set, so the copying is redundant.

 - `putToQueryCache(...)` now reads `dependentTables` directly from the request's
   `CQueryPlan` at cache-put time.
 - Removed the `OrmQueryRequest.dependentTables` field and `addDependentTables(Set)`.
 - Removed the now-unused `dependentTables()` helpers from `CQuery`,
   `CQueryRowCount` and `CQueryFetchSingleAttribute` (and their `Set` imports).

 This is behaviour-preserving: each CQuery is built with exactly the plan stored
 under `request.queryPlanKey`, so `request.queryPlan().dependentTables()` is the
 same set the per-CQuery helpers returned.

 **2. Fix a query-plan trim race that could null the plan at put time**

 A freshly built `CQueryPlan` was stored in the plan cache with
 `lastQueryTime == 0`, making it immediately eligible for `trimQueryPlans`
 (runs every 60s; TTL default 300s) during its *first* execution. If the trim
 fired mid-execution, `request.queryPlan()` could return `null` at put time — and
 a cache entry with `null` dependentTables is never invalidated by table
 modifications (`TableModState.isValid`), i.e. latent stale data.

 - `CQueryPlanStats` now initialises `lastQueryTime` to construction time, so a
   new plan is only trim-eligible after a genuine TTL idle. `trimQueryPlans` is
   the sole consumer of `lastQueryTime()`, so this is safe.
 - `putToQueryCache(...)` skips the put when the plan is `null` (fail safe rather
   than caching an un-invalidatable entry) for the residual pathological case
   (a single query running past the TTL on first execution).

 ### Tests

 - Added `testFindSingleAttributeOnDependent` and `testFindListOnDependent` to
   `TestQueryCacheTableDependency`, asserting cache-hit then dependent-table
   invalidation for the single-attribute and findMany paths (the count path was
   already covered).
 - All cache/query tests pass: 69 in `ebean-test` `org.tests.cache.**`, 929 in
   `ebean-core` cache + query packages.
@rbygrave rbygrave self-assigned this Jun 23, 2026
@rbygrave rbygrave merged commit ca19a78 into master Jun 23, 2026
1 check passed
@rbygrave rbygrave deleted the feature/optimize-dependantTables branch June 23, 2026 10:44
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