diff --git a/.config/example.yml b/.config/example.yml index f73f4f1d79..fc85291c1e 100644 --- a/.config/example.yml +++ b/.config/example.yml @@ -52,6 +52,14 @@ db: # host: localhost # rejectUnauthorized: false +# ┌────────────────────────┐ +#───┘ ScyllaDB configuration └───────────────────────────────────── + +# scylla: +# nodes: ['localhost:9042'] +# keyspace: calckey +# replicationFactor: 1 + # ┌─────────────────────┐ #───┘ Redis configuration └───────────────────────────────────── @@ -67,7 +75,7 @@ redis: #db: 1 #user: default -# ┌─────────────────────────────┐ +# ┌────────────────────────────┐ #───┘ Cache server configuration └───────────────────────────────────── # A Redis-compatible server (DragonflyDB, Keydb, Redis) for caching diff --git a/packages/backend/native-utils/scylla-migration/cql/1689400417034_timeline/down.cql b/packages/backend/native-utils/scylla-migration/cql/1689400417034_timeline/down.cql new file mode 100644 index 0000000000..fff4c12856 --- /dev/null +++ b/packages/backend/native-utils/scylla-migration/cql/1689400417034_timeline/down.cql @@ -0,0 +1,9 @@ +DROP TABLE IF EXISTS reaction; +DROP MATERIALIZED VIEW IF EXISTS note_by_user_id; +DROP INDEX IF EXISTS note_by_id; +DROP INDEX IF EXISTS note_by_uri; +DROP INDEX IF EXISTS note_by_url; +DROP TABLE IF EXISTS note; +DROP TYPE IF EXISTS emoji; +DROP TYPE IF EXISTS note_edit_history; +DROP TYPE IF EXISTS drive_file; diff --git a/packages/backend/native-utils/scylla-migration/cql/1689400417034_timeline/up.cql b/packages/backend/native-utils/scylla-migration/cql/1689400417034_timeline/up.cql new file mode 100644 index 0000000000..bfcebd6319 --- /dev/null +++ b/packages/backend/native-utils/scylla-migration/cql/1689400417034_timeline/up.cql @@ -0,0 +1,83 @@ +CREATE TYPE IF NOT EXISTS drive_file ( + id ascii, + type ascii, + created_at timestamp, + name text, + comment text, + blurhash text, + url text, + thumbnail_url text, + is_sensitive boolean, + is_link boolean, + md5 ascii, + size int, + width int, + height int, +); + +CREATE TYPE IF NOT EXISTS note_edit_history ( + content text, + cw text, + files set>, + updated_at timestamp, +); + +CREATE TYPE IF NOT EXISTS emoji ( + name text, + url text, + width int, + height int, +); + +CREATE TABLE IF NOT EXISTS note ( -- Models timeline + created_at_date date, -- For partitioning + created_at timestamp, + id ascii, -- Post + visibility ascii, + content text, + name text, + cw text, + local_only boolean, + renote_count int, + replies_count int, + uri text, + url text, + score int, + files set>, + visible_users set, + mentions set, + emojis set>, + tags set, + has_poll boolean, + thread_id ascii, + channel_id ascii, -- Channel + channel_name text, + user_id ascii, -- User + reply_id ascii, -- Reply + renote_id ascii, -- Boost + note_edit set>, -- Edit History + updated_at timestamp, + reactions map, + reaction_emojis map>, + PRIMARY KEY (created_at_date, created_at) +) WITH CLUSTERING ORDER BY (created_at DESC); + +CREATE INDEX IF NOT EXISTS note_by_id ON note (id); +CREATE INDEX IF NOT EXISTS note_by_uri ON note (uri); +CREATE INDEX IF NOT EXISTS note_by_url ON note (url); + +CREATE MATERIALIZED VIEW IF NOT EXISTS note_by_user_id AS + SELECT * FROM note + WHERE user_id IS NOT NULL + AND created_at IS NOT NULL + AND created_at_date IS NOT NULL + PRIMARY KEY (user_id, created_at, created_at_date) + WITH CLUSTERING ORDER BY (created_at DESC); + +CREATE TABLE IF NOT EXISTS reaction ( + note_id ascii, + created_at timestamp, + user_id ascii, + reaction frozen, + PRIMARY KEY (note_id, created_at, user_id) +); diff --git a/packages/backend/src/config/types.ts b/packages/backend/src/config/types.ts index 7789c26e07..49a96642f2 100644 --- a/packages/backend/src/config/types.ts +++ b/packages/backend/src/config/types.ts @@ -16,6 +16,11 @@ export type Source = { disableCache?: boolean; extra?: { [x: string]: string }; }; + scylla?: { + nodes: string[]; + keyspace: string; + replicationFactor: number; + }, redis: { host: string; port: number; diff --git a/packages/backend/src/db/scylla.ts b/packages/backend/src/db/scylla.ts new file mode 100644 index 0000000000..56ee9bb016 --- /dev/null +++ b/packages/backend/src/db/scylla.ts @@ -0,0 +1,62 @@ +import config from "@/config/index.js"; +import { Client } from "cassandra-driver"; + +function newClient(): Client | null { + if (!config.scylla) { + return null; + } + + return new Client({ + contactPoints: config.scylla.nodes, + keyspace: config.scylla.keyspace, + }); +} + +export const scyllaClient = newClient(); + +export const prepared = { + timeline: { + insert: `INSERT INTO note ( + created_at_date, + created_at, + id, + visibility, + content, + name, + cw, + local_only, + renote_count, + replies_count, + uri, + url, + score, + files, + visible_users, + mentions, + emojis, + tags, + has_poll, + thread_id, + channel_id, + channel_name, + user_id, + user_id, + reply_id, + renote_id, + note_edit, + updated_at, + reactions, + reaction_emojis + ) + VALUES + (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, + select: { + byDate: "SELECT * FROM note WHERE created_at_date = ? AND created_at < ?", + byId: "SELECT * FROM note WHERE id IN ?", + byUri: "SELECT * FROM note WHERE uri = ?", + byUrl: "SELECT * FROM note WHERE url = ?", + byUserId: "SELECT * FROM note WHERE user_id = ? AND created_at < ?", + }, + delete: "DELETE FROM note WHERE id IN ?", + }, +}