this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

docs: Add rationale for connection pooling, error handling, and rate limiting

+104
+57
notes/Design choices/Rationale/Backend > Connection pooling and caching.md
··· 1 + ## Overview 2 + - PostgreSQL and Redis are pooled with `bb8` (see `server/src/database.rs`). 3 + - Redis is used only for performance: bloom filters and timeline caches; PostgreSQL remains source of truth. 4 + - Background maintainer (`database::maintain`) periodically deletes old sessions and prunes timeline caches. 5 + 6 + ## PostgreSQL pool (bb8-postgres) 7 + - Built from `tokio_postgres::Config` with `NoTls`. 8 + - Pools are cloned everywhere via `DatabaseConnections::get_postgres_pool` to keep acquisition cheap. 9 + - Avoid `unwrap()` on `.get()`; surface bb8 run errors via `LuminaError::Bb8RunErrorPg`. 10 + 11 + Example (simplified): 12 + ```rust 13 + let pg_pool = PgConn { postgres_pool, redis_pool }; 14 + let client = pg_pool.postgres_pool.get().await?; // ? maps to LuminaError::Bb8RunErrorPg 15 + ``` 16 + 17 + ## Redis pool (bb8-redis) 18 + - Configured pool builder (currently max_size 50, 5s timeout, 5m idle timeout). 19 + - Connection type is `MultiplexedConnection`; use `redis::cmd(...).query_async(&mut **conn)`. Pool errors surface as `LuminaError::Bb8RunErrorRedis`. 20 + 21 + ### Bloom filters 22 + - Keys: `bloom:email`, `bloom:username`. 23 + - Populated at startup from Postgres (`database::setup`). 24 + - Checked in `user::register_validitycheck` before DB uniqueness queries. 25 + 26 + Example add/check: 27 + ```rust 28 + let mut conn = redis_pool.get().await?; 29 + redis::cmd("BF.ADD").arg("bloom:email").arg(email).query_async(&mut *conn).await?; 30 + let exists: bool = redis::cmd("BF.EXISTS").arg("bloom:email").arg(email).query_async(&mut *conn).await?; 31 + ``` 32 + 33 + ### Timeline cache 34 + - Cache keys: `timeline_cache:{tlid}:page:{page}`; metadata key: `timeline_cache:{tlid}:meta`. 35 + - Cache TTL: 3600s; high-traffic threshold: 100 lookups (global always high-traffic). 36 + - Write path (`cache_timeline_page`) stores page JSON and total count; read path (`get_cached_timeline_page`) returns `CachedTimelinePage`. 37 + - Invalidation: `invalidate_timeline_cache` SCANs matching keys and DELs; called after timeline writes and from the maintainer loop when timelines change. 38 + - Background invalidation cursor uses `timeline_cache_last_check` stored in Redis. 39 + 40 + Example invalidate: 41 + ```rust 42 + let mut conn = redis_pool.get().await?; 43 + timeline::invalidate_timeline_cache(&mut conn, tlid).await?; 44 + ``` 45 + 46 + ## Background maintainer 47 + - `database::maintain` (spawned at setup) runs two intervals: 48 + - Every 60s: delete sessions older than 20 days. 49 + - Every 300s: prune expired timeline cache entries; check timeline invalidations based on latest timestamps. 50 + 51 + ## Tests 52 + - `src/tests.rs` covers: pool setup, bloom filter add/exists, timeline cache invalidation. 53 + 54 + ## Operational cautions 55 + - Pool exhaustion: handle `.get()` errors; avoid panics from `unwrap()`. 56 + - Redis is non-authoritative; always fall back to Postgres on cache miss or bloom filter hit. 57 + - Keep TTLs and thresholds in sync if tuning (`timeline.rs` constants).
+20
notes/Design choices/Rationale/Backend > Error handling and logging.md
··· 1 + ## LuminaError 2 + - Defined in `server/src/errors.rs`. 3 + - Key variants: `DbError(LuminaDbError)`, `Bb8RunErrorPg(bb8::RunError<postgres::Error>)`, `Bb8RunErrorRedis(bb8::RunError<redis::RedisError>)`, auth/registration errors, `SerializationError(String)`, `RocketFaillure(Box<rocket::Error>)`. 4 + - Conversions implemented for Rocket, Postgres, Redis, and bb8 run errors. 5 + - Guidance: propagate the source error (`?`) to keep context; avoid lossy `to_string()` unless necessary. 6 + 7 + ## Logging 8 + - Event logging macros `info_elog!`, `warn_elog!`, `error_elog!`, `success_elog!` are used across DB/timeline flows. 9 + - `EventLogger` can log to stdout and (optionally) Postgres `logs` table (see `helpers/events.rs`). 10 + - When logging DB failures, prefer structured context (timeline id, page, user) to aid diagnosis. 11 + 12 + ## Failure-handling patterns 13 + - Pool acquisition: use `?` so `.get()` maps to `LuminaError::Bb8RunErrorPg/Redis`; avoid panics. 14 + - Redis is non-authoritative: on Redis errors, proceed with Postgres path to avoid request failure where possible. 15 + - Bloom filters: treat `BF.EXISTS` positives as hints; always confirm with Postgres. 16 + - Timeline cache: cache misses/failures should fall back to DB fetch; invalidation is best-effort. 17 + 18 + ## Operational notes 19 + - If Rocket state is missing (e.g., limiter), guards fail-open; verify state wiring at startup. 20 + - Add tracing/metrics around pool usage and cache hit/miss for production readiness.
+27
notes/Design choices/Rationale/Backend > Rate limiting.md
··· 1 + ## Overview 2 + - Token-bucket limiter in `server/src/rate_limiter.rs` using in-memory `HashMap` protected by `tokio::sync::Mutex`. 3 + - Rocket request guard `RateLimit` pulls `State<GeneralRateLimiter>`; missing state = allow (fail-open). 4 + - Separate wrapper types: `GeneralRateLimiter` and `AuthRateLimiter` so Rocket can manage both independently. 5 + 6 + ## Defaults / Tuning 7 + - Constructor requires `refill_per_second` and `capacity`; no hardcoded defaults. Decide per endpoint. 8 + - In-memory only: resets on process restart; not distributed. For multi-node, replace with shared store (e.g., Redis token bucket) or IP hash partitioning. 9 + - Keyed by client IP (`Request::client_ip()`); missing IP maps to key "unknown". 10 + 11 + ## Usage pattern 12 + ```rust 13 + // Configure and mount in Rocket managed state 14 + let limiter = GeneralRateLimiter::new(refill_per_second, capacity); 15 + rocket::build().manage(limiter); 16 + 17 + // Handler signature adds guard 18 + #[get("/protected")] 19 + async fn protected(_rate: RateLimit) -> &'static str { 20 + "ok" 21 + } 22 + ``` 23 + 24 + ## Gotchas 25 + - Fail-open if the guard cannot fetch state; ensure the limiter is registered in Rocket. 26 + - No per-route tuning baked in; provide distinct limiters via type wrappers if needed. 27 + - Single-threaded bottleneck: Mutex over HashMap is fine for moderate QPS; consider sharding or lock-free structure if contention grows.