์ฝ๊ธฐ ๋ณต์ œ๋ณธ

ํ”„๋กœ์ ํŠธ์— ์ฝ๊ธฐ ๋ณต์ œ๋ณธ ์ธ์Šคํ„ด์Šค ์„ธํŠธ๊ฐ€ ํฌํ•จ๋˜์–ด ์žˆ๊ณ , ์ฝ๊ธฐ ๋ณต์ œ๋ณธ์—์„œ SELECT ์ฟผ๋ฆฌ๋ฅผ ๊ด€๋ฆฌํ•˜๊ณ  ๊ธฐ๋ณธ ์ธ์Šคํ„ด์Šค์—์„œ ์ƒ์„ฑ, ์‚ญ์ œ ๋ฐ ์—…๋ฐ์ดํŠธ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•˜๋Š” ํŽธ๋ฆฌํ•œ ๋ฐฉ๋ฒ•์ด ํ•„์š”ํ•œ ๊ฒฝ์šฐ, Drizzle ๋‚ด์—์„œ withReplicas() ํ•จ์ˆ˜๋ฅผ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

PostgreSQL
MySQL
SQLite
SingleStore
import { sql } from 'drizzle-orm';
import { drizzle } from 'drizzle-orm/node-postgres';
import { boolean, jsonb, pgTable, serial, text, timestamp, withReplicas } from 'drizzle-orm/pg-core';

const usersTable = pgTable('users', {
	id: serial('id' as string).primaryKey(),
	name: text('name').notNull(),
	verified: boolean('verified').notNull().default(false),
	jsonb: jsonb('jsonb').$type<string[]>(),
	createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
});

const primaryDb = drizzle("postgres://user:password@host:port/primary_db");
const read1 = drizzle("postgres://user:password@host:port/read_replica_1");
const read2 = drizzle("postgres://user:password@host:port/read_replica_2");

const db = withReplicas(primaryDb, [read1, read2]);

์ด์ œ ์ด์ „๊ณผ ๋™์ผํ•œ ๋ฐฉ์‹์œผ๋กœ db ์ธ์Šคํ„ด์Šค๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. Drizzle์€ ์ฝ๊ธฐ ๋ณต์ œ๋ณธ๊ณผ ๊ธฐ๋ณธ ์ธ์Šคํ„ด์Šค ๊ฐ„์˜ ์„ ํƒ์„ ์ž๋™์œผ๋กœ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค.

// read1 ์—ฐ๊ฒฐ ๋˜๋Š” read2 ์—ฐ๊ฒฐ์—์„œ ์ฝ๊ธฐ
await db.select().from(usersTable)

// ์‚ญ์ œ ์ž‘์—…์— ๊ธฐ๋ณธ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์‚ฌ์šฉ
await db.delete(usersTable).where(eq(usersTable.id, 1))

์ฝ๊ธฐ ์ž‘์—…์—์„œ๋„ ๊ธฐ๋ณธ ์ธ์Šคํ„ด์Šค๋ฅผ ๊ฐ•์ œ๋กœ ์‚ฌ์šฉํ•˜๋ ค๋ฉด $primary ํ‚ค๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

// ๊ธฐ๋ณธ์—์„œ ์ฝ๊ธฐ
await db.$primary.select().from(usersTable);

Drizzle์„ ์‚ฌ์šฉํ•˜๋ฉด ์ฝ๊ธฐ ๋ณต์ œ๋ณธ์„ ์„ ํƒํ•˜๊ธฐ ์œ„ํ•œ ์‚ฌ์šฉ์ž ์ •์˜ ๋กœ์ง์„ ์ง€์ •ํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ฐ€์ค‘์น˜ ๊ฒฐ์ • ๋˜๋Š” ๋ฌด์ž‘์œ„ ์ฝ๊ธฐ ๋ณต์ œ๋ณธ ์„ ํƒ์„ ์œ„ํ•œ ๊ธฐํƒ€ ์‚ฌ์šฉ์ž ์ •์˜ ์„ ํƒ ๋ฐฉ๋ฒ•์„ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋‹ค์Œ์€ ์ฒซ ๋ฒˆ์งธ ๋ณต์ œ๋ณธ์ด 70%์˜ ํ™•๋ฅ ๋กœ ์„ ํƒ๋˜๊ณ , ๋‘ ๋ฒˆ์งธ ๋ณต์ œ๋ณธ์ด 30%์˜ ํ™•๋ฅ ๋กœ ์„ ํƒ๋˜๋Š” ์ฝ๊ธฐ ๋ณต์ œ๋ณธ ์„ ํƒ์„ ์œ„ํ•œ ์‚ฌ์šฉ์ž ์ •์˜ ๋กœ์ง์˜ ๊ตฌํ˜„ ์˜ˆ์ž…๋‹ˆ๋‹ค.

์ฝ๊ธฐ ๋ณต์ œ๋ณธ์— ๋Œ€ํ•œ ๋ชจ๋“  ์œ ํ˜•์˜ ๋ฌด์ž‘์œ„ ์„ ํƒ ๋ฐฉ๋ฒ•์„ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ์ ์„ ์œ ๋…ํ•˜์„ธ์š”.

const db = withReplicas(primaryDb, [read1, read2], (replicas) => {
    const weight = [0.7, 0.3];
    let cumulativeProbability = 0;
    const rand = Math.random();

    for (const [i, replica] of replicas.entries()) {
      cumulativeProbability += weight[i]!;
      if (rand < cumulativeProbability) return replica;
    }
    return replicas[0]!
});

await db.select().from(usersTable)