Skip to content

Commit 5ce3f49

Browse files
rajivmlclaude
andcommitted
docs: add MIGRATION.md covering Redis / bg-scaling / UX
Single migration doc covering all three slices on this branch: 1. Background indexing scaling (Dask topology) 2. Redis caching + rate limiting 3. Assistants UX rework Organized for an operator: TL;DR up top ("everything default OFF"), new deps/env/secrets summarized, deployment order, verification checklist BEFORE flipping any flags, per-feature enable steps, and the known footguns (k8s manifests missing REDIS_PASSWORD env wire-up in the bg-scaling path, seed script bypassing persona cache, CLAUDE.md update.py gate). Plus the recommended manual test list and the branch's commit map. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 85ba241 commit 5ce3f49

1 file changed

Lines changed: 377 additions & 0 deletions

File tree

MIGRATION.md

Lines changed: 377 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,377 @@
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

Comments
 (0)