ealeonraz opened a new pull request, #13106:
URL: https://github.com/apache/gravitino/pull/13106

   ### What changes were proposed in this pull request?
   
   Add an optional `redis` entity cache behind the existing `EntityCache` SPI, 
selected with `gravitino.cache.implementation=redis`. The default stays 
`caffeine`; no caller changes.
   
   - `RedisEntityCache` reports `Coherence.SHARED` and keeps one copy of every 
cached entity in Redis. Keys are `<ns>:{<metalake>}:D:<identifier>:<TYPE>` for 
values, `<ns>:{<metalake>}:F:<identifier>` for version fences, and 
`<ns>:{<metalake>}:IDX` for a lex-ordered index ZSet. The metalake is the Redis 
Cluster hash tag, so every multi-key script is single-slot at any depth (no 
`CROSSSLOT`).
   - Container drop is one Lua script: bump the dropped identifier's fence, 
delete its value, then `ZRANGEBYLEX` the index over `<identifier>.` (and 
`<identifier><schema separator>` for a schema, mirroring #12416) and delete 
each descendant. Readers never see a torn drop.
   - Stale-write guard: a read miss records the fences of the identifier and of 
every ancestor for the calling thread; the write that fills the entry is a Lua 
script that re-reads those fences and refuses to write if any moved. A load 
that began before a drop of the entity or of any container above it (including 
a nested-schema parent) cannot refill the key, whether or not it was indexed. 
Fences outlive the value TTL (`gravitino.cache.redis.fenceTtlMs`, default 2x 
`expireTimeInMs`), so a slow reader cannot win by outliving the value.
   - Failure policy: reads and fills degrade to a miss on any Redis error or 
timeout; an invalidation failure is propagated so it is never silently dropped; 
an unreachable Redis at startup fails fast.
   - `KryoEntitySerializer`: Kryo with `CompatibleFieldSerializer` (field names 
written, so rolling upgrades tolerate added/removed fields), Objenesis 
instantiation, `Type` values written in the REST JSON form so primitive type 
singletons come back as the same instances, and Guava immutable collections 
rebuilt on read. An undecodable entry is discarded and treated as a miss.
   - `CacheFactory` registers `"redis"`; new `gravitino.cache.redis.*` config 
entries; docs and LICENSE.bin updated for Jedis (MIT), Kryo/ReflectASM/MinLog 
(BSD-3), Objenesis (Apache-2), org.json (public domain).
   
   Scope, as agreed on the issue: this is a shared cache, not a strongly 
consistent database/cache pair. The cache is only touched after the store 
commits, and a crash between commit and invalidation leaves staleness bounded 
by the value TTL. Consistency-sensitive call sites still need the fresh-read 
path from #12150. No dependency on #12151: the writing node invalidates the 
single shared copy directly.
   
   ### Why are the changes needed?
   
   Phase 2 of #11737. With per-node Caffeine caches, a write on one node leaves 
the other nodes' copies stale until the change-log poller reaches them; a 
shared cache removes that cross-node divergence for deployments that accept a 
network hop per read.
   
   Fix: #12020
   
   ### Does this PR introduce _any_ user-facing change?
   
   New optional configuration, all additive:
   
   | Key | Default |
   |-----|---------|
   | `gravitino.cache.redis.address` | (required when `implementation=redis`) |
   | `gravitino.cache.redis.cluster` | `false` |
   | `gravitino.cache.redis.namespace` | `gravitino` |
   | `gravitino.cache.redis.fenceTtlMs` | `0` (twice `expireTimeInMs`) |
   | `gravitino.cache.redis.serializer` | `kryo` |
   | `gravitino.cache.redis.timeoutMs` | `1000` |
   | `gravitino.cache.redis.username` / `password` | (none) |
   
   `gravitino.cache.expireTimeInMs` is reused as the Redis value TTL. New 
bundled dependencies in `core`: `redis.clients:jedis` and 
`com.esotericsoftware:kryo`.
   
   ### How was this patch tested?
   
   Unit tests (no Redis needed): `TestRedisKeyspace` (key layout, hash tag, 
ancestor fence paths incl. nested schemas, descendant prefixes), 
`TestKryoEntitySerializer` (round trip of every cacheable entity type, type 
singleton identity, garbage rejection), `TestRedisEntityCache` (config 
validation, factory registration, fail-fast startup, failure policy with a 
mocked client, fence bookkeeping between miss and write, single-slot hashing 
via `JedisClusterCRC16`).
   
   Integration tests tagged `gravitino-docker-test`: `RedisEntityCacheIT` 
(Testcontainers `redis:7.2-alpine`) and `RedisClusterEntityCacheIT` 
(`grokzen/redis-cluster`, 3 masters) share `RedisEntityCacheTestBase`: 
cross-node freshness, catalog/metalake/schema drops incl. nested schemas and 
prefix siblings, stale write rejected after same-key invalidation, after an 
ancestor drop of a never-indexed key, and after a nested-schema parent drop, 
reload after invalidation accepted, unconditional write without a preceding 
miss, the store's lock/miss/load/put pattern with a concurrent invalidation, 
concurrent readers and writers across two nodes, TTL expiry, clear/size across 
metalakes and cluster nodes, undecodable entry discarded, non-cacheable types 
not stored, namespace isolation, and a 40-metalake drop sweep on the cluster. 
Both suites also accept an external server via `GRAVITINO_REDIS_ADDRESS` / 
`GRAVITINO_REDIS_CLUSTER_ADDRESS`; I ran them that way against a local Redis 
7.2.7 standalon
 e and a local 3-master cluster.
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to