Skip to content

add cached tokens to matview; allow matview refersh on the fly - #5507

Merged
akshaydeo merged 1 commit into
devfrom
07-24-add_cached_tokens_to_matview_allow_matview_refersh_on_the_fly
Jul 23, 2026
Merged

add cached tokens to matview; allow matview refersh on the fly#5507
akshaydeo merged 1 commit into
devfrom
07-24-add_cached_tokens_to_matview_allow_matview_refersh_on_the_fly

Conversation

@akshaydeo

@akshaydeo akshaydeo commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Summary

This PR adds two improvements to the materialized view read path: cache-hit statistics are now served from the hybrid aggregate (interior buckets from mv_logs_hourly, boundary slivers classified raw) instead of a separate full-window raw scan, and a runtime self-heal mechanism automatically recovers from missing or stale-shaped materialized views without requiring a process restart.

Changes

  • Cache hits in the hybrid aggregate: Three new columns (direct_cache_hits, semantic_cache_hits, cache_debug_count) are added to mv_logs_hourly using the same cacheDebugJSONGuard and cacheDebugHitTypeExpr expressions shared across the matview DDL, boundary sliver queries, and aggregateCacheHits. cache_debug_count preserves the nil contract: when no row in the window carried valid cache_debug JSON the fields are omitted from the response, and when cache rows exist but none were direct/semantic explicit zeros are returned. The previous approach issued a separate full-window raw scan for cache hits after the hybrid aggregate completed; that scan is removed.

  • Runtime matview self-heal (matviewheal.go): isMatViewShapeError classifies PostgreSQL error codes 42P01 (undefined table), 42703 (undefined column), and 55000 (object not in prerequisite state) as shape errors. fallBackToRaw is called at every matview dispatch site — on a shape error it disables the matview read path process-wide, logs a warning, and triggers a single-flight background repair via triggerMatViewSelfHeal. The repair runs ensureMatViews then refreshMatViews and re-enables the path on success. A 30-second cooldown (matViewHealCooldown) bounds repair frequency; while broken, every request continues succeeding via the raw fallback. Two new atomic fields (matViewHealInFlight, matViewHealLastAttempt) are added to RDBLogStore.

  • Shared SQL constants: The inline regex strings for the cache debug guard and hit-type extractor are replaced with named constants cacheDebugJSONGuard and cacheDebugHitTypeExpr, used consistently across the matview DDL, applyFilters, rawTerminalStatsAgg, and aggregateCacheHits.

  • Tests: TestGetStatsMatViewCacheHitsHybrid verifies the hybrid cache-hit path including nil and zero contracts and agreement with the raw path. TestMatViewShapeErrorFallsBackAndSelfHeals, TestMatViewStaleShapeFallsBackAndSelfHeals, and TestFilterMatViewShapeErrorFallsBack cover the 42P01, 42703, and filter-view drop scenarios end-to-end, including background self-heal convergence.

Type of change

  • Bug fix
  • Feature
  • Refactor
  • Documentation
  • Chore/CI

Affected areas

  • Core (Go)
  • Transports (HTTP)
  • Providers/Integrations
  • Plugins
  • UI (React)
  • Docs

How to test

go test ./framework/logstore/... -run TestGetStatsMatViewCacheHitsHybrid
go test ./framework/logstore/... -run TestMatViewShapeErrorFallsBackAndSelfHeals
go test ./framework/logstore/... -run TestMatViewStaleShapeFallsBackAndSelfHeals
go test ./framework/logstore/... -run TestFilterMatViewShapeErrorFallsBack
go test ./framework/logstore/... -run TestIsMatViewShapeError
go test ./framework/logstore/...

The self-heal tests drop or replace mv_logs_hourly mid-run and assert that reads continue returning correct results from the raw table with no error, that matViewsReady is set to false immediately, and that the view is recreated with the correct shape within 90 seconds.

Breaking changes

  • No

The new matview columns require a schema migration. On first deploy, repairMatViewShapes detects the missing columns, drops and recreates mv_logs_hourly, and the self-heal path handles any replica that reads before the rebuild completes.

Related issues

Closes #5384

Security considerations

