agentsclimarketplace

Caching strategies

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

Agent skills for profiling and optimizing mobile app performance (startup, memory, frame-rate, network).

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.

What its author says it does

Copied from the file, not written here

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

SKILL.md

7.3 KB, 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.

Keep looking

Skills are one crate of 328,083. 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.