This feature of Infinispan is tracked by ISPN-78, and the goal is to be able to support the storage of entries that exceed the size of any given JVM in the cluster and make use of distribution's ability to push chunks around the network to realise the total available memory in the grid.
To do so, a streaming interface is necessary since passing in large objects would create issues around serialization.
The streaming interface
One approach would be to add the following methods to the Cache interface (or perhaps the AdvancedCache interface):
OutputStream writeToKey(K key); InputStream readFromKey(K key);
Enabling Large Object support
LO support would need to be enabled in the cache's configuration as it would involve adding an interceptor to handle chunking. Users would also need to configure a chunk size.
The LargeObjectInterceptor
This interceptor would need to map traditional Key -> Value entries to something like:
Key -> LargeObjectMetadata
where LargeObjectMetadata is an internal class as follows:
class LargeObjectMetadata { int totalSize; String[] chunkKeys; }
Cache API methods
The following cache methods will be unsupported if the key passed in maps to a LargeObjectMetadata instance, and would throw a CacheException.
V put(K key, V value); V remove(K key, V oldValue); V putForExternalRead(K key, V value); boolean replace(K key, V oldValue, V value); V get(K key)
The only supported methods would be:
InputStream readFromKey(K key); OutputStream writeToKey(K key); V remove(K key); // except that this will always return a null boolean containsKey(K key); V replace(K key, V value); // except that this will always return a null
The streams
The streams created by readFromKey() and writeToKey() would need to read bytes and, upon hitting chunk sizes, would need to retrieve the next appropriate chunk from the cache.
Transactions
All stream operations are transactional and happen within the scope of a JTA transaction. This is to enable proper locking of chunks. And to prevent running out of memory by storing all chunks within a single transaction's context, eager locking is required.
Steps: writeToKey(K k)
- tx.begin();
- put(k, new LargeObjectMetadata())
- return new CacheOutputStream()
- Read bytes
- as chunk size is hit, move bytes to a new byte[]
- Store byte array: chunkKey = new UUID(); put(chunkKey, chunk)
- Update LargeObjectMetadata: totalSize += chunk.length, chunkKeys += chunkKey
- stream.close()
- Finalise remaining bytes into a partial chunk (same as step 5)
- tx.commit() to finalise tx and release locks.
Comments