Add simple ObjectCache and cached SolanaBlockChain decorator #28

Closed
opened 2026-07-07 21:27:27 +02:00 by minimons · 0 comments
Owner

Background

Some RPC/data lookups are currently very slow and are called repeatedly with the same parameters. One concrete example is:

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:

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:

com.r35157.libs.objcache.ObjectCache
com.r35157.libs.objcache.ObjectCacheKey
com.r35157.libs.objcache.ObjectCacheEntry

ObjectCacheEntry

A cache entry should contain:

Object object
long insertedAtMillis
long ttlMillis

Do not store expiresAtMillis, because that is derived from:

insertedAtMillis + ttlMillis

The entry should make it possible to check:

ageMillis
isExpired

ObjectCache

The cache should be based on ConcurrentHashMap.

It should support only simple operations for now:

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:

owner
operation
parameters

For example:

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:

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:

com.r35157.libs.solana.impl.cache.CachedSolanaBlockChain

The decorator should:

- implement SolanaBlockChain
- contain a delegate SolanaBlockChain
- contain an ObjectCache
- contain/default a ttlMillis value

For the first baby-step, only cache:

getProgramAccounts(ΩSolanaProgramIdΩ programId, Set<SolanaProgramAccountMemcmpFilter> filters)

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.

First use case

The first target is reducing repeated calls to:

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.
## 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.
minimons added the enhancement label 2026-07-07 21:27:27 +02:00
minimons self-assigned this 2026-07-07 21:27:27 +02:00
minimons added this to the Nenjim project 2026-07-07 21:27:27 +02:00
minimons moved this to In Progress in Nenjim on 2026-07-13 18:48:49 +02:00
minimons moved this to Done in Nenjim on 2026-07-13 18:48:55 +02:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: r35157/com_r35157_nenjim-hubd-impl_ref#28