java.lang.Object
org.gcube.informationsystem.resourceregistry.types.redis.RedisTypeCache

public class RedisTypeCache extends Object
Redis-backed type cache for stateless multi-instance deployments.

Stores per-type JSON blobs and a structural index of direct-children (first-level subtypes only) so that polymorphic read responses can be assembled entirely from Redis without a database round-trip.

Key schema (see also RedisKeySpace):

  • type:uuid:{uuid} — STRING — canonical single-type JSON blob
  • type:name:{encoded} — STRING — URL-encoded type name → UUID string
  • type:children:{uuid} — SET — direct (first-level) subtype UUIDs
  • type:parent:{uuid} — SET — supertype UUIDs (multiple inheritance supported)
  • type:linearization:{uuid} — STRING — precomputed C3-linearized ancestor name list, internal-only (never part of a type's own returned JSON representation)
  • type:list — ZSET — all type UUIDs, used for invalidateAll()

All operations degrade gracefully: if Redis is unavailable every method returns null / an empty collection and the caller falls through to the database.

A miss is just a miss. This cache never rebuilds itself in response to a lookup that failed to find something: it reports the miss and lets the caller fall through to its own database path, which every caller already has. Reacting to a single miss by reloading every type — as this class used to — made an ordinary miss cost O(all types) instead of O(1), turned a single PUT /types/... (which wipes the cache) into a full rebuild per concurrent request thread, and let a lookup for a type that does not exist be repeated to amplify load at will. Repopulating is a background responsibility, driven by the schema projection worker, never by a request.

Invalidation strategy:

  • create — granular: add the new type's own keys and register it as a child of each of its supertypes.
  • delete — granular (only possible on leaf types): remove own keys and remove this type from each parent's children SET.
  • update — full: wipe all type:* keys via invalidateAll() because properties / version change.
Author:
Luca Frosini (ISTI - CNR)
  • Method Details

    • getInstance

      public static RedisTypeCache getInstance()
      Returns the singleton instance of RedisTypeCache.
      Returns:
      the singleton instance
    • isAvailable

      public boolean isAvailable()
      Returns true when Redis is reachable and the connection is open.
      Returns:
      true if Redis is available, false otherwise
    • getUUIDByName

      public UUID getUUIDByName(String typeName)
      Returns the cached UUID for the given type name, or null on cache miss or Redis unavailability.
      Parameters:
      typeName - canonical type name (e.g. "Resource")
      Returns:
      UUID, or null
    • getRawByUUID

      public String getRawByUUID(UUID uuid, long currentVersion)
      Returns the cached single-type JSON blob for the given UUID, or null on cache miss or Redis unavailability.
      Parameters:
      uuid - the type's UUID
      currentVersion - the schema version the caller requires the entry to match
      Returns:
      single-type JSON, or null
    • getRawByName

      public String getRawByName(String typeName, long currentVersion)
      Returns the cached single-type JSON blob for the given type name, or null on cache miss or Redis unavailability.
      Parameters:
      typeName - canonical type name
      currentVersion - the schema version the caller requires the entry to match
      Returns:
      single-type JSON, or null
    • getDirectChildrenUuids

      public Set<String> getDirectChildrenUuids(UUID uuid)
      Returns the set of direct children UUID strings for the given parent UUID, or null if not cached or Redis is unavailable.
      Parameters:
      uuid - the parent type's UUID
      Returns:
      set of child UUID strings, or null
    • getAllDescendantsRaw

      public List<String> getAllDescendantsRaw(UUID rootUuid, long currentVersion)
      Performs a BFS traversal of the type:children graph rooted at rootUuid and returns the JSON blob of every encountered type (root included) in BFS order.

      Returns null if the root type is not in the cache or Redis is unavailable, so that the caller can fall through to the database.

      Parameters:
      rootUuid - the UUID of the root (requested) type
      currentVersion - the schema version the caller requires every entry to match
      Returns:
      list of single-type JSON blobs in BFS order, or null on cache miss / unavailability
    • saveType

      public void saveType(UUID uuid, String typeName, String json, long version)
      Writes the UUID blob and name→UUID lookup for a single type. Does not touch children or parent keys.
      Parameters:
      uuid - the type's UUID
      typeName - canonical type name
      json - serialized single-type JSON
      version - the schema version this payload reflects
    • saveLinearization

      public void saveLinearization(UUID uuid, List<String> superTypes, long version)
      Stores the precomputed, C3-linearized ancestor name list for a type, computed once at creation / warm-up time so that CachedType.getSuperTypes() never has to recompute or re-derive it on an ordinary read. Internal-only: kept alongside the type's canonical JSON blob, the same way type:parent/type:children are, never inside it -- this is never part of a type's own returned representation.
      Parameters:
      uuid - the type's UUID
      superTypes - the linearized ancestor names, most-derived-first
      version - the schema version this linearization reflects
    • getLinearization

      public List<String> getLinearization(UUID uuid, long currentVersion)
      Returns the precomputed, C3-linearized ancestor name list for a type, or null if not cached or Redis is unavailable.
      Parameters:
      uuid - the type's UUID
      currentVersion - the schema version the caller requires the entry to match
      Returns:
      the linearized ancestor names, most-derived-first, or null
    • addNewTypeToCache

      public void addNewTypeToCache(UUID uuid, String typeName, String json, Collection<String> supertypeNames, long version)
      Granular cache update for a newly created type.

      In addition to writing the UUID blob and name lookup (via saveType(java.util.UUID, java.lang.String, java.lang.String, long)), this method:

      • Looks up the UUID of each supertype by name from the cache.
      • Adds this type's UUID to the type:children:{parentUuid} SET of every supertype whose UUID is found in Redis.
      • Adds each found parent UUID to the type:parent:{uuid} SET of this type (supports multiple inheritance).

      If any supertype UUID is not found in the cache the parent/children association for that supertype is simply skipped; the cache remains consistent for the supertypes that are cached.

      Parameters:
      uuid - the new type's UUID
      typeName - canonical type name
      json - serialized single-type JSON
      supertypeNames - the direct supertype names from type.getExtendedTypes()
      version - the schema version this payload reflects
    • saveAll

      public boolean saveAll(List<RedisTypeCache.TypeCacheEntry> entries, long version)
      Writes an entire set of types in one pipelined batch: their blobs and name index, their parent/child edges, and their precomputed linearizations.

      Issued through the asynchronous API with auto-flushing disabled, so the whole batch costs one network round trip instead of roughly half a dozen per type. Repopulating ~100 types the one-command-at-a-time way meant several hundred sequential round trips, each paying the full latency of the connection; that cost is what made a full rebuild expensive enough to matter in the first place.

      Auto-flushing is restored before returning even on failure — the handle is backed by the connection every other caller shares, and leaving it disabled would silently stall them.

      Parameters:
      entries - the types to write
      version - the schema version these payloads reflect
      Returns:
      true if the batch was written, false if Redis was unavailable or the write failed
    • markMissing

      public void markMissing(String typeName, long version)
      Records, for a short while, that no Type with this name exists.

      Without this, a lookup for a name that does not exist misses the cache every single time and reaches the database every single time — so repeating it is an inexpensive way to generate unbounded database load. A tombstone makes the second and subsequent lookups cost one Redis read.

      Stamped with the schema version, like every other entry: creating the type advances the counter, which retires the tombstone immediately. Without that, a type registered right after a failed lookup would keep answering 404 until the tombstone expired — and the explicit removal performed on creation runs inside a best-effort block that swallows cache failures, so it cannot be relied upon on its own.

      Parameters:
      typeName - the canonical type name that was not found
      version - the schema version at which the absence was established
    • isKnownMissing

      public boolean isKnownMissing(String typeName, long currentVersion)
      Returns whether a tombstone recorded by markMissing(String, long) is still in force.
      Parameters:
      typeName - the canonical type name
      currentVersion - the current schema version; a tombstone from an earlier version is treated as expired, since the schema has changed since it was recorded
      Returns:
      true if this name is known not to exist, false if it exists or is simply unknown
    • invalidate

      public void invalidate(UUID uuid, String typeName)
      Granular cache invalidation for a deleted type.

      Removes:

      • type:uuid:{uuid}
      • type:name:{encodedName}, but only if it still points to this UUID - a subsequent create under the same name may already have claimed it, and that mapping must not be clobbered.
      • type:children:{uuid} (should be empty for a leaf type)
      • This UUID from the type:children:{parentUuid} SET of every parent (resolved via type:parent:{uuid}).
      • type:parent:{uuid}
      • type:linearization:{uuid}
      • This UUID from the global type:list ZSET.
      Parameters:
      uuid - the deleted type's UUID
      typeName - canonical type name of the deleted type
    • invalidateAll

      public void invalidateAll()
      Deletes all type:* keys from Redis using a SCAN loop. Called after every type update (properties / version change) to ensure subsequent reads reflect the latest database state.