No auth, secrets, PII, or sandboxing changes. The new SQL expressions are constants composed only of built-in PostgreSQL operators and are not user-controlled.

Checklist

  • I read docs/contributing/README.md and followed the guidelines
  • I added/updated tests where appropriate
  • I updated documentation where needed
  • I verified builds succeed (Go and UI)
  • I verified the CI pipeline passes locally if applicable

@coderabbitai

coderabbitai Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

You’ve reached a temporary PR review limit under our Fair Usage Limits Policy.

Your recent review volume is higher than typical usage, so adaptive limits are currently applied.

Next review available in: 15 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 03acfd19-2b8f-43cc-984d-101dd19e0f08

📥 Commits

Reviewing files that changed from the base of the PR and between 39d00a1 and d704eac.

📒 Files selected for processing (8)
  • framework/configstore/tables/webhooks.go
  • framework/logstore/logstoreparity_test.go
  • framework/logstore/matview_count_test.go
  • framework/logstore/matviewheal.go
  • framework/logstore/matviewheal_test.go
  • framework/logstore/matviews.go
  • framework/logstore/rdb.go
  • framework/logstore/tables.go
📝 Walkthrough

Walkthrough

Materialized-view statistics now include cache-hit aggregates, and PostgreSQL matview read failures fall back to raw tables. Shape failures trigger throttled background repair. Webhook-related enum values gain database serialization, and ranking canonicalization uses live comparison keys.

Changes

Matview resilience and statistics

Layer / File(s) Summary
Hybrid cache statistics
framework/logstore/matviews.go, framework/logstore/rdb.go, framework/logstore/matview_count_test.go
Matview and raw aggregation paths compute direct, semantic, and valid-JSON cache counts using shared expressions, while tests verify hybrid/raw parity and nil-versus-zero behavior.
Matview read fallback and ordering
framework/logstore/rdb.go, framework/logstore/matviews.go
Matview-backed count, statistics, histogram, ranking, and distinct-value queries continue through raw implementations after shape errors, with deterministic ranking tie-breakers.
Self-healing repair
framework/logstore/matviewheal.go, framework/logstore/matviewheal_test.go
Missing or stale shapes disable matview reads and start cooldown-limited single-flight repair through ensureMatViews and refreshMatViews; tests verify fallback and recovery.

Database serialization and parity

Layer / File(s) Summary
Enum database serialization
framework/configstore/tables/webhooks.go, framework/logstore/tables.go
WebhookEvent and WebhookDeliveryOutcome implement driver.Valuer by returning their string values.
Ranking parity ordering
framework/logstore/logstoreparity_test.go
Ranking canonicalization computes comparison keys from live slice elements during sorting.

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

Possibly related PRs

