This issue introduces a very small and general object cache that can be used behind decorators for existing interfaces.
The goal is not to build a large cache framework yet. The first version should be simple, explicit and easy to reason about.
Goal
Add a general ObjectCache in:
com.r35157.libs.objcache
and use it from a decorator around SolanaBlockChain, initially only for caching getProgramAccounts(...).
The cache should be able to store any Object, but it should not know anything about the actual object types. Type casting belongs in the decorator, because the decorator knows the method and expected return type.
All other methods should just delegate directly to the real SolanaBlockChain.
For getProgramAccounts(...), the decorator should:
1. Build an ObjectCacheKey from method name and parameters.
2. Try ObjectCache.get(key).
3. If a valid cache entry exists, cast the cached object to Set<SolanaAccountInfo> and return it.
4. If no valid cache entry exists, call the delegate.
5. Store the result in ObjectCache with ttlMillis.
6. Return the result.
When caching sets, store an immutable copy, for example:
Set.copyOf(result)
This prevents callers from modifying the cached set.
It is possible to wire the cached decorator transparently so existing call sites do not need to change.
## Background
Some RPC/data lookups are currently very slow and are called repeatedly with the same parameters. One concrete example is:
```java
Set<SolanaAccountInfo> accountInfos = solanaBlockChain.getProgramAccounts(
JUPITER_PERPS_PROGRAM_ID,
Set.of(new SolanaProgramAccountMemcmpFilter(
POSITION_OWNER_OFFSET,
owner
))
);
```
This issue introduces a very small and general object cache that can be used behind decorators for existing interfaces.
The goal is not to build a large cache framework yet. The first version should be simple, explicit and easy to reason about.
## Goal
Add a general `ObjectCache` in:
```text
com.r35157.libs.objcache
```
and use it from a decorator around `SolanaBlockChain`, initially only for caching `getProgramAccounts(...)`.
The cache should be able to store any `Object`, but it should not know anything about the actual object types. Type casting belongs in the decorator, because the decorator knows the method and expected return type.
## Design
Add these simple classes:
```text
com.r35157.libs.objcache.ObjectCache
com.r35157.libs.objcache.ObjectCacheKey
com.r35157.libs.objcache.ObjectCacheEntry
```
### ObjectCacheEntry
A cache entry should contain:
```text
Object object
long insertedAtMillis
long ttlMillis
```
Do not store `expiresAtMillis`, because that is derived from:
```text
insertedAtMillis + ttlMillis
```
The entry should make it possible to check:
```text
ageMillis
isExpired
```
### ObjectCache
The cache should be based on `ConcurrentHashMap`.
It should support only simple operations for now:
```text
put(key, object, ttlMillis)
get(key)
removeExpired()
clear()
size()
```
`get(key)` should return the `ObjectCacheEntry` if it exists and has not expired.
If the entry has expired, `get(key)` should remove it and return `null`.
The cache should not start its own background cleanup thread. Cleanup can be called manually or later by NenjimHub/a periodic job.
### ObjectCacheKey
The key should be suitable for method-call based caching.
It should include something like:
```text
owner
operation
parameters
```
For example:
```text
owner: SolanaBlockChain.class.getName()
operation: getProgramAccounts
parameters: programId, filters
```
The parameters should be copied defensively where relevant, so mutable caller input cannot change the cache key after insertion.
## Explicitly out of scope for this issue
Do not add these yet:
```text
hit/miss statistics
lookup duration metadata
automatic background cleanup thread
dynamic proxy caching
annotations
generic typed cache facade
getOrCompute
freshness/maxAge policy
```
These may be added later if needed.
For this issue, the cache only handles hard TTL.
## CachedSolanaBlockChain
Add a decorator for `SolanaBlockChain`.
Suggested package/name:
```text
com.r35157.libs.solana.impl.cache.CachedSolanaBlockChain
```
The decorator should:
```text
- implement SolanaBlockChain
- contain a delegate SolanaBlockChain
- contain an ObjectCache
- contain/default a ttlMillis value
```
For the first baby-step, only cache:
```java
getProgramAccounts(ΩSolanaProgramIdΩ programId, Set<SolanaProgramAccountMemcmpFilter> filters)
```
All other methods should just delegate directly to the real `SolanaBlockChain`.
For `getProgramAccounts(...)`, the decorator should:
```text
1. Build an ObjectCacheKey from method name and parameters.
2. Try ObjectCache.get(key).
3. If a valid cache entry exists, cast the cached object to Set<SolanaAccountInfo> and return it.
4. If no valid cache entry exists, call the delegate.
5. Store the result in ObjectCache with ttlMillis.
6. Return the result.
```
When caching sets, store an immutable copy, for example:
```java
Set.copyOf(result)
```
This prevents callers from modifying the cached set.
## First use case
The first target is reducing repeated calls to:
```java
solanaBlockChain.getProgramAccounts(
JUPITER_PERPS_PROGRAM_ID,
Set.of(new SolanaProgramAccountMemcmpFilter(
POSITION_OWNER_OFFSET,
owner
))
);
```
A TTL around 60 seconds is enough for the first test.
## Acceptance criteria
* A general `ObjectCache` exists in `com.r35157.libs.objcache`.
* The cache can store arbitrary `Object` values by key.
* Entries contain `object`, `insertedAtMillis` and `ttlMillis`.
* Expired entries are ignored and removed on `get`.
* No `expiresAtMillis` field is stored.
* No statistics are implemented yet.
* No lookup-duration metadata is implemented yet.
* No internal cleanup thread is started by the cache.
* A `CachedSolanaBlockChain` decorator exists.
* `CachedSolanaBlockChain` caches `getProgramAccounts(...)`.
* Other `SolanaBlockChain` methods delegate directly.
* The project still compiles.
* It is possible to wire the cached decorator transparently so existing call sites do not need to change.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Background
Some RPC/data lookups are currently very slow and are called repeatedly with the same parameters. One concrete example is:
This issue introduces a very small and general object cache that can be used behind decorators for existing interfaces.
The goal is not to build a large cache framework yet. The first version should be simple, explicit and easy to reason about.
Goal
Add a general
ObjectCachein:and use it from a decorator around
SolanaBlockChain, initially only for cachinggetProgramAccounts(...).The cache should be able to store any
Object, but it should not know anything about the actual object types. Type casting belongs in the decorator, because the decorator knows the method and expected return type.Design
Add these simple classes:
ObjectCacheEntry
A cache entry should contain:
Do not store
expiresAtMillis, because that is derived from:The entry should make it possible to check:
ObjectCache
The cache should be based on
ConcurrentHashMap.It should support only simple operations for now:
get(key)should return theObjectCacheEntryif it exists and has not expired.If the entry has expired,
get(key)should remove it and returnnull.The cache should not start its own background cleanup thread. Cleanup can be called manually or later by NenjimHub/a periodic job.
ObjectCacheKey
The key should be suitable for method-call based caching.
It should include something like:
For example:
The parameters should be copied defensively where relevant, so mutable caller input cannot change the cache key after insertion.
Explicitly out of scope for this issue
Do not add these yet:
These may be added later if needed.
For this issue, the cache only handles hard TTL.
CachedSolanaBlockChain
Add a decorator for
SolanaBlockChain.Suggested package/name:
The decorator should:
For the first baby-step, only cache:
All other methods should just delegate directly to the real
SolanaBlockChain.For
getProgramAccounts(...), the decorator should:When caching sets, store an immutable copy, for example:
This prevents callers from modifying the cached set.
First use case
The first target is reducing repeated calls to:
A TTL around 60 seconds is enough for the first test.
Acceptance criteria
ObjectCacheexists incom.r35157.libs.objcache.Objectvalues by key.object,insertedAtMillisandttlMillis.get.expiresAtMillisfield is stored.CachedSolanaBlockChaindecorator exists.CachedSolanaBlockChaincachesgetProgramAccounts(...).SolanaBlockChainmethods delegate directly.