Data Structures
This page covers the distributed data structures that LoomCache provides: maps, queues, sets, topics, CRDTs, and CP
primitives. The client SDK exposes each structure through a dedicated Loom* wrapper obtained from LoomClient (or,
for CP primitives, from client.consistencySubsystem()).
For the production-supported single-group path, every committed write flows through Raft: replicated to a majority of nodes, persisted to the write-ahead log (WAL), and applied atomically to the state machine before the client response is sent. Multi-group sharding remains unsupported and fail-closed in production until each group has independent WAL, Raft metadata, snapshot, install-snapshot, restart recovery evidence, durable migration chunk ACKs, consensus-backed ownership cutover, and an explicit sharding release gate.
Distributed Data Structures
A dozen-plus distributed primitives — maps, queues, topics, CRDTs, and CP — replicated over Raft.
Client SDK
Section titled “Client SDK”LoomMap<K, V>
Section titled “LoomMap<K, V>”LoomMap is the primary key-value structure:
LoomMap<String, String> users = client.getMap("users");users.put("alice", "Alice");String name = users.get("alice"); // near cache may answer locallyusers.putIfAbsent("bob", "Bob"); // atomicusers.delete("alice");Supported operations: get, put, putTransient, putIfAbsent, putAll, putWithTtl, delete, evict,
containsKey, size, getAll, getAndRemove, getEntryView, computeIfAbsent, replace(K, V, V)
(compare-and-swap), compareAndDelete(K, V), removeAll, clear(), cursor-based scan / scanner, query helpers
(keySet, entrySet, values, keyset pagination, projections, and aggregates), addEntryListener /
removeEntryListener, executeOnKey, executeOnKeys, executeOnEntries, MapStore loadAll, and async variants
(getAsync, putAsync, …). Stats are available via LoomMap.getStats().
Reads are linearizable by default: leaders use lease/read-index checks and followers redirect to the leader. Non-production
embedded/member-local compatibility deployments can opt into readBackupData(true) to serve reads from the local
replica. This trades latency for possible staleness during replication lag or leader failover. loomcache.profile=production
rejects ClusterConfig.readBackupData=true, so production reads stay on the linearizable leader/read-index path. The
property key depends on the deployment mode: the standalone server reads loomcache.cluster.read-backup-data (legacy
alias: loomcache.read-backup-data); the Spring Boot module binds loomcache.server.read-backup-data.
Embedded/server maps expose MapStore/EntryStore (and the generalized DataStore/DataLoader) integration points.
Packaged generic JDBC MapStore and Spring JPA write-through configuration surfaces are fail-closed under the production
profile. The Spring default-map JPA write-through bridge is not installed in production; unsafe/local optional
datasources still fail preflight when that bean is present. The production-eligible external-store path is a custom
MapStore/DataStore wired programmatically through a clustered server extension. That path provides leader-owned
external writes, snapshot/graceful-drain recovery for write-behind queues, and the leader-owned read-through fill path.
Client-facing MapStore helpers are scoped to that same path: loadAll(...) is leader-owned and Raft-replicated, while
putTransient(...) deliberately bypasses external write-through for memory-only replicated values.
Per-map eviction is configured through map configuration using a maxSize entry-count ceiling and one of five
EvictionPolicy values: LRU, LFU, FIFO, RANDOM, or NONE (default). The server-wide memory ceiling is
a separate concept: loomcache.eviction.max-memory-bytes (standalone server) / loomcache.server.eviction.max-memory-bytes
(Spring Boot) applies a shared byte budget across all maps on a node. These two levels are independent — per-map limits
entry count, server-wide limits total heap footprint.
This release fail-closes server-side LRU, LFU, FIFO, and RANDOM eviction together with maximum idle expiration: neither per-map eviction nor server-wide memory eviction is production-supported until eviction victim selection is Raft-applied and proven through WAL/snapshot/restart tests. Configuring either in the production profile raises a startup error.
LoomQueue<E>
Section titled “LoomQueue<E>”LoomQueue provides distributed queue operations:
LoomQueue<String> tasks = client.getQueue("tasks");tasks.offer("send-email");String next = tasks.poll();String peek = tasks.peek();Supported operations: offer, poll, poll(Duration), take(), peek, size, offerAll, bounded poll(int),
drain, drainTo, and async variants. Embedded QueueStore exists as an SPI, but snapshot/restart parity is not
production-supported until queue restore, rollback, and duplicate/lost item failure windows are release-validated.
LoomSet<E>
Section titled “LoomSet<E>”LoomSet provides distributed set operations:
LoomSet<String> tags = client.getSet("tags");tags.add("java");tags.contains("cache");tags.size();Supported operations: add, remove, contains, size, clear, cursor-based scan, and async variants.
LoomTopic<T>
Section titled “LoomTopic<T>”LoomTopic provides typed publish/subscribe over a client-managed polling path:
LoomTopic<String> events = client.getTopic("events", String.class);int sub = events.subscribe(msg -> System.out.println("got: " + msg));events.publish("hello");events.unsubscribe(sub);subscribe(...) returns an integer subscription id. Pass that id to LoomTopic.unsubscribe(int) to remove one polling
subscription, or call closeSubscriptions() to drop them all. Do not assume Hazelcast-style push dispatch semantics.
LoomAtomicLong (CP)
Section titled “LoomAtomicLong (CP)”LoomAtomicLong is a distributed atomic counter:
LoomAtomicLong counter = client.consistencySubsystem().getAtomicLong("hits");counter.incrementAndGet();long cur = counter.get();counter.compareAndSet(cur, cur + 10);All operations — get, set, arithmetic, and compare-and-set — are fully Raft-replicated.
LoomLinearizableLock / LoomLinearizableSemaphore (CP)
Section titled “LoomLinearizableLock / LoomLinearizableSemaphore (CP)”Obtain CP locks and semaphores from the consistency subsystem:
LoomLinearizableLock lock = client.consistencySubsystem().getLock("orders");LoomLinearizableSemaphore permits = client.consistencySubsystem().getSemaphore("pool");Production servers accept lock and semaphore mutations only when the request carries an active Raft-managed CP
session. The Java client manages that session lifecycle internally for LoomLinearizableLock and
LoomLinearizableSemaphore; direct session operations are internal to the client/server contract, not a public
application workflow. A sessionless semaphore mutation still fails closed in production with an
unsupported-operation response.
Other CP extension handles are non-production only and their direct requests are rejected under the production profile.
Use the in-process ConsistencySubsystem for non-production embedded scenarios.
LoomBatch
Section titled “LoomBatch”LoomBatch groups multiple operations into a single request:
// Non-atomic (default): execute() sends one server request; executeAndReport() can split keyed map owner groups.client.batch() .map("users").put("alice", "Alice") .map("users").delete("eve") .execute();
// Atomic: single-owner batches use one mutation boundary; cross-owner keyed map put/delete uses server 2PC.client.batch() .atomic() .map("users").put("alice", "Alice") .map("users").delete("eve") .execute();
// Replicated: implies atomic=true and requests cross-replica durability for the batch.client.batch() .replicated() .map("users").put("alice", "Alice") .execute();Atomicity is opt-in: by default (atomic=false), execute() sends one
server-side request and sharded servers fail closed for unsafe mixed-owner batches. Use executeAndReport() when you
want keyed map put/delete/putIfAbsent operations split into owner-group sub-batches with explicit
partial-outcome reporting. Sharded batch execution currently accepts only keyed map put/delete/putIfAbsent
operations. Set/queue batches are supported only on the non-sharded/single-Raft-group path; in sharded mode any batch
containing set or queue operations fails closed. Call .atomic() for all-or-nothing server handling: single-owner
batches use one mutation boundary, and cross-owner sharded batches currently support keyed map put/delete through
the server 2PC path. Call .replicated() to additionally request cross-replica durability, which implies atomic=true.
Additional client wrappers
Section titled “Additional client wrappers”Beyond the structures shown above, the SDK ships a dedicated Loom* wrapper for every server-side structure. Obtain
them from LoomClient (or client.consistencySubsystem() for CP primitives):
getMultiMap→LoomMultiMap,getList→LoomList,getPriorityQueue→LoomPriorityQueue,getRingbuffer→LoomRingbuffer. The client API exposesadd(E),readOne(long),readMany(long, int),headSequence(),tailSequence(), andcapacity(). Server-sideLoomFunctionfiltering is an internal implementation detail —LoomRingbuffer.readManyhas no filter overload on the client API.RingbufferStoresupports embedded persistence.getReplicatedMap→LoomReplicatedMap— AP, eventually-consistent broadcast map (put,get,remove,containsKey,size) with per-key last-writer-wins and a background anti-entropy sweep; not linearizable. Writes are fail-closed underloomcache.profile=productionuntil broadcast convergence is chaos-certified (-Dloomcache.replicatedmap.allow-production=trueopts in for non-production use); reads are unrestricted.getReliableTopic→LoomReliableTopic— ringbuffer-backed message store with monotonic sequence numbers.
LoomReliableTopic is a pull model, not a push/subscribe model:
publish(T)— appends a message and returns the assigned sequence number.publish(T, TopicOverloadPolicy)— overload that explicitly specifies the overload policy (DISCARD_OLDEST,DISCARD_NEWEST,BLOCK, orERROR) for this publish call.ERRORrejects the publish with an exception when the ringbuffer is full.readFrom(long sequence, int maxCount)— fetches up tomaxCountmessages starting atsequence.unsubscribe()— the no-argumentLoomReliableTopic.unsubscribe()method always throwsUnsupportedOperationExceptionbecause reliable topics have no server-side push subscription. This is separate fromLoomTopic.unsubscribe(int), which removes a client-managed polling subscription by id.
Default capacity is 10,000 messages per topic (configurable at construction time).
getIdGenerator→LoomIdGenerator(SnowflakeIdGeneratoron the server).- CRDTs:
getPNCounter/getGSet/getORSet/getLWWRegister→LoomPNCounter/LoomGSet/LoomORSet/LoomLWWRegister. The server-side CRDT state is managed by the data-structure registry. - CP primitives:
LoomLinearizableLock,LoomLinearizableSemaphore,LoomAtomicLong. Production posture is as described underLoomLinearizableLock/LoomLinearizableSemaphoreabove: the Java client manages lock/semaphore CP sessions internally; other CP extension handles are non-production only.
Two further Loom* wrappers are not data structures but share the same client surface:
getExecutorService(name)→LoomExecutorService— distributed task execution.newTransaction()/newTransaction(Duration)→LoomTransaction— a multi-key transactional unit (see Client API › Transactions).
ContinuousQueryCache (server-side filtered map view that auto-updates via change events) is exposed client-side as
LoomQueryCache in com.loomcache.client.query.
Related
Section titled “Related”- Client API reference — every method on the SDK facades.
- Architecture overview — how data structures ride the state machine.
- Chaos testing — linearizability verification.
LoomCache is an independent open-source project. It is not affiliated with, endorsed by, or sponsored by Hazelcast, Inc. or by any other company whose products are named in this documentation. “Hazelcast” is a trademark of Hazelcast, Inc.; references to it are nominative and describe only migration and comparison. All other product and company names are trademarks of their respective owners and are used for identification purposes only.