Sidekiq Redis DB Per-Namespace Isolation (Staging)
Summary
On EKS staging, every namespace’s hh-server app and Sidekiq workers share one
Redis host but must use a distinct Redis logical DB number so jobs enqueued in
one namespace are not executed by another namespace’s workers.
When two namespaces share both the host and the DB number, their Sidekiq queues
are literally the same Redis lists. A job enqueued by hh-engineering can be picked
up and run by a hh-venus worker (and vice-versa), producing the classic symptom:
“Reservation data is created on hh-engineering but executed by hh-venus sidekiq … the reservation data is not found.” — and both
/sidekiq/retriesdashboards show identical data.
This blocks QA on staging because background work (reservations, mailers, etc.) crosses namespace boundaries non-deterministically.
Why changing the DB number alone did not fix it
The DB number is hardcoded in two independent layers that both have to agree:
-
Rails app —
config/initializers/0_0_defined_constants.rb:db_num = is_development ? 10 : 11 # legacy: ALL non-dev namespaces → DB 11Staging deploys run with
RAILS_ENV=production, so every staging namespace resolved to DB 11. Setting a different DB on the Redis side (or a different env var the app doesn’t read) had no effect, because the app ignored it and reconnected to DB 11. -
KEDA ScaledObjects —
manifest/base/staging/private/scaledobjects/*.yaml:databaseIndex: "11" # autoscaler polls DB 11 for queue depthEven if the workers move to another DB, KEDA still polls DB 11, so autoscaling reads the wrong queue depth.
Both layers must point at the same DB for a namespace. Earlier ad-hoc edits left
namespaces inconsistent (e.g. hh-ballbot app on DB 13 while its KEDA still polled
DB 11; one sidekiq-google-reserve-lp ScaledObject polled DB 12 while its workers
used DB 11). Those silent app≠KEDA mismatches are exactly what this fix removes.
On the old DigitalOcean staging cluster the same shared-DB setup did not mix jobs, because there was effectively a single staging app per Redis DB. After the EKS migration multiple namespaces (
hh-engineering,hh-venus,hh-syn,hh-ballbot) run against the same Redis host concurrently, so the collision surfaced.
The fix: SIDEKIQ_DB_NUMBER
A single per-namespace value, SIDEKIQ_DB_NUMBER, now drives both layers from one
place (the namespace’s buildspec-hh-<ns>.yaml):
| Layer | File | How it reads the value |
|---|---|---|
| App (workers + Flipper) | config/initializers/0_0_defined_constants.rb | db_num = ENV.fetch('SIDEKIQ_DB_NUMBER', default_db_num) — baked into the image .env via build_time_env |
| KEDA autoscaler | manifest/.../scaledobjects/*.yaml | databaseIndex: "<REDIS_DB>" placeholder, substituted by set_env.sh from ${SIDEKIQ_DB_NUMBER:-11} |
Data flow per namespace build (CodeBuild):
buildspec env.variables: SIDEKIQ_DB_NUMBER="<n>"
│
├─► echo "SIDEKIQ_DB_NUMBER=<n>" >> build_time_env ─► Dockerfile cp build_time_env .env
│ └─► app boots, ENV.fetch('SIDEKIQ_DB_NUMBER') ─► workers connect to DB <n>
│
└─► set_env.sh: ["<REDIS_DB>"]="${SIDEKIQ_DB_NUMBER:-11}"
└─► sed substitutes <REDIS_DB> in ScaledObjects ─► KEDA polls DB <n>
Because app and KEDA derive from the same build variable, they can never drift.
If SIDEKIQ_DB_NUMBER is unset the app falls back to the legacy 10 (dev) / 11
(non-dev) behaviour and KEDA falls back to 11, so the change is backward compatible.
DB number assignment (staging namespaces)
Each namespace’s *-public and *-private sides share one DB number. Avoid
DB 12 (used by the DigitalOcean-staging code path) and DB 15 (FirebaseIdToken cache,
see below).
| Namespace | SIDEKIQ_DB_NUMBER |
|---|---|
hh-engineering | 11 |
hh-venus | 14 |
hh-syn | 10 |
hh-ballbot | 13 |
hh-ballbotkeeps 13 because its app was already hand-moved to DB 13; this fix only realigns its KEDAdatabaseIndexto match.
Replication checklist (apply per branch/namespace)
Each staging namespace deploys from its own long-lived branch (hh-engineering,
hh-venus, hh-syn, hh-ballbot) with its own copy of the manifests + buildspec.
The fix must be applied to each branch with that namespace’s DB number:
- App —
config/initializers/0_0_defined_constants.rb, in the non-DO branch:default_db_num = is_development ? 10 : 11 db_num = ENV.fetch('SIDEKIQ_DB_NUMBER', default_db_num) - KEDA — replace every hardcoded
databaseIndex: "<n>"withdatabaseIndex: "<REDIS_DB>"inmanifest/base/staging/private/scaledobjects/*.yamland anymanifest/overlays/staging/<ns>-private/{scaledobjects,patches}/*.yaml. - set_env.sh — in
manifest/overlays/staging/<ns>-private/set_env.sh, add to thereplacementsmap:
(["<REDIS_DB>"]="${SIDEKIQ_DB_NUMBER:-<n>}"<ns>-publichas no ScaledObjects, so it does not need this.) - buildspec — in
buildspec-hh-<ns>.yaml:- add
SIDEKIQ_DB_NUMBER: "<n>"underenv.variables - after the
build_time_envSSM writes, appendecho "SIDEKIQ_DB_NUMBER=${SIDEKIQ_DB_NUMBER}" >> build_time_env
- add
Verify after deploy
# Workers: confirm the app connects to the expected DB
kubectl --context <eks-staging> -n <ns>-private exec deploy/hungryhub-sidekiq -- \
ruby -e 'require "./config/environment"; puts REDIS_FOR_SIDEKIQ_CONFIG[:url]'
# KEDA: confirm databaseIndex matches
kubectl --context <eks-staging> -n <ns>-private get scaledobject sidekiq-default -o yaml \
| grep databaseIndex
# Cross-check: the two /sidekiq/retries dashboards should now show DIFFERENT data
# https://hh-engineering.my.id/sidekiq/retries
# https://hh-venus.my.id/sidekiq/retries
Related / out of scope
- FirebaseIdToken (
config/initializers/2_firebase_id_token.rb) uses the same shared Redis host on hardcoded DB 15. It is also shared across namespaces, but it is a token cache (not a job queue), so cross-namespace sharing does not mix jobs. Left as-is by this fix; revisit if token-cache isolation is ever needed. - Flipper (
config/initializers/3_flipper.rb) reusesREDIS_FOR_SIDEKIQ_CONFIG, so feature-flag state is now also isolated per namespace on staging — desirable.
See also: Sidekiq KEDA Autoscaling — Architecture & Tuning, Domain and Namespace List.