Use Valkey instead of Redis
Redis 7.4+ is RSALv2/SSPL and 8.0+ adds AGPLv3. None of the three works for a closed-source commercial cloud service, and SSPL section 13 specifically targets offering the software's functionality as a service, which is what a control plane does. Valkey is BSD-3-Clause, under the Linux Foundation, forked from Redis 7.2.4. Deciding this now rather than after step 3 of PLAN.md stands up real infrastructure makes it free: Valkey speaks the same RESP protocol, so `ioredis` is untouched and only names change. `REDIS_URL` becomes `VALKEY_URL`, the exported client becomes `valkey`, and `/health` reports a `valkey` field. Two things deliberately do not change, documented in place so they are not "fixed" later: the `redis://` URL scheme, which names the wire protocol and is what every client actually parses (none understands `valkey://`), and `ioredis` itself, which is a RESP client whose name refers to the protocol's origin and not to the server we run. docker-compose.yml is new, pinning valkey/valkey:8-alpine alongside pgvector/pgvector:pg17 - the plain postgres image lacks the extension `frame_embeddings` needs. It makes the Valkey choice concrete rather than a note someone has to remember.
This commit is contained in:
+6
-2
@@ -4,8 +4,12 @@ PORT=3000
|
||||
DATABASE_URL=postgres://continuum:continuum@localhost:6432/continuum
|
||||
DATABASE_POOL_MAX=10
|
||||
|
||||
# Redis (state + streams)
|
||||
REDIS_URL=redis://localhost:6379
|
||||
# Valkey (state + streams), NOT Redis - Redis 7.4+ is RSALv2/SSPL and 8.0+
|
||||
# adds AGPLv3, none of which we can ship commercially. Valkey is BSD-3-Clause
|
||||
# and wire-compatible. The URL scheme stays `redis://` because that names the
|
||||
# RESP wire protocol, which is what the client libraries parse - no client
|
||||
# understands a `valkey://` scheme.
|
||||
VALKEY_URL=redis://localhost:6379
|
||||
|
||||
# Cloudflare R2 (S3-compatible)
|
||||
R2_ACCOUNT_ID=
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
# continuum-backend
|
||||
|
||||
Cloud control plane for Continuum. Bun + ElysiaJS, PostgreSQL (via pgCat/PgBouncer,
|
||||
with pgvector for frame embeddings), Redis Streams, and Cloudflare R2 storage.
|
||||
with pgvector for frame embeddings), Valkey Streams, and Cloudflare R2 storage.
|
||||
|
||||
## Run
|
||||
|
||||
```sh
|
||||
bun install
|
||||
cp .env.example .env # fill in DATABASE_URL, REDIS_URL, R2_*, SUPABASE_JWT_SECRET
|
||||
cp .env.example .env # fill in DATABASE_URL, VALKEY_URL, R2_*, SUPABASE_JWT_SECRET
|
||||
bun run db:generate && bun run db:migrate
|
||||
bun run dev
|
||||
```
|
||||
@@ -21,8 +21,14 @@ bun run dev
|
||||
instead of two hand-synced ones.
|
||||
- `src/s3/` - R2 presigned URL helpers (`PUT` for client uploads, `GET` for the
|
||||
edge proxy).
|
||||
- `src/queue/` - Redis client + `XADD` helper pushing onto `stream:camera_frames`
|
||||
for `continuum-ai-worker`.
|
||||
- `src/queue/` - Valkey client (`ioredis`) + `XADD` helper pushing onto
|
||||
`stream:camera_frames` for `continuum-ai-worker`.
|
||||
|
||||
Valkey, not Redis: Redis 7.4+ is RSALv2/SSPL and 8.0+ adds AGPLv3, none of
|
||||
which a commercial product can ship. Valkey is BSD-3-Clause and speaks the
|
||||
same RESP protocol, so `ioredis` is unchanged - only the server differs. See
|
||||
`continuum-common/docs/LICENSES.md`. `docker-compose.yml` here brings up
|
||||
Valkey and Postgres+pgvector for local development.
|
||||
- `src/routes/` - `/auth`, `/farms`, `/printers`, `/jobs`, and the edge-gateway
|
||||
WebSocket at `/ws/edge/v1`.
|
||||
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
# Local development dependencies for continuum-backend.
|
||||
#
|
||||
# Valkey rather than Redis on purpose: Redis 7.4+ is RSALv2/SSPL and 8.0+
|
||||
# adds AGPLv3, none of which a closed-source commercial product can ship.
|
||||
# Valkey is BSD-3-Clause and speaks the same RESP protocol, so both clients
|
||||
# (`ioredis` here, the `redis` crate in continuum-ai-worker) are unchanged.
|
||||
# See continuum-common/docs/LICENSES.md.
|
||||
#
|
||||
# pgCat/PgBouncer is deliberately not here - DATABASE_URL in .env.example
|
||||
# points at :6432 for production, but talking straight to Postgres on :5432
|
||||
# is one less moving part locally.
|
||||
|
||||
services:
|
||||
valkey:
|
||||
image: valkey/valkey:8-alpine
|
||||
ports:
|
||||
- "6379:6379"
|
||||
command: ["valkey-server", "--appendonly", "yes"]
|
||||
volumes:
|
||||
- valkey-data:/data
|
||||
healthcheck:
|
||||
test: ["CMD", "valkey-cli", "ping"]
|
||||
interval: 5s
|
||||
retries: 5
|
||||
|
||||
postgres:
|
||||
# pgvector/pgvector ships the extension prebuilt; the plain postgres
|
||||
# image does not, and `frame_embeddings` needs it.
|
||||
image: pgvector/pgvector:pg17
|
||||
ports:
|
||||
- "5432:5432"
|
||||
environment:
|
||||
POSTGRES_USER: continuum
|
||||
POSTGRES_PASSWORD: continuum
|
||||
POSTGRES_DB: continuum
|
||||
volumes:
|
||||
- postgres-data:/var/lib/postgresql/data
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U continuum -d continuum"]
|
||||
interval: 5s
|
||||
retries: 5
|
||||
|
||||
volumes:
|
||||
valkey-data:
|
||||
postgres-data:
|
||||
+3
-3
@@ -3,7 +3,7 @@ import { cors } from "@elysiajs/cors";
|
||||
import { swagger } from "@elysiajs/swagger";
|
||||
import { sql } from "drizzle-orm";
|
||||
import { db } from "./db";
|
||||
import { redis } from "./queue";
|
||||
import { valkey } from "./queue";
|
||||
import { authRoutes } from "./routes/auth";
|
||||
import { farmRoutes } from "./routes/farms";
|
||||
import { printerRoutes } from "./routes/printers";
|
||||
@@ -15,8 +15,8 @@ const app = new Elysia()
|
||||
.use(swagger({ path: "/docs" }))
|
||||
.get("/health", async () => {
|
||||
const rows = await db.execute<{ ok: number }>(sql`select 1 as ok`);
|
||||
const redisPing = await redis.ping();
|
||||
return { status: "ok", db: rows[0]?.ok === 1, redis: redisPing === "PONG" };
|
||||
const valkeyPing = await valkey.ping();
|
||||
return { status: "ok", db: rows[0]?.ok === 1, valkey: valkeyPing === "PONG" };
|
||||
})
|
||||
.use(authRoutes)
|
||||
.use(farmRoutes)
|
||||
|
||||
+5
-2
@@ -1,6 +1,9 @@
|
||||
// ioredis talks RESP, so it drives Valkey unchanged - see docs/LICENSES.md in
|
||||
// continuum-common for why the server is Valkey and not Redis. The `redis://`
|
||||
// scheme names the wire protocol, not the product.
|
||||
import Redis from "ioredis";
|
||||
|
||||
export const redis = new Redis(process.env.REDIS_URL ?? "redis://localhost:6379", {
|
||||
export const valkey = new Redis(process.env.VALKEY_URL ?? "redis://localhost:6379", {
|
||||
maxRetriesPerRequest: 3,
|
||||
});
|
||||
|
||||
@@ -16,7 +19,7 @@ export interface FrameIngestionTask {
|
||||
|
||||
/** Pushes a frame-ingestion task for continuum-ai-worker to pick up via XREADGROUP. */
|
||||
export async function enqueueFrameIngestion(task: FrameIngestionTask): Promise<string> {
|
||||
return redis.xadd(
|
||||
return valkey.xadd(
|
||||
CAMERA_FRAMES_STREAM,
|
||||
"*",
|
||||
"farmId",
|
||||
|
||||
Reference in New Issue
Block a user