|
| 1 | +# Migration Guide |
| 2 | + |
| 3 | +This branch combines three independent slices of work: |
| 4 | + |
| 5 | +1. **Background indexing scaling** — Dask scheduler topology, split out |
| 6 | + into separate k8s deployments |
| 7 | +2. **Redis caching + rate limiting** — read-through KV cache, per-user |
| 8 | + request rate limiter, persona-list cache with write-through |
| 9 | + invalidation |
| 10 | +3. **Assistants UX rework** — Manage Assistants + Assistant Gallery |
| 11 | + pages, seed script for local UX testing |
| 12 | + |
| 13 | +> **TL;DR — everything new is default OFF.** Deploying this branch |
| 14 | +> as-is does **not** change runtime behaviour for the chat path or the |
| 15 | +> background workers. You opt in per feature by setting env vars. |
| 16 | +
|
| 17 | +The only mandatory deltas at deploy time are: |
| 18 | +- Two new Python deps (`redis`, `bokeh`) installed automatically when |
| 19 | + the backend image rebuilds against the new `requirements/default.txt`. |
| 20 | +- A few non-secret env vars added to the configmap (all defaulting to |
| 21 | + empty/false — safe). |
| 22 | + |
| 23 | +Everything else (Redis pod, cache enablement, rate limits, new |
| 24 | +background topology) is opt-in. |
| 25 | + |
| 26 | +--- |
| 27 | + |
| 28 | +## 1. What's in this branch — quick map |
| 29 | + |
| 30 | +### Backend / infra |
| 31 | + |
| 32 | +| Slice | Files | Default state | |
| 33 | +|---|---|---| |
| 34 | +| Redis foundation + KV cache | `backend/danswer/redis/redis_pool.py`, `dynamic_configs/store.py`, `factory.py`, `configs/app_configs.py` | `REDIS_KV_CACHE_ENABLED=""` → OFF | |
| 35 | +| Per-user request rate limiter | `backend/danswer/server/middleware/request_rate_limit.py`, wired on `/send-message` + `/stream-answer-with-quote` | `REQUEST_RATE_LIMIT_ENABLED=""` → OFF | |
| 36 | +| Persona list cache | `backend/danswer/db/persona_cache.py`, `db/persona.py` (write-through invalidation), `ee/danswer/db/user_group.py` | `PERSONA_CACHE_ENABLED=""` → OFF | |
| 37 | +| Dask scheduler topology | `backend/danswer/background/update.py`, new `deployment/kubernetes/*` manifests | Existing single `background` pod still runs; new manifests not applied unless you `kubectl apply` them | |
| 38 | + |
| 39 | +### Frontend / UX |
| 40 | + |
| 41 | +| Page | What changed | Risk | |
| 42 | +|---|---|---| |
| 43 | +| `/assistants/mine` (Manage) | Drag-and-drop reorder, default pin, visibility toggle, search, bulk actions, undo toast | Cosmetic — backend unchanged | |
| 44 | +| `/assistants/gallery` (Browse) | Sections, filter chips, sort, column picker (persists in localStorage), doc-set names | Cosmetic — backend unchanged | |
| 45 | + |
| 46 | +### Tooling |
| 47 | + |
| 48 | +| Item | Purpose | |
| 49 | +|---|---| |
| 50 | +| `backend/scripts/seed_assistants.py` | Local dev seed of ~50 assistants for UX testing | |
| 51 | +| `REDIS_CACHING_PLAN.md` | Design rationale; not load-bearing for deploy | |
| 52 | + |
| 53 | +--- |
| 54 | + |
| 55 | +## 2. New Python dependencies |
| 56 | + |
| 57 | +`backend/requirements/default.txt` adds two pins: |
| 58 | + |
| 59 | +``` |
| 60 | +redis==5.0.8 # Redis client for the caching/rate-limit layer |
| 61 | +bokeh>=2.4.2,<3.0 # Dask scheduler dashboard at :8787 (bg-scaling commit) |
| 62 | +``` |
| 63 | + |
| 64 | +**Action**: rebuild the backend image. If you `pip install -r` in a |
| 65 | +venv, also re-run that. |
| 66 | + |
| 67 | +--- |
| 68 | + |
| 69 | +## 3. New env vars (env-configmap) |
| 70 | + |
| 71 | +All default to empty/false. Add to `darwin-kubernetes/env-configmap.yaml` |
| 72 | +(already done on this branch — verify and apply): |
| 73 | + |
| 74 | +```yaml |
| 75 | +# Redis connection (only used when one of the *_ENABLED flags below is true) |
| 76 | +REDIS_HOST: "redis" # in-cluster service name |
| 77 | +REDIS_PORT: "6379" |
| 78 | +REDIS_DB_NUMBER: "0" |
| 79 | +REDIS_SSL: "" |
| 80 | + |
| 81 | +# Feature flags — default OFF |
| 82 | +REDIS_KV_CACHE_ENABLED: "" # set to "true" to enable read-through KV cache |
| 83 | +REDIS_KV_CACHE_TTL_SECONDS: "86400" |
| 84 | +REQUEST_RATE_LIMIT_ENABLED: "" # set to "true" to enable |
| 85 | +REQUEST_RATE_LIMIT_PER_MINUTE: "" # set a number (e.g. "20") to cap; 0/empty = no per-min cap |
| 86 | +REQUEST_RATE_LIMIT_PER_HOUR: "" # set a number (e.g. "300") to cap; 0/empty = no per-hour cap |
| 87 | +``` |
| 88 | +
|
| 89 | +`PERSONA_CACHE_ENABLED` / `PERSONA_CACHE_TTL_SECONDS` live in |
| 90 | +`backend/danswer/configs/app_configs.py` defaults — you can override |
| 91 | +via env if you want to enable, but they're not in the configmap by |
| 92 | +default. Add a line if you plan to enable. |
| 93 | + |
| 94 | +--- |
| 95 | + |
| 96 | +## 4. New secrets |
| 97 | + |
| 98 | +`darwin-kubernetes/secrets.yaml` gains one optional key: |
| 99 | + |
| 100 | +```yaml |
| 101 | +stringData: |
| 102 | + redis_password: "" # empty for unauth'd in-cluster Redis |
| 103 | +``` |
| 104 | + |
| 105 | +The `api_server` and `background` deployments reference it with |
| 106 | +`optional: true`, so an absent or empty value is fine for the unauth'd |
| 107 | +in-cluster Redis StatefulSet. |
| 108 | + |
| 109 | +--- |
| 110 | + |
| 111 | +## 5. New Kubernetes manifests |
| 112 | + |
| 113 | +### 5a. Redis StatefulSet (always needed if any Redis flag is on) |
| 114 | + |
| 115 | +```bash |
| 116 | +kubectl apply -f darwin-kubernetes/redis-statefulset.yaml |
| 117 | +``` |
| 118 | + |
| 119 | +What it ships: a single-replica Redis 7.2-alpine, cache-only config |
| 120 | +(no AOF, no RDB snapshots, `maxmemory 256mb`, `allkeys-lru`), exposed |
| 121 | +as the `redis` ClusterIP Service on 6379. Pod restart drops the cache |
| 122 | +— that's intentional; the source of truth is Postgres, and counters |
| 123 | +self-heal as windows expire. |
| 124 | + |
| 125 | +### 5b. Dask scaling topology (optional, future) |
| 126 | + |
| 127 | +The bg-scaling commit adds these in `deployment/kubernetes/` (the |
| 128 | +upstream-style path, **not** the darwin-specific path): |
| 129 | + |
| 130 | +- `background-beat-deployment.yaml` |
| 131 | +- `background-celery-deployment.yaml` |
| 132 | +- `background-indexer-scheduler-deployment.yaml` |
| 133 | +- `dask-scheduler-service-deployment.yaml` |
| 134 | +- `dask-worker-deployment.yaml` |
| 135 | +- `docker-compose.dask-distributed.yml` (compose variant) |
| 136 | + |
| 137 | +Darwin currently runs `darwin-kubernetes/background-deployment.yaml` |
| 138 | +(a single combined background pod). **The new manifests are NOT |
| 139 | +applied automatically** and don't conflict with the existing |
| 140 | +`background-deployment.yaml`. |
| 141 | + |
| 142 | +If/when you want to switch Darwin to the new topology: |
| 143 | + |
| 144 | +1. Mirror the Redis env wiring from `darwin-kubernetes/background-deployment.yaml` |
| 145 | + into each of the new manifests (none of them currently have |
| 146 | + `REDIS_PASSWORD` wired — see §10). |
| 147 | +2. Apply the new manifests, scale the old background-deployment to 0. |
| 148 | +3. Verify each pod boots and the worker logs show clean startup. |
| 149 | + |
| 150 | +Out of scope for this PR; the relevant files are present so the |
| 151 | +migration is a one-step "apply" later. |
| 152 | + |
| 153 | +--- |
| 154 | + |
| 155 | +## 6. Deployment order |
| 156 | + |
| 157 | +Safe to roll out **in this order, defaults OFF**: |
| 158 | + |
| 159 | +1. Apply the configmap (no behaviour change — flags default OFF): |
| 160 | + ```bash |
| 161 | + kubectl apply -f darwin-kubernetes/env-configmap.yaml |
| 162 | + ``` |
| 163 | +2. Apply the (possibly updated) secrets: |
| 164 | + ```bash |
| 165 | + kubectl apply -f darwin-kubernetes/secrets.yaml |
| 166 | + ``` |
| 167 | +3. Apply the Redis StatefulSet: |
| 168 | + ```bash |
| 169 | + kubectl apply -f darwin-kubernetes/redis-statefulset.yaml |
| 170 | + ``` |
| 171 | +4. Rebuild + push the backend image (so it has `redis` and `bokeh` deps). |
| 172 | +5. Rebuild + push the web image (so the UX rewrites ship). |
| 173 | +6. Roll out the deployments: |
| 174 | + ```bash |
| 175 | + kubectl rollout restart deploy/api-server-deployment deploy/background-deployment deploy/web-server-deployment |
| 176 | + ``` |
| 177 | +7. Wait for health, then verify (§7). |
| 178 | + |
| 179 | +**At this point nothing has changed for users** — Redis is up but |
| 180 | +nothing is using it, and the api_server / background pods just have |
| 181 | +new dependencies + new env vars they're ignoring. |
| 182 | + |
| 183 | +--- |
| 184 | + |
| 185 | +## 7. Verification checklist (after deploy, BEFORE flipping flags) |
| 186 | + |
| 187 | +- [ ] All pods healthy: `kubectl get pods -l app=api-server -l app=background -l app=redis` |
| 188 | +- [ ] Redis responds: `kubectl exec deploy/redis -- redis-cli PING` → `PONG` |
| 189 | +- [ ] api_server logs show no errors importing the new modules |
| 190 | +- [ ] `/api/health` returns 200 |
| 191 | +- [ ] Open `/assistants/mine` — drag handles visible on visible rows, default-pin shows on first row, search input present |
| 192 | +- [ ] Open `/assistants/gallery` — sees Yours / Featured sections (and Shared if applicable), filter chips, sort dropdown, columns dropdown |
| 193 | +- [ ] Send a chat message — succeeds (proves rate limiter, even though OFF, didn't break the dependency wiring) |
| 194 | +- [ ] Background indexer still picks up new indexing attempts (bg-scaling change in `update.py`) |
| 195 | + |
| 196 | +--- |
| 197 | + |
| 198 | +## 8. Enabling features (per environment, in any order) |
| 199 | + |
| 200 | +Each flag is independent. Flipping one doesn't require the others. |
| 201 | + |
| 202 | +### 8a. Redis KV cache (settings, tokens, invited users) |
| 203 | + |
| 204 | +```bash |
| 205 | +kubectl set env configmap/env-configmap REDIS_KV_CACHE_ENABLED=true |
| 206 | +kubectl rollout restart deploy/api-server-deployment |
| 207 | +``` |
| 208 | + |
| 209 | +**Smoke test:** change an admin setting in pod A, verify it's visible |
| 210 | +on pod B within seconds (not TTL). |
| 211 | + |
| 212 | +### 8b. Per-user request rate limit |
| 213 | + |
| 214 | +Pick window values per your traffic shape. Recommended at "few |
| 215 | +hundred users" scale: |
| 216 | + |
| 217 | +```bash |
| 218 | +kubectl set env configmap/env-configmap \ |
| 219 | + REQUEST_RATE_LIMIT_ENABLED=true \ |
| 220 | + REQUEST_RATE_LIMIT_PER_MINUTE=20 \ |
| 221 | + REQUEST_RATE_LIMIT_PER_HOUR=300 |
| 222 | +kubectl rollout restart deploy/api-server-deployment |
| 223 | +``` |
| 224 | + |
| 225 | +**Smoke test:** send 21 chat messages in <60s — the 21st returns 429 |
| 226 | +with `Retry-After` header. |
| 227 | + |
| 228 | +### 8c. Persona list cache |
| 229 | + |
| 230 | +```bash |
| 231 | +# add to the configmap: |
| 232 | +PERSONA_CACHE_ENABLED: "true" |
| 233 | +PERSONA_CACHE_TTL_SECONDS: "86400" |
| 234 | +
|
| 235 | +kubectl apply -f darwin-kubernetes/env-configmap.yaml |
| 236 | +kubectl rollout restart deploy/api-server-deployment deploy/background-deployment |
| 237 | +``` |
| 238 | + |
| 239 | +> Background pod also gets restarted because `ee/danswer/db/user_group.py` |
| 240 | +> mutations from there must bust the cache. |
| 241 | + |
| 242 | +**Smoke test:** load `/assistants/mine`, edit one assistant's name in |
| 243 | +the admin UI, refresh — the name updates immediately (not on TTL). |
| 244 | + |
| 245 | +### 8d. Bg-scaling Dask topology |
| 246 | + |
| 247 | +Not enabled by env flag — it's a deployment-shape change. Out of |
| 248 | +scope for this PR's flip-a-switch flow; if/when adopted, see §5b. |
| 249 | + |
| 250 | +--- |
| 251 | + |
| 252 | +## 9. Rollback |
| 253 | + |
| 254 | +Each feature flag flips off independently. The two emergency knobs: |
| 255 | + |
| 256 | +- **Disable a feature flag** (no restart needed for new requests after |
| 257 | + flag propagates): |
| 258 | + ```bash |
| 259 | + kubectl set env configmap/env-configmap REDIS_KV_CACHE_ENABLED="" |
| 260 | + kubectl rollout restart deploy/api-server-deployment |
| 261 | + ``` |
| 262 | +- **Redis pod dies entirely** — every Redis call in this codebase is |
| 263 | + wrapped fail-open. The app falls back to direct Postgres reads (cache), |
| 264 | + permissive (rate limit), or no invalidation (persona cache; |
| 265 | + worst-case 24h staleness via TTL). **No outage.** Logs will be noisy |
| 266 | + with `Redis GET/SET/DEL failed: …` warnings — that's the signal that |
| 267 | + Redis needs attention. |
| 268 | + |
| 269 | +To roll back the **code** entirely: revert the merge commit, redeploy. |
| 270 | +All features default OFF means even without revert, setting all |
| 271 | +`*_ENABLED=""` returns the app to pre-PR behaviour. |
| 272 | + |
| 273 | +--- |
| 274 | + |
| 275 | +## 10. Known footguns |
| 276 | + |
| 277 | +### 10a. Bg-scaling k8s manifests don't have `REDIS_PASSWORD` wired |
| 278 | + |
| 279 | +The new `deployment/kubernetes/{background-celery,background-beat, |
| 280 | +background-indexer-scheduler,dask-scheduler-service,dask-worker}-deployment.yaml` |
| 281 | +files don't include the `REDIS_PASSWORD` env var pattern that the |
| 282 | +existing `darwin-kubernetes/background-deployment.yaml` has. |
| 283 | + |
| 284 | +- **Today's impact: none.** Darwin runs the darwin-kubernetes/ tree, |
| 285 | + not the upstream-style deployment/kubernetes/ tree, and none of the |
| 286 | + bg-scaling processes currently invoke persona-mutating code paths |
| 287 | + that would need Redis access. |
| 288 | +- **Becomes a real concern if** Darwin adopts the new topology AND |
| 289 | + `PERSONA_CACHE_ENABLED=true` AND a future Celery task ever mutates a |
| 290 | + Persona / Persona__User / Persona__UserGroup row. In that scenario |
| 291 | + the mutation succeeds, the cache bust logs a warning, and `/persona` |
| 292 | + serves stale data for up to 24h (TTL backstop). |
| 293 | +- **Fix when relevant**: mirror the `REDIS_PASSWORD` `secretKeyRef` |
| 294 | + block from `darwin-kubernetes/background-deployment.yaml` into each |
| 295 | + of the new manifests. |
| 296 | + |
| 297 | +### 10b. `backend/scripts/seed_assistants.py` bypasses persona-cache invalidation |
| 298 | + |
| 299 | +The seed script writes rows via raw `session.add(Persona(...))` rather |
| 300 | +than going through `upsert_persona()`, so `invalidate_personas_all()` |
| 301 | +never fires. |
| 302 | + |
| 303 | +- **Today's impact: none** if `PERSONA_CACHE_ENABLED` is the default |
| 304 | + OFF. |
| 305 | +- **If you seed with the cache enabled**, `/persona` will keep |
| 306 | + returning the pre-seed list until either a real mutation flows |
| 307 | + through the proper code path or the 24h TTL kicks in. Manual fix: |
| 308 | + ```bash |
| 309 | + kubectl exec deploy/redis -- redis-cli DEL danswer:personas:all:not_deleted |
| 310 | + ``` |
| 311 | +- **One-line code fix** if this becomes a recurring problem: import |
| 312 | + `invalidate_personas_all` and call it at the end of `main()` in |
| 313 | + `seed_assistants.py`. |
| 314 | + |
| 315 | +### 10c. `update.py` indexing scheduler change needs human eyes |
| 316 | + |
| 317 | +CLAUDE.md flags `update.py` / scheduler changes for manual confirmation |
| 318 | +(past breakage was silent — worker died with no logs). The bg-scaling |
| 319 | +commit modifies the Dask scheduler / submission path; verify locally |
| 320 | +via `python scripts/dev_run_background_jobs.py` and confirm the worker |
| 321 | +boots cleanly + dispatches an indexing attempt without errors. |
| 322 | + |
| 323 | +### 10d. Frontend's `chosen_assistants` array can hold stale ids after seed wipe |
| 324 | + |
| 325 | +If you run `python -m scripts.seed_assistants --clear` after seeding, |
| 326 | +deleted persona ids may remain in your `User.chosen_assistants` array. |
| 327 | +This is harmless — `get_personas` filters out non-existent ids — but |
| 328 | +will be cleaned up by the next preference write (any Manage page |
| 329 | +reorder / hide / show action). |
| 330 | + |
| 331 | +--- |
| 332 | + |
| 333 | +## 11. Manual tests recommended before merge |
| 334 | + |
| 335 | +These need eyes — automated coverage doesn't catch them: |
| 336 | + |
| 337 | +- [ ] **Background worker boots cleanly** on the rebased branch |
| 338 | + (CLAUDE.md gate). `python scripts/dev_run_background_jobs.py`, |
| 339 | + confirm clean startup and an indexing attempt dispatches. |
| 340 | +- [ ] **Seed 50 assistants locally**, open `/assistants/mine` and |
| 341 | + `/assistants/gallery`, exercise: drag-reorder, set default, hide via |
| 342 | + toggle, click a hidden row (toggle should pulse), search, bulk |
| 343 | + select, undo from a toast, switch column count in gallery, refresh |
| 344 | + page → column choice persisted. |
| 345 | +- [ ] **With KV cache enabled**, edit a setting on pod A while pod B |
| 346 | + is serving — second pod sees the new value without waiting for TTL. |
| 347 | +- [ ] **With rate limit enabled**, exceed the per-minute cap; verify |
| 348 | + 429 with `Retry-After` header. |
| 349 | +- [ ] **With persona cache enabled**, edit an assistant via admin UI; |
| 350 | + `/persona` reflects the edit immediately. |
| 351 | + |
| 352 | +--- |
| 353 | + |
| 354 | +## 12. Branch contents at-a-glance |
| 355 | + |
| 356 | +14 commits on top of `rajiv/add-claude` (PR #45): |
| 357 | + |
| 358 | +``` |
| 359 | +[BG-scale] Scale indexing via remote Dask scheduler topology |
| 360 | + |
| 361 | +[UX] Gallery: column picker as dropdown to match Sort |
| 362 | +[UX] Gallery: user-controllable column count (segmented control, persists) |
| 363 | +[UX] Show document-set names on assistant cards (was: count only) |
| 364 | +[UX] Parameterize gallery grid column count (default 3) |
| 365 | +[UX] Remove tools chip from Manage Assistants page |
| 366 | +[UX] Assistants UX polish: toggle highlight + gallery declutter |
| 367 | +[UX] Add backend/scripts/seed_assistants.py for local UX testing |
| 368 | +[UX] Assistant Gallery page UX overhaul |
| 369 | +[UX] Manage Assistants page UX overhaul |
| 370 | + |
| 371 | +[Redis] Persona list cache with explicit write-through invalidation |
| 372 | +[Redis] P2: per-user request rate limiter on chat/query endpoints |
| 373 | +[Redis] P1: Redis foundation + read-through KV cache |
| 374 | +[Redis] docs: add Redis caching & scaling plan |
| 375 | +``` |
| 376 | +
|
| 377 | +Total: **45 files changed, +5857 / −499**. 63 unit tests pass. |
0 commit comments