agentsclimarketplace

Caching strategies

Skill almasumdev/awesome-mobile-performance-agent-skills/.github/skills/patterns/caching-strategies

Apply read-through, write-through, TTL, LRU, and encrypted caches correctly across memory, disk, and secure storage on mobile.From its SKILL.md

Install
npx -y skills add almasumdev/awesome-mobile-performance-agent-skills --skill caching-strategies

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

2 things to look at

  • no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
  • 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

SKILL.md

7.3 KB, ~1.7k tokens by cl100k_base, as published. Nobody here has run it

Caching Strategies

Instructions

Every cache is a tradeoff between freshness, memory, disk, and security. Pick the strategy to match the data. Mis-sized or mis-policied caches cause jank (evicting too aggressively), memory kills (unbounded growth), or stale UI (never revalidating).

1. Strategy Catalog

StrategyWhen to use
Read-throughDefault for feed/list screens. Cache sits in front of origin.
Write-throughWrites go to cache + origin synchronously. Safe but slow.
Write-behindWrite cache immediately; persist to origin async. Fast but risky.
Cache-asideApp checks cache; on miss, fetches and populates. Most flexible.
Stale-while-revalidateServe cached while refreshing in background.
TTL (time-to-live)Expire entries after N seconds. Easy to reason about.
LRU (least recently used)Bounded by size; evicts coldest. Best for memory caches.
LFU (least frequently used)Retains hot items. Better for mixed workloads.
Keyset / content-addressableImmutable blobs keyed by hash (e.g., images).

2. Sizing

LayerSize budget (typical)
JSON/memory cache4–16 MB, LRU by entry size
Image memory cache20–25% of process heap
HTTP disk cache (OkHttp/URLCache)20–50 MB for API JSON
Image disk cache128–256 MB
SQLite/Drift/RealmProduct-driven; cap at 200 MB and evict LRU rows

3. Read-Through + Stale-While-Revalidate

Most data is "it's fine if it's 60 seconds old." Render cache, refresh in background, diff in.

Kotlin:

class FeedRepository(private val api: Api, private val cache: Cache<String, Feed>) {
    fun feed(userId: String): Flow<Feed> = flow {
        cache.get(userId)?.let { emit(it) }       // instant render
        try {
            val fresh = api.feed(userId)
            cache.put(userId, fresh)
            emit(fresh)
        } catch (e: IOException) { /* keep cached version */ }
    }
}

Swift:

actor FeedRepository {
    private let api: Api
    private var cache: [String: Feed] = [:]

    func feed(for userId: String) -> AsyncStream<Feed> {
        AsyncStream { cont in
            Task {
                if let c = cache[userId] { cont.yield(c) }
                if let fresh = try? await api.feed(userId) {
                    cache[userId] = fresh
                    cont.yield(fresh)
                }
                cont.finish()
            }
        }
    }
}

4. TTL and LRU

Pure TTL is easy but can cause all entries to expire simultaneously. Combine with an LRU that tracks size.

Kotlin using Caffeine:

val cache: Cache<String, Feed> = Caffeine.newBuilder()
    .maximumWeight(8L * 1024 * 1024)                  // 8 MB
    .weigher<String, Feed> { _, v -> v.byteSize }
    .expireAfterWrite(10, TimeUnit.MINUTES)
    .recordStats()
    .build()

Swift NSCache is LRU with size limits:

let cache = NSCache<NSString, Feed>()
cache.totalCostLimit = 8 * 1024 * 1024
cache.setObject(feed, forKey: userId as NSString, cost: feed.byteSize)

5. Persistence Layer

For true offline, persist in a local DB or a typed disk cache:

  • Drift (Flutter) / Isar / Realm — typed, queryable, indexed.
  • Room (Android) — first-party, coroutines/Flow support.
  • Core Data / SwiftData (iOS) or GRDB / Realm.
  • WatermelonDB / Realm / MMKV for React Native.

Rule: the DB is the source of truth. Network writes into the DB; UI reads from the DB reactively. This eliminates two-source-of-truth bugs.

6. Encrypted Caches

For anything that touches PII, health, financial, or auth:

PlatformStorage
iOSKeychain for secrets; file protection class completeUntilFirstUserAuthentication for PII.
AndroidEncryptedSharedPreferences / EncryptedFile + Keystore
Flutterflutter_secure_storage (Keychain/Keystore under the hood)
RNreact-native-keychain, expo-secure-store

Android Jetpack Security example:

val masterKey = MasterKey.Builder(ctx)
    .setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
    .build()

val prefs = EncryptedSharedPreferences.create(
    ctx, "secure_prefs", masterKey,
    EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
    EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM,
)

iOS Keychain (simple wrapper):

func set(_ value: String, key: String) throws {
    let q: [CFString: Any] = [
        kSecClass: kSecClassGenericPassword,
        kSecAttrAccount: key,
        kSecValueData: Data(value.utf8),
        kSecAttrAccessible: kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly,
    ]
    SecItemDelete(q as CFDictionary)
    let status = SecItemAdd(q as CFDictionary, nil)
    guard status == errSecSuccess else { throw KeychainError(status) }
}

Rules:

  • Never cache bearer tokens in UserDefaults / SharedPreferences / AsyncStorage.
  • Tie sensitive cache lifetime to auth session.
  • On logout, zero the cache (cache.invalidateAll(), delete encrypted files).

7. Invalidation

Surgical invalidation beats blanket invalidation.

  • Tag cache entries by domain (user:123, feed:home). On mutation, invalidate only matching tags.
  • react-query: queryClient.invalidateQueries({ queryKey: ['feed'] }).
  • Paging 3: pagingSource.invalidate().

8. Metrics

Every cache should expose:

  • Hit rate (hits / (hits + misses)).
  • Eviction rate.
  • Byte size.
  • p95 get / put latency.

Log these daily. A hit rate trending down is the early signal of a cache key bug.

9. Anti-patterns

  • Unbounded caches — always cap by size or count.
  • Caching everything — auth responses, one-time requests, huge binaries: usually not worth it.
  • Cache objects that reference a Context/View — guaranteed leak.
  • Shared cache across users on the same device — data leakage after account switch; partition by user id.

Checklist

  • Cache strategy selected per data type; documented.
  • Memory caches bounded by size (LRU) and TTL.
  • Disk caches bounded; eviction policy tested.
  • Sensitive data uses Keychain / Keystore / secure storage APIs.
  • DB is the single source of truth where offline matters.
  • Surgical invalidation on writes; blanket clearAll only on logout.
  • Hit rate, eviction rate, and size emitted as telemetry.
  • Cache is partitioned per user / account.

What ships with it

Read from the repository

Just SKILL.md. No reference files, no scripts.

Keep looking

Skills are one crate of 325,949. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.