Suggested reviewers: pratham-mishra04, impoiler

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning Issue #5384 requires shape-based readiness gating, but the summary shows error-triggered fallback/self-heal rather than the explicit matViewShapesReady flow. Add the shape-check gate and related ensureMatViews signature/call-site updates so matViewsReady stays off until all required columns are verified.
Out of Scope Changes check ⚠️ Warning The PR includes unrelated webhook driver.Valuer additions, which are outside the matview cache-hit and self-heal objectives. Move the webhook Value() changes to a separate PR unless they are required for the matview fix.
✅ Passed checks (3 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Title check ✅ Passed The title is specific and matches the main changes: hybrid cache-hit matview support and automatic matview self-healing.
Description check ✅ Passed The description follows the template well with summary, changes, testing, breaking changes, issues, security, and checklist sections.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch 07-24-add_cached_tokens_to_matview_allow_matview_refersh_on_the_fly

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

@CLAassistant

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

@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

🧹 Nitpick comments (1)
framework/logstore/matviewheal.go (1)

79-108: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Add panic recovery to the self-heal goroutine.

The goroutine has no recover(); a panic inside ensureMatViews/refreshMatViews (not visible in this diff) would propagate up and crash the entire process. For a best-effort background repair path that is expected to fail gracefully (as the surrounding logging/fallback design already assumes), an unrecovered panic is a disproportionate blast radius.

🛡️ Proposed fix
 	go func() {
 		defer s.matViewHealInFlight.Store(false)
+		defer func() {
+			if r := recover(); r != nil && s.logger != nil {
+				s.logger.Warn(fmt.Sprintf("logstore: matview self-heal panicked: %v", r))
+			}
+		}()
 		if time.Since(time.Unix(0, s.matViewHealLastAttempt.Load())) < matViewHealCooldown {
 			return
 		}
🤖 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 `@framework/logstore/matviewheal.go` around lines 79 - 108, Add panic recovery
to the background goroutine created by triggerMatViewSelfHeal, ensuring the
existing matViewHealInFlight reset still runs and a recovered panic is logged
through s.logger when available. Keep the self-heal path best-effort so panics
from ensureMatViews or refreshMatViews do not escape and terminate the process.
🤖 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 `@framework/logstore/matviewheal.go`:
- Around line 91-102: The matview self-heal flow must retry after
refreshMatViews fails instead of permanently remaining on raw tables. Update the
refresh failure handling around ensureMatViews and refreshMatViews to re-arm or
independently schedule self-heal retries without requiring another shape error,
while preserving the existing warning and raw-table fallback behavior.

---

Nitpick comments:
In `@framework/logstore/matviewheal.go`:
- Around line 79-108: Add panic recovery to the background goroutine created by
triggerMatViewSelfHeal, ensuring the existing matViewHealInFlight reset still
runs and a recovered panic is logged through s.logger when available. Keep the
self-heal path best-effort so panics from ensureMatViews or refreshMatViews do
not escape and terminate the process.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 2a51f375-8f28-47f8-8ea1-e471b5e6149d

📥 Commits

Reviewing files that changed from the base of the PR and between 82d99fb and 0d42413.

📒 Files selected for processing (5)
  • framework/logstore/matview_count_test.go
  • framework/logstore/matviewheal.go
  • framework/logstore/matviewheal_test.go
  • framework/logstore/matviews.go
  • framework/logstore/rdb.go

Comment thread framework/logstore/matviewheal.go
@akshaydeo
akshaydeo force-pushed the 07-24-add_cached_tokens_to_matview_allow_matview_refersh_on_the_fly branch from 0d42413 to 39d00a1 Compare July 23, 2026 22:16

@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.

🧹 Nitpick comments (1)
framework/logstore/matview_count_test.go (1)

270-346: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Docstring claims raw/matview parity for the nil and zero contracts, but only the main window checks it.

The doc comment says the test verifies the matview path "agrees exactly with the raw path, including the nil contract" and the zero contract. In the actual body, store.matViewsReady is only flipped to false and compared around the main window (lines 318-325); the nil-contract window (329-334) and zero-contract window (338-345) are only ever exercised through the matview path (matViewsReady stays true), so the raw-path equivalence for those two contracts is asserted by the docstring but not actually tested.

🧪 Proposed fix to close the coverage gap
 	filters = SearchFilters{StartTime: &nilStart, EndTime: &nilEnd}
 	require.True(t, store.canUseMatViewForFreshAggregate(filters))
 	stats, err = store.GetStats(ctx, filters)
 	require.NoError(t, err)
 	assert.Nil(t, stats.DirectCacheHits, "no cache rows -> nil, matching the raw path contract")
 	assert.Nil(t, stats.SemanticCacheHits)
+
+	store.matViewsReady.Store(false)
+	rawStats, err = store.GetStats(ctx, filters)
+	require.NoError(t, err)
+	assert.Nil(t, rawStats.DirectCacheHits, "raw path must also stay nil for the same window")
+	assert.Nil(t, rawStats.SemanticCacheHits)
+	store.matViewsReady.Store(true)

 	// Zero contract: cache rows exist but none direct/semantic -> explicit
 	// zeros on both paths.
 	filters = SearchFilters{StartTime: &zeroStart, EndTime: &zeroEnd}
 	require.True(t, store.canUseMatViewForFreshAggregate(filters))
 	stats, err = store.GetStats(ctx, filters)
 	require.NoError(t, err)
 	require.NotNil(t, stats.DirectCacheHits, "cache rows present -> explicit zeros, not omission")
 	require.NotNil(t, stats.SemanticCacheHits)
 	assert.Equal(t, int64(0), *stats.DirectCacheHits)
 	assert.Equal(t, int64(0), *stats.SemanticCacheHits)
+
+	store.matViewsReady.Store(false)
+	rawStats, err = store.GetStats(ctx, filters)
+	require.NoError(t, err)
+	require.NotNil(t, rawStats.DirectCacheHits)
+	require.NotNil(t, rawStats.SemanticCacheHits)
+	assert.Equal(t, int64(0), *rawStats.DirectCacheHits)
+	assert.Equal(t, int64(0), *rawStats.SemanticCacheHits)
+	store.matViewsReady.Store(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 `@framework/logstore/matview_count_test.go` around lines 270 - 346, Extend
TestGetStatsMatViewCacheHitsHybrid so the nil-contract and zero-contract windows
are each executed through both matview and raw paths. Capture and assert the
matview results first, set store.matViewsReady to false and repeat GetStats for
the same filters, then compare direct and semantic cache-hit
pointers/values—including nil versus explicit zero—and restore matViewsReady
afterward.
🤖 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 `@framework/logstore/matview_count_test.go`:
- Around line 270-346: Extend TestGetStatsMatViewCacheHitsHybrid so the
nil-contract and zero-contract windows are each executed through both matview
and raw paths. Capture and assert the matview results first, set
store.matViewsReady to false and repeat GetStats for the same filters, then
compare direct and semantic cache-hit pointers/values—including nil versus
explicit zero—and restore matViewsReady afterward.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: bf6481fd-11ef-46e9-9d52-301d9acdc3d6

📥 Commits

Reviewing files that changed from the base of the PR and between 0d42413 and 39d00a1.

📒 Files selected for processing (8)
  • framework/configstore/tables/webhooks.go
  • framework/logstore/logstoreparity_test.go
  • framework/logstore/matview_count_test.go
  • framework/logstore/matviewheal.go
  • framework/logstore/matviewheal_test.go
  • framework/logstore/matviews.go
  • framework/logstore/rdb.go
  • framework/logstore/tables.go
🚧 Files skipped from review as they are similar to previous changes (4)
  • framework/logstore/matviewheal.go
  • framework/logstore/matviews.go
  • framework/logstore/matviewheal_test.go
  • framework/logstore/rdb.go

@akshaydeo
akshaydeo force-pushed the 07-24-add_cached_tokens_to_matview_allow_matview_refersh_on_the_fly branch from 39d00a1 to 5b4b7fe Compare July 23, 2026 22:44
coderabbitai[bot]
coderabbitai Bot previously approved these changes Jul 23, 2026
@akshaydeo
akshaydeo force-pushed the 07-23-fix_matviews_time-bounded_windows_the_count branch from 82d99fb to f38a29f Compare July 23, 2026 22:57
@akshaydeo
akshaydeo force-pushed the 07-24-add_cached_tokens_to_matview_allow_matview_refersh_on_the_fly branch from 5b4b7fe to 560241a Compare July 23, 2026 22:57
@akshaydeo
akshaydeo changed the base branch from 07-23-fix_matviews_time-bounded_windows_the_count to graphite-base/5507 July 23, 2026 22:59
@akshaydeo
akshaydeo force-pushed the graphite-base/5507 branch from f38a29f to b5890b5 Compare July 23, 2026 23:00
@akshaydeo
akshaydeo force-pushed the 07-24-add_cached_tokens_to_matview_allow_matview_refersh_on_the_fly branch from 560241a to 948326e Compare July 23, 2026 23:00
@graphite-app
graphite-app Bot changed the base branch from graphite-base/5507 to dev July 23, 2026 23:00
@graphite-app
graphite-app Bot dismissed coderabbitai[bot]’s stale review July 23, 2026 23:00

The base branch was changed.

@akshaydeo
akshaydeo force-pushed the 07-24-add_cached_tokens_to_matview_allow_matview_refersh_on_the_fly branch from 948326e to d704eac Compare July 23, 2026 23:00

akshaydeo commented Jul 23, 2026

Copy link
Copy Markdown
Contributor Author

Merge activity

  • Jul 23, 11:12 PM UTC: A user started a stack merge that includes this pull request via Graphite.
  • Jul 23, 11:12 PM UTC: @akshaydeo merged this pull request with Graphite.

@akshaydeo
akshaydeo merged commit d979bb8 into dev Jul 23, 2026
13 of 14 checks passed
@akshaydeo
akshaydeo deleted the 07-24-add_cached_tokens_to_matview_allow_matview_refersh_on_the_fly branch July 23, 2026 23:12
akshaydeo added a commit that referenced this pull request Jul 24, 2026
## Summary

This PR adds two improvements to the materialized view read path: cache-hit statistics are now served from the hybrid aggregate (interior buckets from `mv_logs_hourly`, boundary slivers classified raw) instead of a separate full-window raw scan, and a runtime self-heal mechanism automatically recovers from missing or stale-shaped materialized views without requiring a process restart.

## Changes

- **Cache hits in the hybrid aggregate**: Three new columns (`direct_cache_hits`, `semantic_cache_hits`, `cache_debug_count`) are added to `mv_logs_hourly` using the same `cacheDebugJSONGuard` and `cacheDebugHitTypeExpr` expressions shared across the matview DDL, boundary sliver queries, and `aggregateCacheHits`. `cache_debug_count` preserves the nil contract: when no row in the window carried valid `cache_debug` JSON the fields are omitted from the response, and when cache rows exist but none were direct/semantic explicit zeros are returned. The previous approach issued a separate full-window raw scan for cache hits after the hybrid aggregate completed; that scan is removed.

- **Runtime matview self-heal** (`matviewheal.go`): `isMatViewShapeError` classifies PostgreSQL error codes `42P01` (undefined table), `42703` (undefined column), and `55000` (object not in prerequisite state) as shape errors. `fallBackToRaw` is called at every matview dispatch site — on a shape error it disables the matview read path process-wide, logs a warning, and triggers a single-flight background repair via `triggerMatViewSelfHeal`. The repair runs `ensureMatViews` then `refreshMatViews` and re-enables the path on success. A 30-second cooldown (`matViewHealCooldown`) bounds repair frequency; while broken, every request continues succeeding via the raw fallback. Two new atomic fields (`matViewHealInFlight`, `matViewHealLastAttempt`) are added to `RDBLogStore`.

- **Shared SQL constants**: The inline regex strings for the cache debug guard and hit-type extractor are replaced with named constants `cacheDebugJSONGuard` and `cacheDebugHitTypeExpr`, used consistently across the matview DDL, `applyFilters`, `rawTerminalStatsAgg`, and `aggregateCacheHits`.

- **Tests**: `TestGetStatsMatViewCacheHitsHybrid` verifies the hybrid cache-hit path including nil and zero contracts and agreement with the raw path. `TestMatViewShapeErrorFallsBackAndSelfHeals`, `TestMatViewStaleShapeFallsBackAndSelfHeals`, and `TestFilterMatViewShapeErrorFallsBack` cover the 42P01, 42703, and filter-view drop scenarios end-to-end, including background self-heal convergence.

## Type of change

- [x] Bug fix
- [x] Feature
- [ ] Refactor
- [ ] Documentation
- [ ] Chore/CI

## Affected areas

- [x] Core (Go)
- [ ] Transports (HTTP)
- [ ] Providers/Integrations
- [ ] Plugins
- [ ] UI (React)
- [ ] Docs

## How to test

```sh
go test ./framework/logstore/... -run TestGetStatsMatViewCacheHitsHybrid
go test ./framework/logstore/... -run TestMatViewShapeErrorFallsBackAndSelfHeals
go test ./framework/logstore/... -run TestMatViewStaleShapeFallsBackAndSelfHeals
go test ./framework/logstore/... -run TestFilterMatViewShapeErrorFallsBack
go test ./framework/logstore/... -run TestIsMatViewShapeError
go test ./framework/logstore/...
```

The self-heal tests drop or replace `mv_logs_hourly` mid-run and assert that reads continue returning correct results from the raw table with no error, that `matViewsReady` is set to false immediately, and that the view is recreated with the correct shape within 90 seconds.

## Breaking changes

- [x] No

The new matview columns require a schema migration. On first deploy, `repairMatViewShapes` detects the missing columns, drops and recreates `mv_logs_hourly`, and the self-heal path handles any replica that reads before the rebuild completes.

## Related issues

Closes #5384

## Security considerations

No auth, secrets, PII, or sandboxing changes. The new SQL expressions are constants composed only of built-in PostgreSQL operators and are not user-controlled.

## Checklist

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [x] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [x] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable
BearTS pushed a commit that referenced this pull request Jul 27, 2026
This PR adds two improvements to the materialized view read path: cache-hit statistics are now served from the hybrid aggregate (interior buckets from `mv_logs_hourly`, boundary slivers classified raw) instead of a separate full-window raw scan, and a runtime self-heal mechanism automatically recovers from missing or stale-shaped materialized views without requiring a process restart.

- **Cache hits in the hybrid aggregate**: Three new columns (`direct_cache_hits`, `semantic_cache_hits`, `cache_debug_count`) are added to `mv_logs_hourly` using the same `cacheDebugJSONGuard` and `cacheDebugHitTypeExpr` expressions shared across the matview DDL, boundary sliver queries, and `aggregateCacheHits`. `cache_debug_count` preserves the nil contract: when no row in the window carried valid `cache_debug` JSON the fields are omitted from the response, and when cache rows exist but none were direct/semantic explicit zeros are returned. The previous approach issued a separate full-window raw scan for cache hits after the hybrid aggregate completed; that scan is removed.

- **Runtime matview self-heal** (`matviewheal.go`): `isMatViewShapeError` classifies PostgreSQL error codes `42P01` (undefined table), `42703` (undefined column), and `55000` (object not in prerequisite state) as shape errors. `fallBackToRaw` is called at every matview dispatch site — on a shape error it disables the matview read path process-wide, logs a warning, and triggers a single-flight background repair via `triggerMatViewSelfHeal`. The repair runs `ensureMatViews` then `refreshMatViews` and re-enables the path on success. A 30-second cooldown (`matViewHealCooldown`) bounds repair frequency; while broken, every request continues succeeding via the raw fallback. Two new atomic fields (`matViewHealInFlight`, `matViewHealLastAttempt`) are added to `RDBLogStore`.

- **Shared SQL constants**: The inline regex strings for the cache debug guard and hit-type extractor are replaced with named constants `cacheDebugJSONGuard` and `cacheDebugHitTypeExpr`, used consistently across the matview DDL, `applyFilters`, `rawTerminalStatsAgg`, and `aggregateCacheHits`.

- **Tests**: `TestGetStatsMatViewCacheHitsHybrid` verifies the hybrid cache-hit path including nil and zero contracts and agreement with the raw path. `TestMatViewShapeErrorFallsBackAndSelfHeals`, `TestMatViewStaleShapeFallsBackAndSelfHeals`, and `TestFilterMatViewShapeErrorFallsBack` cover the 42P01, 42703, and filter-view drop scenarios end-to-end, including background self-heal convergence.

- [x] Bug fix
- [x] Feature
- [ ] Refactor
- [ ] Documentation
- [ ] Chore/CI

- [x] Core (Go)
- [ ] Transports (HTTP)
- [ ] Providers/Integrations
- [ ] Plugins
- [ ] UI (React)
- [ ] Docs

```sh
go test ./framework/logstore/... -run TestGetStatsMatViewCacheHitsHybrid
go test ./framework/logstore/... -run TestMatViewShapeErrorFallsBackAndSelfHeals
go test ./framework/logstore/... -run TestMatViewStaleShapeFallsBackAndSelfHeals
go test ./framework/logstore/... -run TestFilterMatViewShapeErrorFallsBack
go test ./framework/logstore/... -run TestIsMatViewShapeError
go test ./framework/logstore/...
```

The self-heal tests drop or replace `mv_logs_hourly` mid-run and assert that reads continue returning correct results from the raw table with no error, that `matViewsReady` is set to false immediately, and that the view is recreated with the correct shape within 90 seconds.

- [x] No

The new matview columns require a schema migration. On first deploy, `repairMatViewShapes` detects the missing columns, drops and recreates `mv_logs_hourly`, and the self-heal path handles any replica that reads before the rebuild completes.

Closes #5384

No auth, secrets, PII, or sandboxing changes. The new SQL expressions are constants composed only of built-in PostgreSQL operators and are not user-controlled.

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [x] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [x] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable
akhsaul pushed a commit to akhsaul/bifrost that referenced this pull request Aug 27, 2026
…hq#5507)

## Summary

This PR adds two improvements to the materialized view read path: cache-hit statistics are now served from the hybrid aggregate (interior buckets from `mv_logs_hourly`, boundary slivers classified raw) instead of a separate full-window raw scan, and a runtime self-heal mechanism automatically recovers from missing or stale-shaped materialized views without requiring a process restart.

## Changes

- **Cache hits in the hybrid aggregate**: Three new columns (`direct_cache_hits`, `semantic_cache_hits`, `cache_debug_count`) are added to `mv_logs_hourly` using the same `cacheDebugJSONGuard` and `cacheDebugHitTypeExpr` expressions shared across the matview DDL, boundary sliver queries, and `aggregateCacheHits`. `cache_debug_count` preserves the nil contract: when no row in the window carried valid `cache_debug` JSON the fields are omitted from the response, and when cache rows exist but none were direct/semantic explicit zeros are returned. The previous approach issued a separate full-window raw scan for cache hits after the hybrid aggregate completed; that scan is removed.

- **Runtime matview self-heal** (`matviewheal.go`): `isMatViewShapeError` classifies PostgreSQL error codes `42P01` (undefined table), `42703` (undefined column), and `55000` (object not in prerequisite state) as shape errors. `fallBackToRaw` is called at every matview dispatch site — on a shape error it disables the matview read path process-wide, logs a warning, and triggers a single-flight background repair via `triggerMatViewSelfHeal`. The repair runs `ensureMatViews` then `refreshMatViews` and re-enables the path on success. A 30-second cooldown (`matViewHealCooldown`) bounds repair frequency; while broken, every request continues succeeding via the raw fallback. Two new atomic fields (`matViewHealInFlight`, `matViewHealLastAttempt`) are added to `RDBLogStore`.

- **Shared SQL constants**: The inline regex strings for the cache debug guard and hit-type extractor are replaced with named constants `cacheDebugJSONGuard` and `cacheDebugHitTypeExpr`, used consistently across the matview DDL, `applyFilters`, `rawTerminalStatsAgg`, and `aggregateCacheHits`.

- **Tests**: `TestGetStatsMatViewCacheHitsHybrid` verifies the hybrid cache-hit path including nil and zero contracts and agreement with the raw path. `TestMatViewShapeErrorFallsBackAndSelfHeals`, `TestMatViewStaleShapeFallsBackAndSelfHeals`, and `TestFilterMatViewShapeErrorFallsBack` cover the 42P01, 42703, and filter-view drop scenarios end-to-end, including background self-heal convergence.

## Type of change

- [x] Bug fix
- [x] Feature
- [ ] Refactor
- [ ] Documentation
- [ ] Chore/CI

## Affected areas

- [x] Core (Go)
- [ ] Transports (HTTP)
- [ ] Providers/Integrations
- [ ] Plugins
- [ ] UI (React)
- [ ] Docs

## How to test

```sh
go test ./framework/logstore/... -run TestGetStatsMatViewCacheHitsHybrid
go test ./framework/logstore/... -run TestMatViewShapeErrorFallsBackAndSelfHeals
go test ./framework/logstore/... -run TestMatViewStaleShapeFallsBackAndSelfHeals
go test ./framework/logstore/... -run TestFilterMatViewShapeErrorFallsBack
go test ./framework/logstore/... -run TestIsMatViewShapeError
go test ./framework/logstore/...
```

The self-heal tests drop or replace `mv_logs_hourly` mid-run and assert that reads continue returning correct results from the raw table with no error, that `matViewsReady` is set to false immediately, and that the view is recreated with the correct shape within 90 seconds.

## Breaking changes

- [x] No

The new matview columns require a schema migration. On first deploy, `repairMatViewShapes` detects the missing columns, drops and recreates `mv_logs_hourly`, and the self-heal path handles any replica that reads before the rebuild completes.

## Related issues

Closes maximhq#5384

## Security considerations

No auth, secrets, PII, or sandboxing changes. The new SQL expressions are constants composed only of built-in PostgreSQL operators and are not user-controlled.

## Checklist

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [x] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [x] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable
occcat pushed a commit to occcat/bifrost that referenced this pull request Sep 2, 2026
…hq#5507)

## Summary

This PR adds two improvements to the materialized view read path: cache-hit statistics are now served from the hybrid aggregate (interior buckets from `mv_logs_hourly`, boundary slivers classified raw) instead of a separate full-window raw scan, and a runtime self-heal mechanism automatically recovers from missing or stale-shaped materialized views without requiring a process restart.

## Changes

- **Cache hits in the hybrid aggregate**: Three new columns (`direct_cache_hits`, `semantic_cache_hits`, `cache_debug_count`) are added to `mv_logs_hourly` using the same `cacheDebugJSONGuard` and `cacheDebugHitTypeExpr` expressions shared across the matview DDL, boundary sliver queries, and `aggregateCacheHits`. `cache_debug_count` preserves the nil contract: when no row in the window carried valid `cache_debug` JSON the fields are omitted from the response, and when cache rows exist but none were direct/semantic explicit zeros are returned. The previous approach issued a separate full-window raw scan for cache hits after the hybrid aggregate completed; that scan is removed.

- **Runtime matview self-heal** (`matviewheal.go`): `isMatViewShapeError` classifies PostgreSQL error codes `42P01` (undefined table), `42703` (undefined column), and `55000` (object not in prerequisite state) as shape errors. `fallBackToRaw` is called at every matview dispatch site — on a shape error it disables the matview read path process-wide, logs a warning, and triggers a single-flight background repair via `triggerMatViewSelfHeal`. The repair runs `ensureMatViews` then `refreshMatViews` and re-enables the path on success. A 30-second cooldown (`matViewHealCooldown`) bounds repair frequency; while broken, every request continues succeeding via the raw fallback. Two new atomic fields (`matViewHealInFlight`, `matViewHealLastAttempt`) are added to `RDBLogStore`.

- **Shared SQL constants**: The inline regex strings for the cache debug guard and hit-type extractor are replaced with named constants `cacheDebugJSONGuard` and `cacheDebugHitTypeExpr`, used consistently across the matview DDL, `applyFilters`, `rawTerminalStatsAgg`, and `aggregateCacheHits`.

- **Tests**: `TestGetStatsMatViewCacheHitsHybrid` verifies the hybrid cache-hit path including nil and zero contracts and agreement with the raw path. `TestMatViewShapeErrorFallsBackAndSelfHeals`, `TestMatViewStaleShapeFallsBackAndSelfHeals`, and `TestFilterMatViewShapeErrorFallsBack` cover the 42P01, 42703, and filter-view drop scenarios end-to-end, including background self-heal convergence.

## Type of change

- [x] Bug fix
- [x] Feature
- [ ] Refactor
- [ ] Documentation
- [ ] Chore/CI

## Affected areas

- [x] Core (Go)
- [ ] Transports (HTTP)
- [ ] Providers/Integrations
- [ ] Plugins
- [ ] UI (React)
- [ ] Docs

## How to test

```sh
go test ./framework/logstore/... -run TestGetStatsMatViewCacheHitsHybrid
go test ./framework/logstore/... -run TestMatViewShapeErrorFallsBackAndSelfHeals
go test ./framework/logstore/... -run TestMatViewStaleShapeFallsBackAndSelfHeals
go test ./framework/logstore/... -run TestFilterMatViewShapeErrorFallsBack
go test ./framework/logstore/... -run TestIsMatViewShapeError
go test ./framework/logstore/...
```

The self-heal tests drop or replace `mv_logs_hourly` mid-run and assert that reads continue returning correct results from the raw table with no error, that `matViewsReady` is set to false immediately, and that the view is recreated with the correct shape within 90 seconds.

## Breaking changes

- [x] No

The new matview columns require a schema migration. On first deploy, `repairMatViewShapes` detects the missing columns, drops and recreates `mv_logs_hourly`, and the self-heal path handles any replica that reads before the rebuild completes.

## Related issues

Closes maximhq#5384

## Security considerations

No auth, secrets, PII, or sandboxing changes. The new SQL expressions are constants composed only of built-in PostgreSQL operators and are not user-controlled.

## Checklist

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [x] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [x] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable
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