Skip to content

improvement: targeted schema invalidation for type/function/aggregate events#885

Open
mykaul wants to merge 2 commits into
scylladb:masterfrom
mykaul:fix/targeted-schema-invalidation
Open

improvement: targeted schema invalidation for type/function/aggregate events#885
mykaul wants to merge 2 commits into
scylladb:masterfrom
mykaul:fix/targeted-schema-invalidation

Conversation

@mykaul

@mykaul mykaul commented May 19, 2026

Copy link
Copy Markdown

Previously, schema change events for types, functions, and aggregates invalidated the entire keyspace cache, forcing a full schema reload. This was unnecessary and expensive.

Now each category has its own dirty flag and monotonic generation counter. Only the affected metadata (types, functions, aggregates) is re-fetched on the next GetKeyspace call, while tables, views, and indexes remain cached.

Key design points:

  • Generation counters prevent clearing invalidations that arrived during an in-flight partial refresh.
  • Function invalidation cascades to aggregates since AggregateMetadata embeds FunctionMetadata by value.
  • Full keyspace refresh merges newer invalidation state to avoid dropping concurrent events.

Fixes: #730

@mykaul
mykaul force-pushed the fix/targeted-schema-invalidation branch from caeebc7 to d93a2bd Compare May 19, 2026 10:07
@mykaul
mykaul force-pushed the fix/targeted-schema-invalidation branch from d93a2bd to d14a0f0 Compare May 29, 2026 09:36
@coderabbitai

coderabbitai Bot commented May 29, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: f42bc4dc-0625-4c51-895a-cf72aefbf805

📥 Commits

Reviewing files that changed from the base of the PR and between 73c56d0 and 7073a44.

📒 Files selected for processing (2)
  • events_unit_test.go
  • metadata_scylla.go
🚧 Files skipped from review as they are similar to previous changes (2)
  • events_unit_test.go
  • metadata_scylla.go

📝 Walkthrough

Walkthrough

This PR changes type, function, and aggregate schema invalidation from whole-keyspace removal to per-category invalidation with generation tracking. Metadata refresh logic now preserves concurrent invalidations and performs targeted partial refreshes. Event handling and tests were updated to verify that keyspace entries remain cached and only affected system schema categories are queried.

Sequence Diagram(s)

sequenceDiagram
  participant SchemaChangeEvent
  participant handleSchemaEvent
  participant metadataDescriber
  participant getKeyspaceInternal
  participant refreshPartialKeyspaceSchema
  participant system_schema

  SchemaChangeEvent->>handleSchemaEvent: Type/function/aggregate schema change
  handleSchemaEvent->>metadataDescriber: Invalidate affected category
  metadataDescriber->>getKeyspaceInternal: Mark cached metadata invalidated
  getKeyspaceInternal->>refreshPartialKeyspaceSchema: Refresh stale categories
  refreshPartialKeyspaceSchema->>system_schema: Query affected metadata
  refreshPartialKeyspaceSchema-->>getKeyspaceInternal: Apply refreshed metadata
Loading

Suggested reviewers: dkropachev

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the core change: targeted schema invalidation for type, function, and aggregate events.
Description check ✅ Passed The description matches the changeset and explains the targeted invalidation approach and its concurrency behavior.
Linked Issues check ✅ Passed The PR fulfills #730 by replacing full keyspace invalidation with targeted refreshes for types, functions, and aggregates.
Out of Scope Changes check ✅ Passed The changes stay within the issue scope and only add supporting cache state, refresh logic, and tests for targeted invalidation.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@mykaul

mykaul commented May 29, 2026

Copy link
Copy Markdown
Author

Expert correctness review — targeted schema invalidation

Performed a deep Go correctness review of this PR (518/-54 across 4 files). Focus areas: targeted invalidation correctness for TYPE/FUNCTION/AGGREGATE events, dependency completeness, concurrency on the schema cache, error handling, and edge cases.

Verdict

The implementation is correct and well-engineered. No HIGH/MEDIUM correctness issues were found, so no code changes were required. Detailed analysis below.

Correctness analysis

Targeted invalidation does not miss dependent entries.

  • TypeMetadata stores FieldNames/FieldTypes as plain strings, and table/column metadata likewise stores column types as strings. There is no compiled cross-reference from tables/views into UDTs in the cache, so a UDT change does not require invalidating dependent tables. Marking only the types category dirty is sound.
  • The function→aggregate dependency is handled: invalidateFunctions() correctly also sets aggregatesInvalidated (aggregates embed copies of FinalFunc/StateFunc), and refreshPartialKeyspaceSchema always re-fetches the function list when aggregates need recompilation, recompiling aggregate metadata against fresh functions within the same atomic COW update.

