Skip to content

feat(metaschema): refresh the schema cache on a timer and make it concurrency-safe - #1878

Open
rohilsurana wants to merge 3 commits into
mainfrom
feat/metaschema-cache-refresh
Open

feat(metaschema): refresh the schema cache on a timer and make it concurrency-safe#1878
rohilsurana wants to merge 3 commits into
mainfrom
feat/metaschema-cache-refresh

Conversation

@rohilsurana

Copy link
Copy Markdown
Member

Summary

Metaschemas hold the JSON schemas that validate entity metadata for users, organizations, groups, and roles. The metaschema service keeps every schema in an in-memory map on the service. Today that map is primed once at boot and after that it only changes on the pod that handled a write. In a multi-pod deployment a schema change made through the API reaches one pod but not the others, so those pods keep validating against the old schema until they restart. The map also has no lock, so concurrent request goroutines on one pod can race on it, which Go turns into a "concurrent map read and map write" panic.

This PR makes the cache safe for concurrent access and reloads it on a timer, so every pod picks up a schema change within a small bounded window.

Changes

  • Guard the schema cache with a sync.RWMutex and switch every service method to a pointer receiver.
  • Add a periodic reload that lists all schemas and swaps the cache, reusing the same robfig/cron pattern as the billing sync jobs.
  • Add Init to prime the cache and start the job, and Close to stop it.
  • Add the app.metaschema.refresh_interval config. It defaults to 1m, and 0 disables the job for single-pod, local, and test runs.
  • Wire Init and Close into server start and shutdown, and document the new setting.

Technical Details

  • Reads (Get, List, Validate) take a read lock. Writes (Create, Update, Delete, and the reload) take a write lock.
  • The writing pod still updates its own cache right away, so its change is visible at once. The timer carries the change to the other pods within one interval.
  • On a reload error the current cache is kept, so a database blip never blanks the schemas.
  • @every schedules from pod start, so pods that start together reload in lockstep. For this tiny table that query cost is small. Jitter or Postgres LISTEN/NOTIFY are noted as future options if it ever matters.
  • The receiver change is internal only. Method signatures do not change, so the MetaSchemaService interface and its generated mock stay valid.

Test Plan

  • Build and type checking passes (go build ./..., go vet)
  • New unit tests in core/metaschema pass under go test -race, covering concurrent access alongside a reload, reload picking up a new schema, cache kept on a list error, and the refresh-disabled path

@vercel

vercel Bot commented Aug 13, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
frontier Ready Ready Preview Aug 13, 2026 8:53am

@coderabbitai

coderabbitai Bot commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Added configurable metaschema cache refreshes, enabled by default every minute.
    • Metaschema data now refreshes automatically in the background and can be refreshed manually during startup.
    • Set the refresh interval to 0 to disable background refreshes while retaining startup loading.
  • Bug Fixes

    • Improved cache safety during concurrent metaschema operations.
    • Preserved the existing cache when a refresh fails.
  • Documentation

    • Documented the new metaschema refresh configuration and behavior.

Walkthrough

Changes

Metaschema cache lifecycle

Layer / File(s) Summary
Refresh configuration and service wiring
core/metaschema/config.go, pkg/server/config.go, config/sample.config.yaml, docs/content/docs/reference/configurations.mdx, cmd/serve.go, cmd/migrate.go
Adds the metaschema refresh interval configuration and passes it to service constructors. Server startup now initializes and closes the metaschema service.
Synchronized cache lifecycle
core/metaschema/service.go
Adds locked cache access, startup loading, optional periodic refresh, reload error handling, and refresh-job shutdown.
Cache concurrency and refresh validation
core/metaschema/service_test.go
Tests concurrent access, cache reloads, reload error retention, and disabled refresh scheduling.

Estimated code review effort: 4 (Complex) | ~45 minutes

Mergeability Score: 🟡 Moderate · up to 3a83e

A repository failure during startup can leave the service with an empty schema cache, allowing metadata validation to be skipped until a later refresh succeeds. Merge should wait for the initial load error to be handled so the service does not serve requests in this unsafe state.

Suggested reviewers: whoabhisheksah

🚥 Pre-merge checks | ✅ 2
✅ Passed checks (2 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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.

@coveralls

Copy link
Copy Markdown

Coverage Report for CI Build 31684086039

Coverage increased (+0.1%) to 48.379%

Details

  • Coverage increased (+0.1%) from the base build.
  • Patch coverage: 33 uncovered changes across 3 files (47 of 80 lines covered, 58.75%).
  • 3 coverage regressions across 1 file.

Uncovered Changes

File Changed Covered %
core/metaschema/service.go 72 47 65.28%
cmd/serve.go 7 0 0.0%
cmd/migrate.go 1 0 0.0%

Coverage Regressions

3 previously-covered lines in 1 file lost coverage.

File Lines Losing Coverage Coverage
core/metaschema/service.go 3 55.91%

Coverage Stats

Coverage Status
Relevant Lines: 39815
Covered Lines: 19262
Line Coverage: 48.38%
Coverage Strength: 15.53 hits per line

💛 - Coveralls

@rohilsurana
rohilsurana marked this pull request as ready for review August 13, 2026 11:11
@rohilsurana
rohilsurana requested review from AmanGIT07 and whoAbhishekSah and removed request for AmanGIT07 August 13, 2026 11:13

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1


ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 59e3f98a-7150-490f-a857-75e9217738c5

📥 Commits

Reviewing files that changed from the base of the PR and between 229ea89 and 3a83e06.

📒 Files selected for processing (8)
  • cmd/migrate.go
  • cmd/serve.go
  • config/sample.config.yaml
  • core/metaschema/config.go
  • core/metaschema/service.go
  • core/metaschema/service_test.go
  • docs/content/docs/reference/configurations.mdx
  • pkg/server/config.go

Comment on lines +154 to +159
func (s *Service) Init(ctx context.Context) error {
s.reload(ctx)
s.mu.RLock()
count := len(s.metaSchemaCache)
s.mu.RUnlock()
s.logger.InfoContext(ctx, "metaschemas loaded", "count", count)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Return the initial cache-load error.

reload logs a Repository.List failure but discards it. Init then returns nil, so cmd/serve.go Line 167 starts the server with an empty cache. Validate returns nil for every absent schema until a later refresh succeeds.

Return the first reload error from Init. Stop startup, or wait for a successful initial load. Keep the current retain-and-log behavior for later scheduled reload failures.

Proposed direction
 func (s *Service) Init(ctx context.Context) error {
-	s.reload(ctx)
+	if err := s.reload(ctx); err != nil {
+		return err
+	}
 	s.mu.RLock()

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