Skip to content

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

appmecca.yaml @ step-9
redis:
  enabled: true

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

  - name: worker
    container: tally
    command: "python -m app.worker"
    resources:
      memory: 192m

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

mecca app redeploy tally -b tutorial
mecca logs tally --type container --service worker -b tutorial

--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.

mecca app stats tally -b tutorial

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

Reset to the reference

git fetch upstream && git reset --hard upstream/step-9 && git push --force origin tutorial

Next: Step 10 — Scheduled work and email