Skip to main content

Cache, Lock, and Cloud Runtime Infrastructure

TeaQL keeps cache, coordination, service discovery, configuration, health, and operational lifecycle outside generated domain code. The application composition root installs these resources in or beside trusted UserContext; generated Query and Mutation APIs continue to receive only context.

“Implemented” and “verified” are deliberately separate below. A class or provider adapter in a repository is not proof of live Redis, Nacos, Consul, or multi-node behavior.

Current capability matrix​

CapabilityJavaRustTypeScriptSwiftPython.NETGo
Process-local cacheVerifiedVerifiedVerifiedVerifiedVerifiedVerifiedVerified
Redis/remote cache API and implementationQualifiedSource——SourceSourceSource
Process-local keyed lockVerifiedVerifiedN/AVerifiedVerifiedVerifiedVerified
Redis/remote lock API and implementationQualifiedVerified——QualifiedQualifiedQualified
NacosHTTP clientv2 gRPC stack————HTTP client
ConsulHTTP clientRegistry/health————HTTP client
OpenTelemetry/OTLPVerifiedVerifiedVerifiedVerifiedVerifiedVerifiedVerified

Status meaning:

  • Verified: retained executable conformance exists for the stated contract.
  • Source: an implementation is present, but the retained evidence reviewed for this page does not establish a live external provider or complete production semantics.
  • Qualified: an API/adapter exists, but the current source has a material bootstrap, ownership, atomic-release, or failure-policy limitation described below.
  • N/A: intentionally excluded. TypeScript's local runtime is single-threaded; this does not rule out a future cross-process lock provider.
  • —: no TeaQL runtime implementation is claimed. A third-party SDK does not change that status.

Local cache​

The portable local-cache behavior is process-local put, get, and remove with optional TTL in seconds. Missing, zero, or negative TTL means no expiry; an expired read is a miss. Replacing a key replaces both its value and TTL.

RuntimeCurrent API shape
JavaputToLocalCache, getFromLocalCache, removeFromLocalCache on UserContext
RustDataStore with InMemoryDataStore; context methods put_in_store, get_in_store, clear_in_store; a separate InMemoryAggregationCache supports namespace invalidation
TypeScriptexported LocalCache and process singleton localCache with put/get/remove/clear
SwiftLocalCache.shared or injected LocalCache, with put/get/remove/removeAll
Pythonput_to_local_cache, get_from_local_cache, remove_from_local_cache on UserContext
.NETPutToLocalCache, GetFromLocalCache, RemoveFromLocalCache on UserContext
GoPutToLocalCache, GetFromLocalCache, RemoveFromLocalCache on UserContext

Cache keys must include every input that changes the result: tenant/root, authorization scope where relevant, locale, namespace, query shape, and entity version. Do not cache a permission-sensitive Query under a global business ID. Treat cached entities as snapshots; do not reuse mutable instances across requests.

Invalidate conservatively after audited create, update, delete, or recover. For Query result caches, invalidate list, Facet, aggregate, and relation keys together so one screen cannot combine rows from one version with counts from another.

Use cache-aside only around an already authorized loader:

key = tenant + policy-scope + locale + query-shape + version
value = localCache.get(key)
if value is absent:
value = boundedQuery.comment(...).purpose(...).execute(context)
localCache.put(key, immutable(value), ttlSeconds)
return value

Never return a cached value before rebuilding and validating the trusted tenant/policy scope used in its key.

Remote cache​

Java, Rust, Python, .NET, and Go contain remote-cache boundaries plus Redis implementations. TypeScript and Swift do not currently expose a TeaQL remote cache provider.

trusted composition root
-> create and health-check Redis client/pool
-> install the runtime's remote cache provider
-> readiness proves the provider is present
-> application uses tenant-scoped, versioned keys

Important current qualifications:

  • Java exposes RemoteCacheProvider and RedisRemoteCache, but the default RedisRemoteCacheProvider source still initializes its cache with a null JedisPool. Applications must supply an operational provider; the built-in SPI path is not documented here as ready-to-use Redis bootstrap.
  • Rust exposes provider-neutral DataStore; RedisDataStore implements JSON value storage with optional expiry.
  • Python's current Redis adapter treats provider errors as cache misses/no-ops. Decide explicitly whether this fail-open behavior is acceptable.
  • .NET uses IRemoteCacheProvider and RedisRemoteCacheProvider with StackExchange.Redis.
  • Go uses RemoteCacheProvider and RedisRemoteCache; current serialization returns untyped JSON values and provider errors generally become misses.
RuntimeRemote-cache entry points
JavaputToRemoteCache, getFromRemoteCache, removeFromRemoteCache
Rustput_in_store, get_in_store, clear_in_store after installing Box<dyn DataStore>
Pythonput_to_remote_cache, get_from_remote_cache, remove_from_remote_cache
.NETPutToRemoteCache, GetFromRemoteCache, RemoveFromRemoteCache
GoPutToRemoteCache, GetFromRemoteCache, RemoveFromRemoteCache

Cache failure must never silently authorize access. A fail-open cache may be appropriate for derived data only when the authoritative Query still applies tenant and permission policy.

Local lock​

The portable Local Lock is process-local keyed mutual exclusion. Acquisition accepts a wait timeout and lease duration in milliseconds. The same owner can renew; another owner cannot release the lock. A non-positive timeout is a single attempt, and a non-positive lease remains held until owner release.

Java, Rust, Swift, Python, .NET, and Go implement this contract. Swift exposes LocalLock.shared.tryLock(...owner:timeoutMillis:expireMillis:); the other general runtime APIs use language-appropriate tryLocalLock/unlockLocal spelling. TypeScript is intentionally N/A.