Concurrency / data races.

  • Cache mutations go through the COW updateKeyspace/replaceKeyspace pattern under c.mu, cloning before mutate-and-store; readers get an immutable published snapshot. No shared-map mutation races.
  • The generation counters (typesInvalidationGen/functionsInvalidationGen/aggregatesInvalidationGen) correctly arbitrate the race between an in-flight full refresh and concurrent invalidation events: a full refresh that started at generation N will not clobber a newer invalidation (gen > N is preserved in the replaceKeyspace merge), and refreshPartialKeyspaceSchema only clears a dirty flag when the generation still matches the snapshot it fetched against. Verified the cross-category gating is internally consistent (aggregate apply and the "functions-fetched-for-aggregates" apply share the same aggregates-generation guard; in the functions path both gens move together).
  • Ran the race detector on the schema-event/COW/partial-refresh tests (-race -tags unit): clean.

Error handling. Fetch/parse errors from errgroup abort the partial refresh and propagate without clearing any dirty flag or partially publishing — the cache is left consistently dirty and will be retried on the next access. updateKeyspace/replaceKeyspace returning "not applied" (keyspace removed mid-flight) falls back to a full deduplicated refresh.

Edge cases. Verified: event for an uncached keyspace is a safe no-op; DROP keyspace removes the entry entirely (recreated keyspace starts with clean flags); nil cache map handled in replaceKeyspace; apply-time generation mismatch is reconciled on the next iteration of the getKeyspaceInternal refresh loop.

Minor (LOW, non-blocking, no change made)

getKeyspaceInternal's for metadata.needsPartialRefresh() loop re-reads the published keyspace each iteration and converges correctly. Under a sustained storm of invalidation events for the same keyspace it could iterate extra times before converging — a liveness edge, not a correctness defect, and the behavior is conservatively safe. Left as-is per minimal-change intent.

