mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-15 03:32:21 +03:00
docs(ops): relocate redis/production and contribution guides, move ROADMAP to repo root
This commit is contained in:
2
.github/pull_request_template.md
vendored
2
.github/pull_request_template.md
vendored
@@ -10,7 +10,7 @@
|
||||
## Validation
|
||||
|
||||
Choose the change type and focused loop from the
|
||||
[Contribution Golden Path](../docs/dev/CONTRIBUTION_GOLDEN_PATH.md). The full unit suite,
|
||||
[Contribution Golden Path](../docs/ops/CONTRIBUTION_GOLDEN_PATH.md). The full unit suite,
|
||||
Vitest, the 60% coverage gate, and the production build all run in CI on this PR (#8329):
|
||||
|
||||
- [ ] Change type: provider / routing / UI / i18n / CLI / DB / build-deploy / other
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
Thank you for your interest in contributing! This guide covers everything you need to get started.
|
||||
|
||||
For the official per-change workflow, start with the
|
||||
[Contribution Golden Path](docs/dev/CONTRIBUTION_GOLDEN_PATH.md). It maps provider, routing,
|
||||
[Contribution Golden Path](docs/ops/CONTRIBUTION_GOLDEN_PATH.md). It maps provider, routing,
|
||||
UI/UX, i18n, CLI, database, and build/deploy changes to their contracts, focused tests, CI
|
||||
coverage, and reconciliation steps.
|
||||
|
||||
@@ -210,7 +210,7 @@ Coverage notes:
|
||||
### Pull Request Requirements
|
||||
|
||||
Before opening a PR, use the
|
||||
[Contribution Golden Path](docs/dev/CONTRIBUTION_GOLDEN_PATH.md) to run the focused loop for
|
||||
[Contribution Golden Path](docs/ops/CONTRIBUTION_GOLDEN_PATH.md) to run the focused loop for
|
||||
what you changed. The full unit suite (4 CI shards), Vitest, the **60%+** coverage gate, and
|
||||
the production build are CI's responsibility — running them locally adds no signal the PR
|
||||
checks will not already give you, and on smaller machines it can saturate the host (#8084):
|
||||
|
||||
@@ -7,7 +7,7 @@ lastUpdated: 2026-08-06
|
||||
# OmniRoute Roadmap
|
||||
|
||||
> Version-gated, not date-gated: each milestone ships when its quality gates pass.
|
||||
> Current line: **v3.8.x** (this branch). Last updated: 2026-07-23.
|
||||
> Current line: **v3.8.x** (this branch). Last updated: 2026-08-06.
|
||||
|
||||
OmniRoute is heading from a monolithic router to a **modular AI platform**: a lightweight
|
||||
core engine, a typed SDK, and everything else as installable modules and plugins. The path
|
||||
@@ -1,178 +0,0 @@
|
||||
# Przewodnik konfiguracji Redis w produkcji
|
||||
|
||||
## Przegląd
|
||||
|
||||
Redis to **opcjonalna, miękka zależność** w OmniRoute — aplikacja degraduje się łagodnie (fallbacki
|
||||
w pamięci), gdy Redis jest niedostępny. W produkcji strojenie Redis zmniejsza opóźnienia dla trzech
|
||||
odrębnych obciążeń:
|
||||
|
||||
| Obciążenie | Sterownik | Fabryka klienta | Wzorzec kluczy |
|
||||
| ------------- | -------------------- | ----------------------------------------------- | --------------------------------- |
|
||||
| Rate limiting | `rateLimiter.ts` | `getRedisClient()` — leniwy singleton `ioredis` | Okna rate limit z atomowością Lua |
|
||||
| Cache auth | `apiKeys.ts` | Ponownie używa klienta `rateLimiter` | `auth:api_key:<sha256>` z TTL |
|
||||
| Magazyn quota | `redisQuotaStore.ts` | Osobny singleton `getRedisClient(url)` | Konfigurowalny per instancja |
|
||||
|
||||
---
|
||||
|
||||
## Bieżąca konfiguracja (domyślne wartości w kodzie)
|
||||
|
||||
| Ustawienie | Wartość | Gdzie |
|
||||
| -------------------------------------------- | ---------------------------------------------------------- | ------------------------------------ |
|
||||
| Zmienna środowiskowa `REDIS_URL` | `redis://redis:6379` (compose), opcjonalna | `rateLimiter.ts:5`, `.env.example` |
|
||||
| Zmienna środowiskowa `QUOTA_STORE_REDIS_URL` | osobna, może różnić się od `REDIS_URL` | `quota/storeFactory.ts` |
|
||||
| `QUOTA_STORE_DRIVER` | `"sqlite"` (domyślnie), `"redis"` opcjonalnie | `quota/storeFactory.ts` |
|
||||
| ioredis `maxRetriesPerRequest` | `3` | tworzenie klienta w `rateLimiter.ts` |
|
||||
| `enableReadyCheck` | nieustawione (domyślnie ioredis: `true`) | — |
|
||||
| `lazyConnect` | nieustawione (domyślnie ioredis: `false`) | — |
|
||||
| `retryStrategy` | nieustawione (domyślnie ioredis: baza 200 ms, wykładniczo) | — |
|
||||
| TLS / hasło / indeks DB | **nieskonfigurowane** | — |
|
||||
| Sentinel / Cluster | **nieskonfigurowane** — tylko samodzielny pojedynczy węzeł | — |
|
||||
|
||||
---
|
||||
|
||||
## Zalecane strojenie produkcyjne
|
||||
|
||||
### 1. Pula połączeń / opcje klienta (konstruktor ioredis `Redis`)
|
||||
|
||||
Obecny kod tworzy pojedyncze `new Redis(url)` bez własnych opcji. W produkcyjnych
|
||||
wdrożeniach multi‑replica przekaż fabrykę klienta w kodzie albo owiń `getRedisClient()`:
|
||||
|
||||
```typescript
|
||||
const redis = new Redis(REDIS_URL, {
|
||||
maxRetriesPerRequest: null, // no retry limit; let retryStrategy decide
|
||||
enableReadyCheck: true, // verify server is ready before accepting calls
|
||||
lazyConnect: true, // don't connect on construction; wait for first call
|
||||
retryStrategy: (times) => {
|
||||
if (times > 10) return null; // give up after 10 retries → reconnect later
|
||||
return Math.min(times * 200, 5000); // 200ms, 400ms, …, 5s cap
|
||||
},
|
||||
enableAutoPipelining: true, // coalesce concurrent commands into one TCP write
|
||||
keepAlive: 10000, // TCP keep‑alive every 10s
|
||||
});
|
||||
```
|
||||
|
||||
**Kluczowe kompromisy:**
|
||||
|
||||
- `maxRetriesPerRequest: null` + `retryStrategy` — preferowane w produkcji, aby chwilowe
|
||||
restarty Redis nie powodowały natychmiastowej awarii każdego żądania. Fallback w pamięci w
|
||||
`checkRateLimit()` obsługuje ścieżkę błędu.
|
||||
- `lazyConnect: true` — unika zależności startowej od dostępności Redis, zanim serwer
|
||||
zacznie przyjmować połączenia.
|
||||
- `enableAutoPipelining: true` — zmniejsza liczbę round-tripów przy współbieżnych sprawdzeniach rate-limit;
|
||||
korzystne przy >50 RPS na jednym połączeniu.
|
||||
|
||||
### 2. Konfiguracja serwera Redis (`redis.conf`)
|
||||
|
||||
```
|
||||
# Memory
|
||||
maxmemory 80% # leave room for OS page cache
|
||||
maxmemory-policy allkeys-lru # evict stale auth cache entries under pressure
|
||||
|
||||
# Persistence (optional — OmniRoute is crash‑safe without it)
|
||||
save 300 1 # snapshot at least every 5 min if ≥1 key changed
|
||||
appendonly no # AOF not needed; data is regeneratable
|
||||
appendfsync no # no fsync overhead (RDB is sufficient)
|
||||
|
||||
# Networking
|
||||
timeout 0 # no idle disconnect
|
||||
tcp-keepalive 300 # 5 min keep‑alive
|
||||
tcp-backlog 511 # connection backlog for bursty load
|
||||
|
||||
# Performance
|
||||
hz 10 # default; 100 for latency‑sensitive
|
||||
activedefrag yes # auto‑defragment when fragmentation >10%
|
||||
```
|
||||
|
||||
**Kompromis dla `maxmemory-policy allkeys-lru`:** Wpisy cache auth mogą zostać usunięte przy
|
||||
presji pamięci. To bezpieczne — `setCachedApiKey` zawsze uzupełnia cache przy miss, a
|
||||
fallback SQLite jest autorytatywny. Skrypt Lua rate-limitera tworzy małe klucze, które z
|
||||
założenia są krótkotrwałe.
|
||||
|
||||
### 3. Ustawienia Docker Compose
|
||||
|
||||
Produkcyjny compose (`docker-compose.prod.yml`) używa `redis:8.6.2-alpine`. Dodaj:
|
||||
|
||||
```yaml
|
||||
redis:
|
||||
image: redis:8.6.2-alpine
|
||||
command:
|
||||
[
|
||||
"redis-server",
|
||||
"--maxmemory",
|
||||
"512mb",
|
||||
"--maxmemory-policy",
|
||||
"allkeys-lru",
|
||||
"--activedefrag",
|
||||
"yes",
|
||||
"--save",
|
||||
"300 1",
|
||||
]
|
||||
healthcheck:
|
||||
test: ["CMD", "redis-cli", "ping"]
|
||||
interval: 10s
|
||||
timeout: 3s
|
||||
retries: 3
|
||||
start_period: 5s
|
||||
```
|
||||
|
||||
### 4. Uwagi dotyczące wielu instancji / skalowania
|
||||
|
||||
**Jeden Redis dla wszystkich replik** — skrypt Lua rate-limitera zależy od jednej
|
||||
autorytatywnej przestrzeni kluczy. Wiele instancji Redis za replikami straciłoby atomowość
|
||||
i podwoiłoby budżet. Używaj jednego Redis (lub klastra Redis Sentinel z failover) dla
|
||||
wszystkich replik aplikacji.
|
||||
|
||||
**Liczba połączeń:** Każda replika aplikacji otwiera **2 połączenia TCP** do Redis
|
||||
(klient rate limitera + klient magazynu quota). Przy 10 replikach → 20 połączeń, znacznie
|
||||
poniżej domyślnego limitu 10k połączeń instancji Redis.
|
||||
|
||||
### 5. Monitoring
|
||||
|
||||
Udostępnij przez endpoint health-check:
|
||||
|
||||
```typescript
|
||||
// src/app/api/monitoring/health/route.ts already calls rateLimiter functions
|
||||
// Add Redis-specific checks:
|
||||
// 1. PING latency via ioredis .ping()
|
||||
// 2. Memory usage via INFO memory
|
||||
// 3. Connection count via INFO clients
|
||||
// 4. Hit rate for maxmemory-policy (evicted_keys / keyspace_hits)
|
||||
```
|
||||
|
||||
Kluczowe metryki do obserwacji:
|
||||
|
||||
- **Evicted keys / sec** — jeśli trwale niezerowe, zwiększ `maxmemory`
|
||||
- **Blocked clients** — wartość niezerowa sugeruje wolne skrypty Lua lub wysoką kontencję
|
||||
- **Rejected connections** — osiągnięty limit połączeń; rzadkie przy 20 połączeniach
|
||||
|
||||
---
|
||||
|
||||
## Diagram architektury
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
subgraph App["App Replica"]
|
||||
RL[rateLimiter.ts]
|
||||
AK[apiKeys.ts]
|
||||
QS[redisQuotaStore.ts]
|
||||
end
|
||||
RL -- "REDIS_URL" --> R1[(Redis\nshared)]
|
||||
AK -- "reuses RL's client" --> R1
|
||||
QS -- "QUOTA_STORE_REDIS_URL" --> R2[(Redis\nquota store)]
|
||||
R1 --> R2 -- "can be same instance" --> R1
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Odnośniki
|
||||
|
||||
| Plik | Przeznaczenie |
|
||||
| ---------------------------------- | -------------------------------------------------------------- |
|
||||
| `src/shared/utils/rateLimiter.ts` | Główny klient Redis, skrypt Lua rate-limit, fallback w pamięci |
|
||||
| `src/lib/db/apiKeys.ts` | Cache auth — fallback Redis→SQLite |
|
||||
| `src/lib/quota/redisQuotaStore.ts` | Osobny klient Redis dla opcjonalnego magazynu quota |
|
||||
| `src/lib/quota/storeFactory.ts` | Przełącza sterowniki quota między `sqlite` a `redis` |
|
||||
| `docker-compose.prod.yml` | Kontener Redis w prod (obraz `redis:8.6.2-alpine`) |
|
||||
| `.env.example` | Dokumentacja zmiennych środowiskowych Redis |
|
||||
| `src/app/api/local/redis/` | Trasy API do orkiestracji kontenera dev |
|
||||
| `bin/cli/commands/redis.mjs` | Komendy CLI do orkiestracji kontenera dev |
|
||||
@@ -13,7 +13,7 @@ change type to its contracts, focused checks, and CI coverage.
|
||||
1. **Choose the base before editing.** Find the highest active `release/v*` branch and branch from
|
||||
its tip. Target that branch, not `main`. If a release freeze is active, do not target the frozen
|
||||
branch; use the next active cycle described in
|
||||
[Branching & Release Model](../ops/BRANCHING_MODEL.md).
|
||||
[Branching & Release Model](BRANCHING_MODEL.md).
|
||||
2. **Name the contracts.** Identify every catalog, schema, generated artifact, public API, or user
|
||||
interface that the change affects. The table below gives the minimum starting set.
|
||||
3. **Write or update focused tests.** Production changes in `src/`, `open-sse/`, `electron/`, or
|
||||
@@ -225,5 +225,5 @@ Before requesting review:
|
||||
- Never weaken assertions or drop required tests merely to match a moved base.
|
||||
|
||||
For release-freeze and retargeting rules, use
|
||||
[Branching & Release Model](../ops/BRANCHING_MODEL.md). For the complete CI inventory, use
|
||||
[Branching & Release Model](BRANCHING_MODEL.md). For the complete CI inventory, use
|
||||
[Quality Gates Reference](../architecture/QUALITY_GATES.md).
|
||||
Reference in New Issue
Block a user