t3-code-android-nightly/.repos/alchemy-effect/examples/gcp-memorystore-redis
Julius Marminge 6f9cea00ae
chore(refs): sync Effect and Alchemy references to 4.0.1 and beta.80 (#16170)
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-05 13:22:30 -07:00
..
src chore(refs): sync Effect and Alchemy references to 4.0.1 and beta.80 (#16170) 2026-10-05 13:22:30 -07:00
test chore(refs): sync Effect and Alchemy references to 4.0.1 and beta.80 (#16170) 2026-10-05 13:22:30 -07:00
alchemy.run.ts chore(refs): sync Effect and Alchemy references to 4.0.1 and beta.80 (#16170) 2026-10-05 13:22:30 -07:00
package.json chore(refs): sync Effect and Alchemy references to 4.0.1 and beta.80 (#16170) 2026-10-05 13:22:30 -07:00
README.md chore(refs): sync Effect and Alchemy references to 4.0.1 and beta.80 (#16170) 2026-10-05 13:22:30 -07:00
tsconfig.json chore(refs): sync Effect and Alchemy references to 4.0.1 and beta.80 (#16170) 2026-10-05 13:22:30 -07:00

gcp-memorystore-redis

A fixed-window rate limiter on Cloud Run, counting in Memorystore for Redis. Each key gets 5 hits per 60-second window; after that the API answers 429 until the window resets.

Route Returns
POST /hit/:key 200 (or 429 + retry-after once over the limit) with the counter below
GET /count/:key the counter, without counting a hit
{
  "key": "user-42",
  "count": 6,
  "limit": 5,
  "remaining": 0,
  "limited": true,
  "resetInSeconds": 41
}

A hit is one Redis round trip: SET key 0 EX 60 NX starts the window (the TTL is only set when the key is new), INCR counts the hit and keeps the TTL, and TTL reports the time left. When the key expires the window starts over.

Architecture

  • GCP.Redis.Instance Counters — BASIC tier (one node, no replica), 1 GiB, Redis AUTH on, on the project's default network, in the stack's region.
  • GCP.Function Api — public Cloud Run service (invokerIamDisabled: true) with Direct VPC egress onto the default network and subnet (template.vpcAccess, PRIVATE_RANGES_ONLY).

Memorystore only has a private IP. Direct VPC egress gives each revision an interface on the network, so traffic to private ranges (the Redis IP) goes through the VPC and everything else leaves the normal way. There is no Serverless VPC Access connector to create or pay for.

Bindings

Binding Role Injects
GCP.Redis.ReadWriteRedis(counters) none redis://:AUTH@host:port for Counters

Memorystore has no data-plane IAM: RESP authenticates with the instance's AUTH string, so the binding grants the runtime service account no role. At deploy time Alchemy reads the instance's private IP, port, and AUTH string (instances.getAuthString, with the deployer's credentials) and carries the URL to the service through its runtime context. The client is alchemy/Redis, so get, set, incr, expire, pipeline, and send behave the same as on any other Redis provider. GCP.Redis.ReadRedis / WriteRedis (with *Http layers) are the narrower clients.

Deploy

Credentials come from your alchemy profile: run alchemy profile once and pick GCP (Service account JSON for a key file, or Stored for an access token or key kept in ~/.alchemy/credentials, plus a default region), then deploy with --profile <name>.

pnpm deploy --profile <name>

Creating the instance takes about four minutes; the service deploys once it is ready.

curl -X POST "$URL/hit/user-42"
curl "$URL/count/user-42"

Test

ALCHEMY_PROFILE=<name> bun test

Deploys the stack, checks the instance out of band (READY, BASIC, 1 GiB, AUTH on, private IP), hits a key until it is rate limited — asserting the count, the window TTL, and the 429 — then destroys the stack and checks that the instance and the service are gone. Needs Docker (the service image is built locally). Expect around 15 minutes, most of it Memorystore create and delete.

Destroy

pnpm destroy --profile <name>

Deletes the service, its image repository and service account, and the Redis instance. Deleting the instance also takes several minutes; destroy waits until it is gone.