Edward Ribeiro
12f6a3e396
Phase 1: shared Redis pod — Django dual-backend cache + startup wiring
django/settings.py:
- REDIS_URL / CACHE_BACKEND env vars read at startup (written by start.sh)
- CACHES['default'] (DB0, KEY_PREFIX='sapl') switches between django-redis
and FileBasedCache transparently; IGNORE_EXCEPTIONS=True for graceful
degradation on Redis failure
- CACHES['ratelimit'] (DB1, no prefix) for cross-pod rate-limit counters
- RATELIMIT_USE_CACHE = 'ratelimit'
- Connection pool capped at 6/worker (1,200 pods × 2 workers × 6 = 14,400
peak connections; maxclients=20,000 gives 40% headroom)
start.sh:
- resolve_redis_url(): reads REDIS_URL from local namespace Secret (envFrom)
or falls back to global cluster Secret via k8s API
- configure_redis_cache(): ensures REDIS_CACHE waffle switch row exists (off)
- resolve_cache_backend(): reads waffle switch; sets CACHE_BACKEND=redis|file
- wait_for_redis(): blocks until Redis reachable; falls back gracefully
- write_env_file() now persists REDIS_URL + CACHE_BACKEND into pod .env
k8s manifests (docker/k8s/):
- redis-configmap.yaml: no persistence, allkeys-lru, maxmemory=5gb,
maxclients=20000, activedefrag, 4 databases
- redis-deployment.yaml: redis:7-alpine, 1 replica, liveness/readiness probes,
1Gi request / 6Gi limit
- redis-service.yaml: ClusterIP on port 6379
requirements: add django-redis==5.4.0
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
3 weeks ago
Edward Ribeiro
eaf4a8405a
Phase 0 hardening: nginx GeoIP2, rate limits, Gunicorn tuning, N+1 fix
- nginx: sendfile on, tcp_nopush, reduced keepalive/proxy timeouts
- nginx: GeoIP2 ASN-based bot blocking (cloud providers + known scrapers)
- nginx: UA blocklist (GPTBot, ClaudeBot, Chrome/98.0.4758 impersonator, etc.)
- nginx: rate-limit zones (30r/m general, 10r/m heavy/relatorios), 429/500 error pages
- nginx: proper ETags + Cache-Control on /media/ to stop 30GB logo re-transfers
- Dockerfile: install libnginx-mod-http-geoip2; download GeoLite2-ASN.mmdb via
BuildKit secret (key never baked into image layers); ARG GEOIP_CACHE_BUST for
forced re-download without --no-cache
- Gunicorn: workers 3->2, threads 8->4, timeout 300->120, max_memory 300->400MB
- Django: FILE_UPLOAD_MAX_MEMORY_SIZE=2MB, FILE_UPLOAD_TEMP_DIR for large uploads
- relatorios/views.py: fix N+1 in get_etiqueta_protocolos with bulk-fetch
MateriaLegislativa + DocumentoAdministrativo using select_related + dict lookups
- Add robots.txt, 429.html, 500.html static pages
- docker-compose.yaml: use sapl:local for local dev
- docker/README.md: build instructions with MAXMIND_LICENSE_KEY
- rate-limiter-v2.md: canonical planning document (Architecture through Phase 5)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
3 weeks ago