You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: CHANGELOG.md
+2-2Lines changed: 2 additions & 2 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -7,13 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
8
8
---
9
9
10
-
## [4.0.0] — 2026-07-29
10
+
## [4.0.0] — 2026-07-20
11
11
12
12
### Added
13
13
14
14
-**Query Builder caching:** supported `DB::table()` reads now participate in NormCache automatically, alongside Eloquent reads. Writes through those cache-aware builders invalidate affected tables automatically.
15
15
-**Unified dependencies:**`dependsOn()` now accepts Eloquent model classes and raw table names in one declaration.
16
-
-**Result overlays:**`useResultCache()` adds a complete-result payload on top of canonical row storage, letting warm reads avoid row-by-row assembly while retaining canonical fallback and repair.
16
+
-**Automatic Result Overlays:**canonical queries returning up to `auto_overlay_max_rows` (default 50) automatically store a completeresult payload in Redis, letting warm reads execute in a single Redis `GET` (~80–120 µs) while retaining canonical fallback and self-healing.
17
17
-**Global tags:** use `tag('name')` to group cached query payloads and `NormCache::flushTag('name')` to invalidate that group.
18
18
-**Unified manual invalidation:**`NormCache::invalidate()` accepts a model instance, model class, table name, or an array of those targets. A model target uses its model connection; table targets use Laravel's default connection unless `connection:` is supplied.
19
19
-**Runtime cache switch:**`normcache:disable` and `normcache:enable`, plus `disableCache()`, `enableCache()`, and `cacheDisabled()`, pause caching across application nodes. Re-enabling advances the global epoch before serving cached data again.
Copy file name to clipboardExpand all lines: README.md
+24-34Lines changed: 24 additions & 34 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -23,12 +23,6 @@ Publish the configuration:
23
23
php artisan vendor:publish --tag=normcache-config
24
24
```
25
25
26
-
### Optional igbinary serialization
27
-
28
-
When the `ext-igbinary` PHP extension is available, NormCache detects it automatically and uses it for cached payloads. Otherwise it falls back to PHP's native serialization; no configuration is required.
29
-
30
-
Every application node and worker sharing the same Redis cache must use the same serializer. After installing or removing igbinary, run `php artisan normcache:flush` before serving traffic so payloads written with the previous format are not reused.
31
-
32
26
Add `Cacheable` to Eloquent models whose writes and reads NormCache should observe:
33
27
34
28
```php
@@ -58,10 +52,23 @@ Cache controls are available on Eloquent and Query Builder:
For queries that would normally use canonical storage, `useResultCache()` also stores the complete result as one payload. Warm reads can use that payload directly, while canonical storage remains available as a fallback.
57
+
## Canonical & Normalized Row Caching
58
+
59
+
At the core of NormCache is **normalized row storage**. Unlike traditional query caching—which stores duplicate, static copies of entire result sets for every unique SQL query—NormCache normalizes data in Redis:
60
+
61
+
-**Single Storage for Model Rows**: Individual database rows are stored once under canonical primary key IDs (`table:r:<id>`).
62
+
-**Lightweight Query Memberships**: Queries cache only a list of primary key IDs (`table:m:<query_hash>`), not full duplicate model attributes.
63
+
-**$O(1)$ Invalidation Without Redis SCAN**: When a model is updated or deleted, NormCache invalidates only that specific row key (`table:r:<id>`) and advances the table version counter (`table:v`). There are no expensive `KEYS` or `SCAN` commands in Redis.
64
+
-**Global Row Freshness**: Every query reading Post #42 automatically receives the updated row data on its next fetch, ensuring instant consistency across all application queries without clearing individual query keys.
65
+
66
+
## Automatic Result & Projection Overlay
67
+
68
+
NormCache automatically optimizes warm query performance by storing single-step result overlays in Redis for eligible canonical queries:
69
+
70
+
-**Automatic Promotion**: Canonical queries returning up to `auto_overlay_max_rows` (default `50`) automatically store a serialized result payload in Redis (`table:e:v1:...`).
71
+
-**Instant Synchronization & Self-Healing**: Updates to underlying models or dependency tables instantly invalidate the overlay alongside canonical storage. If an overlay key expires or misses, NormCache seamlessly falls back to canonical row assembly and repromotes automatically.
`flushTag()` advances a Redis version counter; it does not scan for or delete matching keys. The affected queries miss and rebuild on their next read, while old payloads expire naturally. Tags are an additional manual invalidation boundary and do not replace automatic dependency invalidation when an underlying table changes.
86
93
87
-
Tags must be non-empty valid UTF-8 strings of at most 128 bytes.
88
-
89
94
## Dependencies
90
95
91
96
NormCache infers identifiable tables from ordinary joins, unions, subqueries, and relationship queries. If a query contains an opaque expression or source, declare every table it reads:
@@ -106,12 +111,7 @@ Writes through cache-aware Eloquent or Query Builder paths invalidate automatica
@@ -138,18 +138,6 @@ While disabled, reads bypass NormCache and go directly to the database, and writ
138
138
139
139
`normcache:enable` atomically advances the global epoch before clearing the disabled flag. This prevents payloads cached before the pause from being served after writes occurred while invalidation was disabled.
140
140
141
-
The same controls are available programmatically:
142
-
143
-
```php
144
-
use NormCache\Facades\NormCache;
145
-
146
-
NormCache::disableCache();
147
-
$disabled = NormCache::cacheDisabled();
148
-
$newEpoch = NormCache::enableCache();
149
-
```
150
-
151
-
This runtime switch is separate from `NORMCACHE_ENABLED=false`. A cache disabled in configuration cannot be enabled with `normcache:enable`; update the configuration first.
152
-
153
141
## Configuration
154
142
155
143
```php
@@ -160,6 +148,7 @@ return [
160
148
161
149
'row_ttl' => 604800,
162
150
'query_ttl' => 3600,
151
+
'auto_overlay_max_rows' => 50,
163
152
164
153
'max_precise_invalidation_keys' => 1000,
165
154
'building_lock_ttl' => 5,
@@ -201,12 +190,7 @@ NormCache bypasses reads when correctness cannot be established, including:
201
190
202
191
Canonical storage requires a supported single-column integer or string primary key. Queries can still use `result` storage when canonical routing is unavailable.
203
192
204
-
Writes performed through raw SQL or a connection not installed by NormCache are invisible until `invalidate()` or `flushAll()` is called. After changing a connection's database, schema, or database objects at runtime, call `NormCache::clearSchemaMetadata()` for that connection.
205
-
206
-
### Consistency & Failure Modes
207
-
208
-
-**Fail-Open Invalidation**: Database availability is prioritized over cache state. If Redis is unreachable during a write operation, NormCache fails open on the writing node (bypassing cache for subsequent reads on that node) and logs a warning. Note that other application nodes connected to Redis may continue serving cached queries until TTL expiration or subsequent invalidation.
209
-
-**Triggers & Database Cascades**: Foreign key `ON DELETE CASCADE` rules, database triggers, and stored procedures operating within the database engine are not intercepted at the application layer. When executing operations that trigger database-side side-effect updates, call `NormCache::invalidate([...])` explicitly for affected secondary tables.
193
+
Direct database writes executed outside of Eloquent (such as raw SQL, triggers, or external services) bypass automatic cache interception. Use `NormCache::invalidate(...)` or `NormCache::flushAll()` to manually invalidate affected models or tables. If connection schemas or table definitions are modified at runtime, call `NormCache::clearSchemaMetadata($connection)` to reset cached schema metadata.
210
194
211
195
## Redis Cluster
212
196
@@ -216,6 +200,12 @@ All keys for one physical table share a Redis hash slot. Query-group entries use
216
200
217
201
When `events` is enabled, NormCache dispatches cache hit, miss, bypass, repair, and invalidation events. When `fruitcake/laravel-debugbar` is installed and `debugbar` is enabled, cache activity appears in Laravel Debugbar.
218
202
203
+
## Optional igbinary serialization
204
+
205
+
When the `ext-igbinary` PHP extension is available, NormCache detects it automatically and uses it for cached payloads. Otherwise it falls back to PHP's native serialization; no configuration is required.
206
+
207
+
Every application node and worker sharing the same Redis cache must use the same serializer. After installing or removing igbinary, run `php artisan normcache:flush` before serving traffic so payloads written with the previous format are not reused.
0 commit comments