Milestone M3.5 | Analyst: Backend Lead | Date: May 2026
Cache-aside pattern via RedisCacheService in Loopless.Infrastructure/Caching/RedisCacheService.cs.
Request → Handler → Check Redis → [HIT] return cached response
→ [MISS] query PostgreSQL → store in Redis → return response
Endpoint benchmarked: GET /api/v1/users/me (user profile fetch)
Cache key: user:profile:{userId}
TTL: 15 minutes (defined as UserProfileTtl in RedisCacheService)
- Tool:
k6(script:devops/loadtest/k6-script.js) - Scenario: 50 concurrent VUs, 60 seconds sustained
- Target: authenticated requests to
/api/v1/users/me - Environment: local Docker Compose (
docker compose up)
http_req_duration (p50=42.1ms, p90=87.3ms, p95=112.4ms, p99=198.7ms)
http_reqs............: 12,843 total (214 req/s avg)
checks...............: 100% ✓ 12843 ✗ 0
http_req_duration (p50=2.3ms, p90=4.1ms, p95=5.8ms, p99=11.2ms)
http_reqs............: 13,921 total (232 req/s avg)
checks...............: 100% ✓ 13921 ✗ 0
First request after TTL expiry: ~45ms (DB read + cache write overhead ≈ 3ms) Subsequent requests: ~2.3ms (cache hit)
Cache invalidation: RedisCacheService.RemoveAsync($"user:profile:{userId}") called on profile update.
| Metric | No Cache | With Redis | Improvement |
|---|---|---|---|
| p50 latency | 42.1ms | 2.3ms | 94.5% |
| p95 latency | 112.4ms | 5.8ms | 94.8% |
| p99 latency | 198.7ms | 11.2ms | 94.4% |
| Throughput | 214 req/s | 232 req/s | +8.4% |
| Endpoint | Cache Key | TTL |
|---|---|---|
GET /api/v1/users/me |
user:profile:{userId} |
15 min |
GET /api/v1/skills |
skills:all |
5 min |
GET /api/v1/matching/discover |
matching:discover:{userId}:{page} |
5 min |