Tests

  • go build ./..., go vet ./...: clean.
  • go test -count=1 ./...: 891 passed across 43 packages.
  • PR-relevant unit-tagged tests (-tags unit, incl. -race): all pass (49 tests, race-clean).
  • Note: a handful of TestPolicyConnPoolSSL/TestShardAwarePort* cases fail in a fresh worktree with "Failed parsing or appending certs". This is unrelated to this PR — testdata/pki/*.crt/*.key are generated (gitignored) artifacts absent in a clean checkout; they pass once certs are generated. Not a regression.

Rebase

Cleanly rebased onto current origin/master (no conflicts), re-ran build/vet/full test suite green, then force-pushed with --force-with-lease.

Benchmarking

Not applicable — this is a correctness fix to schema-event invalidation, not a hot-path perf change. No benchmarks run.

@mykaul
mykaul marked this pull request as ready for review June 5, 2026 06:19
@mykaul

mykaul commented Jun 5, 2026

Copy link
Copy Markdown
Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 5, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@mykaul
mykaul requested a review from Copilot July 4, 2026 15:53
@mykaul mykaul changed the title fix: targeted schema invalidation for type/function/aggregate events improvement: targeted schema invalidation for type/function/aggregate events Jul 4, 2026

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

This PR improves schema-cache efficiency by introducing targeted invalidation and refresh for type/function/aggregate schema change events, avoiding expensive full keyspace reloads when only these lightweight schema objects change.

Changes:

  • Add per-category dirty flags and monotonic generation counters for types, functions, and aggregates in KeyspaceMetadata.
  • Implement partial keyspace refresh (refreshPartialKeyspaceSchema) and integrate it into GetKeyspace flow so only invalidated categories are re-fetched.
  • Update schema event handling and tests to reflect targeted invalidation semantics (keyspace entry remains cached).

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

File Description
metadata_scylla.go Adds targeted invalidation flags/generations, atomic replace-with-merge on full refresh, and partial refresh logic for types/functions/aggregates.
metadata_scylla_test.go Adds unit tests for the new invalidation methods, generation bumps, and cloning behavior.
events.go Routes type/function/aggregate schema change events to targeted invalidation methods instead of clearing the entire keyspace cache.
events_unit_test.go Updates schema event unit tests to expect keyspace retention and (for types) targeted refresh queries.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread metadata_scylla.go
Comment thread events_unit_test.go
@mykaul
mykaul force-pushed the fix/targeted-schema-invalidation branch 2 times, most recently from ef6653d to 93f4086 Compare July 4, 2026 17:21

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
metadata_scylla.go (1)

1079-1090: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Factor the aggregate function-linking into a shared helper. Both refresh paths currently resolve only FinalFunc and StateFunc; keeping that logic in one place would avoid drift if the linking rules change later.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@metadata_scylla.go` around lines 1079 - 1090, The aggregate function-linking
logic in the metadata refresh path is duplicated and should be centralized. Move
the resolution of AggregateMetadata.FinalFunc and AggregateMetadata.StateFunc
into a shared helper used by the ks.Aggregates rebuild flow and the other
refresh path, so both paths call the same linking logic from metadata_scylla.go
and stay consistent if the rules change.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@metadata_scylla.go`:
- Around line 1079-1090: The aggregate function-linking logic in the metadata
refresh path is duplicated and should be centralized. Move the resolution of
AggregateMetadata.FinalFunc and AggregateMetadata.StateFunc into a shared helper
used by the ks.Aggregates rebuild flow and the other refresh path, so both paths
call the same linking logic from metadata_scylla.go and stay consistent if the
rules change.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 68c829af-bd11-4c25-8cb2-5c95baabbd06

📥 Commits

Reviewing files that changed from the base of the PR and between d14a0f0 and 93f4086.

📒 Files selected for processing (4)
  • events.go
  • events_unit_test.go
  • metadata_scylla.go
  • metadata_scylla_test.go
🚧 Files skipped from review as they are similar to previous changes (3)
  • events.go
  • events_unit_test.go
  • metadata_scylla_test.go

Previously, schema change events for types, functions, and aggregates
invalidated the entire keyspace cache, forcing a full schema reload.
This was unnecessary and expensive.

Now each category has its own dirty flag and monotonic generation
counter. Only the affected metadata (types, functions, aggregates) is
re-fetched on the next GetKeyspace call, while tables, views, and
indexes remain cached.

Key design points:
- Generation counters prevent clearing invalidations that arrived
  during an in-flight partial refresh.
- Function invalidation cascades to aggregates since AggregateMetadata
  embeds FunctionMetadata by value.
- Full keyspace refresh merges newer invalidation state to avoid
  dropping concurrent events.

Fixes: scylladb#730
@mykaul
mykaul force-pushed the fix/targeted-schema-invalidation branch from 93f4086 to 033638c Compare July 19, 2026 05:01
@coderabbitai
coderabbitai Bot requested a review from dkropachev July 19, 2026 05:02

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
events_unit_test.go (1)

926-936: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Ensure partial refresh completed and cleared invalidation flags correctly.

Consider adding expectNoRequery: true to this test case to verify that the partial refresh loop properly clears the typesInvalidated flag, preventing subsequent calls to GetTable from needlessly re-querying the system schema. This provides consistent behavioral coverage with the GetKeyspace equivalent test case.

💡 Proposed fix
 			{
 				name: "after type event: refreshes only types",
 				knownKeyspaces: map[string][]tableInfo{
 					"test_ks": {{name: "tbl_a", columns: []columnInfo{{name: "id", kind: "partition_key", position: 0}}}},
 				},
 				populateKs: map[string][]string{"test_ks": {"tbl_a"}},
 				event:      &frm.SchemaChangeType{Change: "CREATED", Keyspace: "test_ks", Object: "my_type"},
 				getTable:   [2]string{"test_ks", "tbl_a"},
 				expectedQueries: map[string]int{
 					"SELECT * FROM system_schema.types WHERE keyspace_name = ?": 1,
 				},
+				expectNoRequery: true,
 			},
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@events_unit_test.go` around lines 926 - 936, Update the “after type event:
refreshes only types” test case to set expectNoRequery: true, ensuring the
partial refresh clears typesInvalidated and subsequent GetTable calls do not
re-query system_schema.types.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@metadata_scylla.go`:
- Around line 937-954: Update the merge closure in the keyspace metadata refresh
flow to keep invalidation flags, generations, and their associated data from the
same source. When current.typesInvalidationGen, functionsInvalidationGen, or
aggregatesInvalidationGen is newer than the captured generation, copy the
corresponding current Types, Functions, or Aggregates along with its flag and
generation onto replacement, preserving lockstep data and metadata.

---

Nitpick comments:
In `@events_unit_test.go`:
- Around line 926-936: Update the “after type event: refreshes only types” test
case to set expectNoRequery: true, ensuring the partial refresh clears
typesInvalidated and subsequent GetTable calls do not re-query
system_schema.types.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 8eb05822-6d58-40e7-8831-09a67df68163

📥 Commits

Reviewing files that changed from the base of the PR and between 93f4086 and 73c56d0.

📒 Files selected for processing (4)
  • events.go
  • events_unit_test.go
  • metadata_scylla.go
  • metadata_scylla_test.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • events.go

Comment thread metadata_scylla.go
@mykaul
mykaul force-pushed the fix/targeted-schema-invalidation branch from 73c56d0 to 7073a44 Compare July 20, 2026 14: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.

Type/function/aggregate schema events invalidate entire keyspace cache unnecessarily

2 participants