60edd473c7
- Updates CLAUDE.md tech stack to current versions (Compose 1.10.3, Kotlin 2.3.20). - Reframes kotlin-multiplatform iOS as mature; adds secp256k1-kmp 0.23.0 references. - Updates desktop-expert Main.kt references (code grew from ~270 to 1341 lines and NavigationRail moved to ui/deck/SinglePaneLayout.kt); replaces obsolete "hardcoded ctrl = true" anti-pattern note with accurate isMacOS branching. - Removes compose-desktop.md (superseded by desktop-expert/). - Adds nostr-expert references: nip19-bech32, event-factory, crypto-and-encryption, large-cache. Adds kotlin-expert/common-utilities, compose-expert/rich-text-parsing, android-expert/image-loading. - New skills: account-state (Account + LocalCache), relay-client (subscriptions, filter assemblers, preloaders), feed-patterns (FeedFilter + FeedViewModel family), auth-signers (NostrSigner across internal / NIP-46 / NIP-55).
47 lines
2.9 KiB
Markdown
47 lines
2.9 KiB
Markdown
# Preloaders & Rate Limiting
|
|
|
|
Bulk-fetch patterns for data that needs to come in over relays but isn't directly user-requested (e.g. "hydrate metadata for 200 pubkeys the user might scroll past", "prefetch images before a gallery opens").
|
|
|
|
## Files
|
|
|
|
Under `commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/relayClient/preload/`:
|
|
|
|
- **`MetadataPreloader.kt`** — `class MetadataPreloader(rateLimiter: MetadataRateLimiter, ...)`. Accepts "I need metadata for pubkeys X, Y, Z" requests and batches them into relay-friendly subscriptions.
|
|
- **`MetadataRateLimiter.kt`** — token-bucket-style limiter. Caps how many metadata fetches are in-flight simultaneously so relays don't rate-limit the client.
|
|
|
|
Additional preloader seams worth knowing:
|
|
- `MetadataPreloader` is itself called by `FeedMetadataCoordinator` (`assemblers/FeedMetadataCoordinator.kt`) — feeds generate the bulk request set.
|
|
- Image prefetching uses an `ImagePrefetcher` interface (see `preload/MetadataPreloader.kt` for the contract). Implementations live in platform code: Android uses Coil's prefetch API, Desktop uses the Skia loader.
|
|
|
|
## When to Use a Preloader
|
|
|
|
- You have a **set** of pubkeys/note ids and you want them in `LocalCache` "soon" but the user isn't waiting on any single one.
|
|
- You need to **coalesce** many small requests from different composables into one relay subscription.
|
|
- You care about **backpressure** — a normal `Subscribable` fires immediately; a preloader defers and batches.
|
|
|
|
If the user is looking at something right now, use a `Subscribable` instead (`MetadataFilterAssembler`). Preloaders are for "might need this".
|
|
|
|
## Typical Flow
|
|
|
|
```kotlin
|
|
// Inside a coordinator, e.g. when a feed list emits the set of visible pubkeys
|
|
metadataPreloader.request(pubKeys)
|
|
// MetadataPreloader deduplicates against what it has already scheduled, clips
|
|
// with the rate limiter, and eventually opens a relay sub through the normal
|
|
// ComposeSubscriptionManager plumbing.
|
|
```
|
|
|
|
The preloader is process-wide (one instance, injected where needed). Don't instantiate per-screen — that defeats the deduplication.
|
|
|
|
## Gotchas
|
|
|
|
- **Rate limits are cooperative.** The limiter throttles the client; relays enforce their own limits. If you see `NOTICE: rate-limited` frames, the preloader's budget is too generous — lower the bucket size.
|
|
- **Don't preload sensitive kinds** (encrypted DMs, gift-wrapped events) — they aren't speculatively useful and waste bandwidth.
|
|
- **Preload scope should match visibility scope.** When a screen unmounts, cancel its preload requests, otherwise you keep fetching for off-screen data.
|
|
- **`MetadataRateLimiter` is not a general rate limiter.** Reuse it only for metadata-like patterns. For publishing rate limits use relay-specific logic in the signer path.
|
|
|
|
## Related
|
|
|
|
- `filter-assemblers.md` — the atomic unit the preloader assembles.
|
|
- `kotlin-coroutines/references/relay-patterns.md` — how the underlying relay pool handles concurrent subscriptions.
|