Always release in finally, defer, or the language's structured cleanup equivalent. A local lock protects one process only; it cannot serialize two replicas, two containers, or two regions.

Remote lock​

Java, Rust, Python, .NET, and Go expose optional remote-lock provider boundaries. In all five general runtime boundaries, a missing provider can behave as a no-op success. That compatibility behavior is unsafe as the only guard for payments, inventory, job leadership, schema work, or idempotency. Production composition must fail readiness when a required provider is absent.

Rust currently has the strongest retained Redis evidence: every context has an owner token; acquisition uses SET key token NX with optional PX; release is an atomic Lua compare-and-delete; live Redis tests cover contention, waiting, lease expiry, reacquisition, and stale-owner release.

The current Java, Python, and Go Redis adapters use a shared lock value and unconditional delete. The .NET adapter has a provider-instance token but uses a non-atomic get-then-delete release. These adapters must not be described as equivalent to owner-safe atomic release until repaired and replayed. Use a provider with atomic owner comparison or keep the feature explicitly qualified. A lock is also not a replacement for idempotency records, database constraints, transactions, or optimistic version checks.

The critical-section pattern is always acquire, short protected work, and owner-safe release:

acquired = tryRemoteLock(tenantScopedBusinessKey, waitMillis, leaseMillis)
if not acquired: return conflict-or-retry
try:
run idempotent, audited transaction
finally:
unlockRemote(tenantScopedBusinessKey)
RuntimeLocal lockRemote lock
JavatryLocalLock / unlockLocaltryRemoteLock / unlockRemote
Rusttry_local_lock / unlock_localtry_remote_lock(...).await / unlock_remote(...).await
TypeScriptN/A—
SwiftLocalLock.tryLock / unlock with stable owner UUID—
Pythontry_local_lock / unlock_localtry_remote_lock / unlock_remote
.NETTryLocalLock / UnlockLocalTryRemoteLock / UnlockRemote
GoTryLocalLock / UnlockLocalTryRemoteLock / UnlockRemote

Cloud runtime features​

Java​

teaql-cloud defines CloudClient and Nacos/Consul HTTP adapters.

  • Nacos 1.x/2.x HTTP OpenAPI: register, deregister, healthy discovery, configuration retrieval, namespace/group, and readiness health.
  • Consul HTTP API: register, deregister, passing-instance discovery, ACL token, and health.
  • Retained evidence uses in-process HTTP servers; it is protocol-level, not a live external cluster test.

Nacos 3.x Client API is not compatible with the documented /nacos/v1 HTTP paths and is not claimed by this adapter.

NacosCloud cloud = new NacosCloud(serverAddress, "production", "APP");
ServiceInstance instance = new ServiceInstance("orders", "10.0.0.7", 8080);
cloud.register(instance);
List<ServiceInstance> healthy = cloud.getInstances("orders");
String config = cloud.getConfig("orders.yaml", "APP");
// On shutdown, call cloud.deregister(instance).

Rust​

Rust has the deepest TeaQL cloud stack:

  • teaql-cloud-core: provider-neutral registry, discovery, configuration, health, Prometheus metrics, lifecycle, and graceful-shutdown contracts.
  • teaql-cloud-nacos: Nacos v2 gRPC registration/discovery/configuration, subscriptions, health, and metrics.
  • teaql-cloud-consul: registration/deregistration, health, and metrics. The current source does not implement the core discovery or configuration traits.
  • teaql-cloud-actuator: /actuator/health, liveness, readiness, info, and Prometheus metrics endpoints for Axum.
  • teaql-cloud-starter: Nacos/Consul connection, registration, route assembly, signal handling, deregistration, and graceful shutdown.

The liveness endpoint reports process life. Readiness includes dependencies and returns out-of-service during shutdown; it must not be replaced by a constant HTTP 200 route.

CloudApp::new()
.nacos("127.0.0.1:8848")
.namespace("production")
.service_name("order-service")
.port(8080)
.routes(routes)
.start()
.await?;

Go​

Go defines provider-neutral registry, discovery, configuration, and health interfaces with Nacos and Consul HTTP implementations.

  • Nacos: register/deregister, healthy discovery, config retrieval, namespace/group, credentials, and readiness health.
  • Consul: register/deregister, passing-instance discovery, ACL token, and leader health.
  • cloud/starter exposes a basic Nacos/Consul composition object. The current Consul starter populates only Registry; use the concrete ConsulCloud for implemented discovery rather than assuming the starter wires it.
  • Retained Java/Go conformance uses in-process HTTP servers, not a live cluster.
cloud := nacos.NewNacosCloud(&nacos.NacosConfig{
ServerAddrs: []string{"127.0.0.1:8848"},
NamespaceId: "production",
Group: "APP",
})
instance := &core.ServiceInstance{ServiceId: "orders", Host: "10.0.0.7", Port: 8080}
if err := cloud.Register(context, instance); err != nil { return err }
instances, err := cloud.GetInstances(context, "orders")
// On shutdown, call cloud.Deregister(context, instance).

What is not currently claimed​

TeaQL does not currently claim a seven-runtime abstraction for S3-compatible object storage, Kafka/event streaming, HTTP resilience policies, JWT/OAuth/OIDC, Vault/secret management, job scheduling, or Elasticsearch/OpenSearch. Those capabilities may be implemented by an application, but a third-party library is not TeaQL runtime support.

Operational verification​

Before enabling a cache, lock, or cloud provider, complete the infrastructure section of the Debugging and Governance Checklist. Use Debugging, Audit, and Observability to inspect cache hit/miss telemetry, lock contention, readiness, cloud registration, and provider failures without exposing keys, tenant IDs, tokens, or configuration values.