Step 9 — Redis, and a second service¶
About 30 minutes. Branch: step-9.
The one new idea: a service with no port is a worker.
Why Tally needs one¶
Visitors can now submit ideas. Writing a submission means a database write, and
possibly an email — slow things, in the middle of a request that should be fast.
So the API will do the least it can: put the submission on a list and answer
202 Accepted. Something else does the writing.
Redis¶
Same arrangement as the database. REDIS_URL is injected; you never see a
credential.
The key is redis:, and it used to be cache:
A top-level cache: meant this once. It is now refused — an old example
showing cache: enabled: true will not parse.
Worse, cache: still exists — under a service, where it configures the
edge cache (step 16). Same word, two unrelated
subsystems, which is exactly why the top-level one was renamed.
The worker¶
Three lines, and read them for what is absent: there is no port.
That is the whole declaration. No port means no URL, no target group, no health check, no share of incoming traffic. It is not a smaller web service; it is a different kind of thing, and the only way the platform can tell is that you left the port out.
Note also container: tally — the same image as the web service. That is why
containers: and deploy: are separate lists: one build, two ways of running
it. No second Dockerfile, no second build, no chance of the two drifting apart.
What the worker does¶
while _running:
item = conn.blpop(SUBMISSION_QUEUE, timeout=1)
if item is None:
continue
handle(json.loads(item[1]))
Block on a list; write whatever arrives. Two details worth copying:
- It handles
SIGTERM. When you redeploy, the platform signals the process and gives it a moment. A worker that ignores that can lose the item in its hand. - A bad message does not stop it. One unparseable item is logged and skipped. A worker that dies on malformed input stops processing everything else too.
Redis is also doing a second job¶
Voting is rate-limited: one vote per person per item per hour, held as a counter with an expiry. It costs one round trip and cleans itself up.
That is the distinction to take away. Redis is for things that are cheap, hot, and fine to lose — a queue, a counter, a short-lived limit. The database is for things that must survive. Putting an hourly vote limit in Postgres would work and would be waste.
Deploy and watch both¶
--service matters now: there are two, and they print different things. The
worker says worker up, waiting for submissions.
Submit an idea on the board. Within a second or two the worker logs
accepted submission: … and the item appears. Vote twice on the same card and
the second is refused.
Two services, listed separately.
If it didn't work¶
| Symptom | Try | Usually |
|---|---|---|
| Submissions never appear | mecca logs tally --type container --service worker |
The worker is not running, or cannot reach Redis |
REDIS_URL is not set |
mecca debug tally |
redis: missing — or written as cache: |
| The worker keeps restarting | its container log | It crashed; the traceback is there |
| Voting never refuses | — | Each container sees a different client IP; the limit is per IP |