mirror of
https://github.com/versia-pub/server.git
synced 2025-12-06 16:38:19 +01:00
feat(database): ✨ Implement read replicas for database
This commit is contained in:
parent
c75306c58b
commit
bc0943c569
|
|
@ -10,6 +10,14 @@ username = "versia"
|
|||
password = "mycoolpassword"
|
||||
database = "versia"
|
||||
|
||||
# Add any eventual read-only database replicas here
|
||||
# [[database.replicas]]
|
||||
# host = "other-host"
|
||||
# port = 5432
|
||||
# username = "versia"
|
||||
# password = "mycoolpassword2"
|
||||
# database = "replica1"
|
||||
|
||||
[redis.queue]
|
||||
# Redis instance for storing the federation queue
|
||||
# Required for federation
|
||||
|
|
|
|||
|
|
@ -27,6 +27,39 @@
|
|||
"type": "string",
|
||||
"minLength": 1,
|
||||
"default": "versia"
|
||||
},
|
||||
"replicas": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"host": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"port": {
|
||||
"type": "integer",
|
||||
"minimum": 1,
|
||||
"maximum": 65535,
|
||||
"default": 5432
|
||||
},
|
||||
"username": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"password": {
|
||||
"type": "string",
|
||||
"default": ""
|
||||
},
|
||||
"database": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"default": "versia"
|
||||
}
|
||||
},
|
||||
"required": ["host", "username"],
|
||||
"additionalProperties": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": ["username"],
|
||||
|
|
|
|||
|
|
@ -1,11 +1,13 @@
|
|||
import { getLogger } from "@logtape/logtape";
|
||||
import { drizzle } from "drizzle-orm/node-postgres";
|
||||
import chalk from "chalk";
|
||||
import { type NodePgDatabase, drizzle } from "drizzle-orm/node-postgres";
|
||||
import { withReplicas } from "drizzle-orm/pg-core";
|
||||
import { migrate } from "drizzle-orm/postgres-js/migrator";
|
||||
import { Client } from "pg";
|
||||
import { Pool } from "pg";
|
||||
import { config } from "~/packages/config-manager";
|
||||
import * as schema from "./schema";
|
||||
|
||||
export const client = new Client({
|
||||
const primaryDb = new Pool({
|
||||
host: config.database.host,
|
||||
port: Number(config.database.port),
|
||||
user: config.database.username,
|
||||
|
|
@ -13,11 +15,35 @@ export const client = new Client({
|
|||
database: config.database.database,
|
||||
});
|
||||
|
||||
const replicas =
|
||||
config.database.replicas?.map(
|
||||
(replica) =>
|
||||
new Pool({
|
||||
host: replica.host,
|
||||
port: Number(replica.port),
|
||||
user: replica.username,
|
||||
password: replica.password,
|
||||
database: replica.database,
|
||||
}),
|
||||
) ?? [];
|
||||
|
||||
export const db =
|
||||
(replicas.length ?? 0) > 0
|
||||
? withReplicas(
|
||||
drizzle(primaryDb, { schema }),
|
||||
replicas.map((r) => drizzle(r, { schema })) as [
|
||||
NodePgDatabase<typeof schema>,
|
||||
...NodePgDatabase<typeof schema>[],
|
||||
],
|
||||
)
|
||||
: drizzle(primaryDb, { schema });
|
||||
|
||||
export const setupDatabase = async (info = true) => {
|
||||
const logger = getLogger("database");
|
||||
|
||||
for (const dbPool of [primaryDb, ...replicas]) {
|
||||
try {
|
||||
await client.connect();
|
||||
await dbPool.connect();
|
||||
} catch (e) {
|
||||
if (
|
||||
(e as Error).message ===
|
||||
|
|
@ -27,11 +53,17 @@ export const setupDatabase = async (info = true) => {
|
|||
}
|
||||
|
||||
logger.fatal`${e}`;
|
||||
logger.fatal`Failed to connect to database. Please check your configuration.`;
|
||||
logger.fatal`Failed to connect to database ${chalk.bold(
|
||||
// Index of the database in the array
|
||||
replicas.indexOf(dbPool) === -1
|
||||
? "primary"
|
||||
: `replica-${replicas.indexOf(dbPool)}`,
|
||||
)}. Please check your configuration.`;
|
||||
|
||||
// Hang until Ctrl+C is pressed
|
||||
await Bun.sleep(Number.POSITIVE_INFINITY);
|
||||
}
|
||||
}
|
||||
|
||||
// Migrate the database
|
||||
info && logger.info`Migrating database...`;
|
||||
|
|
@ -50,5 +82,3 @@ export const setupDatabase = async (info = true) => {
|
|||
|
||||
info && logger.info`Database migrated`;
|
||||
};
|
||||
|
||||
export const db = drizzle(client, { schema });
|
||||
|
|
|
|||
|
|
@ -33,6 +33,22 @@ export const configValidator = z.object({
|
|||
username: z.string().min(1),
|
||||
password: z.string().default(""),
|
||||
database: z.string().min(1).default("versia"),
|
||||
replicas: z
|
||||
.array(
|
||||
z.object({
|
||||
host: z.string().min(1),
|
||||
port: z
|
||||
.number()
|
||||
.int()
|
||||
.min(1)
|
||||
.max(2 ** 16 - 1)
|
||||
.default(5432),
|
||||
username: z.string().min(1),
|
||||
password: z.string().default(""),
|
||||
database: z.string().min(1).default("versia"),
|
||||
}),
|
||||
)
|
||||
.optional(),
|
||||
}),
|
||||
redis: z.object({
|
||||
queue: z
|
||||
|
|
|
|||
Loading…
Reference in a new issue