๋ฉ”์ธ ์ฝ˜ํ…์ธ ๋กœ ๊ฑด๋„ˆ๋›ฐ๊ธฐ
Sonamu๋Š” BentoCache๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ํ•˜๋Š” ๊ฐ•๋ ฅํ•œ ์บ์‹ฑ ์‹œ์Šคํ…œ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ๋ฉ”๋ชจ๋ฆฌ, Redis ๋“ฑ ๋‹ค์–‘ํ•œ ๋“œ๋ผ์ด๋ฒ„๋ฅผ ์ง€์›ํ•˜๋ฉฐ, ๋‹ค์ธต ์บ์‹œ(L1/L2)์™€ ๋ถ„์‚ฐ ๋ฌดํšจํ™”๋ฅผ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค.

๊ธฐ๋ณธ ๊ตฌ์กฐ

import { defineConfig } from "sonamu";
import { drivers, store } from "sonamu/cache";

export default defineConfig({
  server: {
    cache: {
      default: "main",
      stores: {
        main: store()
          .useL1Layer(drivers.memory({ maxSize: "100mb" }))
          .useL2Layer(drivers.redis({ connection: redisConnection })),
      },
      ttl: "5m",
      prefix: "myapp:",
    },
  },
  // ...
});

default

๊ธฐ๋ณธ์œผ๋กœ ์‚ฌ์šฉํ•  ์บ์‹œ ์Šคํ† ์–ด๋ฅผ ์ง€์ •ํ•ฉ๋‹ˆ๋‹ค. ํƒ€์ž…: string
export default defineConfig({
  server: {
    cache: {
      default: "main",  // stores.main ์‚ฌ์šฉ
      stores: {
        main: store().useL1Layer(drivers.memory({ maxSize: "100mb" })),
      },
    },
  },
});

stores

์‚ฌ์šฉํ•  ์บ์‹œ ์Šคํ† ์–ด๋“ค์„ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค. ํƒ€์ž…: Record<string, BentoCacheStore>
import { drivers, store } from "sonamu/cache";

export default defineConfig({
  server: {
    cache: {
      default: "main",
      stores: {
        main: store()
          .useL1Layer(drivers.memory({ maxSize: "100mb" })),
        
        distributed: store()
          .useL1Layer(drivers.memory({ maxSize: "50mb" }))
          .useL2Layer(drivers.redis({ connection: redisConnection }))
          .useBus(drivers.redisBus({ connection: redisConnection })),
      },
    },
  },
});

๋‹จ์ผ ๋ ˆ์ด์–ด: ๋ฉ”๋ชจ๋ฆฌ

๊ฐ€์žฅ ๊ฐ„๋‹จํ•œ ์„ค์ •์œผ๋กœ, ๋ฉ”๋ชจ๋ฆฌ์—๋งŒ ์บ์‹œ๋ฅผ ์ €์žฅํ•ฉ๋‹ˆ๋‹ค.
import { drivers, store } from "sonamu/cache";

export default defineConfig({
  server: {
    cache: {
      default: "main",
      stores: {
        main: store().useL1Layer(drivers.memory({ maxSize: "100mb" })),
      },
      ttl: "5m",
    },
  },
});
์žฅ์ : ๋น ๋ฅธ ์†๋„, ์„ค์ • ๊ฐ„๋‹จ
๋‹จ์ : ์„œ๋ฒ„ ์žฌ์‹œ์ž‘ ์‹œ ์บ์‹œ ์†์‹ค, ๋‹ค์ค‘ ์„œ๋ฒ„ ๊ฐ„ ๊ณต์œ  ๋ถˆ๊ฐ€

๋‹ค์ธต ์บ์‹œ: L1 (๋ฉ”๋ชจ๋ฆฌ) + L2 (Redis)

L1์€ ๋ฉ”๋ชจ๋ฆฌ๋กœ ๋น ๋ฅด๊ฒŒ, L2๋Š” Redis๋กœ ์˜๊ตฌ์ ์œผ๋กœ ์บ์‹ฑํ•ฉ๋‹ˆ๋‹ค.
import { drivers, store } from "sonamu/cache";
import { createClient } from "redis";

const redisConnection = createClient({
  url: process.env.REDIS_URL ?? "redis://localhost:6379",
});

export default defineConfig({
  server: {
    cache: {
      default: "main",
      stores: {
        main: store()
          .useL1Layer(drivers.memory({ maxSize: "50mb" }))
          .useL2Layer(drivers.redis({ connection: redisConnection })),
      },
      ttl: "1h",
    },
  },
});
๋™์ž‘ ๋ฐฉ์‹:
  1. ์บ์‹œ ์กฐํšŒ ์‹œ L1(๋ฉ”๋ชจ๋ฆฌ) ๋จผ์ € ํ™•์ธ
  2. L1์— ์—†์œผ๋ฉด L2(Redis) ํ™•์ธ
  3. L2์—์„œ ์ฐพ์œผ๋ฉด L1์—๋„ ์ €์žฅ
  4. ๋‘˜ ๋‹ค ์—†์œผ๋ฉด ์›๋ณธ ๋ฐ์ดํ„ฐ ์กฐํšŒ ํ›„ L1, L2์— ์ €์žฅ

๋ถ„์‚ฐ ์บ์‹œ: L1 + L2 + Bus

์—ฌ๋Ÿฌ ์„œ๋ฒ„ ๊ฐ„ ์บ์‹œ ๋ฌดํšจํ™”๋ฅผ ๋™๊ธฐํ™”ํ•ฉ๋‹ˆ๋‹ค.
import { drivers, store } from "sonamu/cache";
import { createClient } from "redis";

const redisConnection = createClient({
  url: process.env.REDIS_URL ?? "redis://localhost:6379",
});

export default defineConfig({
  server: {
    cache: {
      default: "main",
      stores: {
        main: store()
          .useL1Layer(drivers.memory({ maxSize: "50mb" }))
          .useL2Layer(drivers.redis({ connection: redisConnection }))
          .useBus(drivers.redisBus({ connection: redisConnection })),
      },
      ttl: "1h",
    },
  },
});
๋™์ž‘ ๋ฐฉ์‹:
  • ํ•œ ์„œ๋ฒ„์—์„œ ์บ์‹œ๋ฅผ ๋ฌดํšจํ™”ํ•˜๋ฉด
  • Bus๋ฅผ ํ†ตํ•ด ๋‹ค๋ฅธ ๋ชจ๋“  ์„œ๋ฒ„์— ์ „ํŒŒ
  • ๋ชจ๋“  ์„œ๋ฒ„์˜ L1 ์บ์‹œ๋„ ์ž๋™ ๋ฌดํšจํ™”
๋‹ค์ค‘ ์„œ๋ฒ„(๋กœ๋“œ ๋ฐธ๋Ÿฐ์„œ ๋’ค)๋ฅผ ์šด์˜ํ•œ๋‹ค๋ฉด Bus๋ฅผ ํ™œ์„ฑํ™”ํ•˜์„ธ์š”. ์บ์‹œ ์ผ๊ด€์„ฑ์ด ๋ณด์žฅ๋ฉ๋‹ˆ๋‹ค.

ttl

์บ์‹œ์˜ ๊ธฐ๋ณธ ์œ ํšจ ์‹œ๊ฐ„(Time To Live)์„ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค. ํƒ€์ž…: string (์„ ํƒ์ ) ๊ธฐ๋ณธ๊ฐ’: "5m" (5๋ถ„)
export default defineConfig({
  server: {
    cache: {
      default: "main",
      stores: { /* ... */ },
      ttl: "1h",  // 1์‹œ๊ฐ„
    },
  },
});
์‹œ๊ฐ„ ํ˜•์‹:
  • "5s" - 5์ดˆ
  • "1m" - 1๋ถ„
  • "1h" - 1์‹œ๊ฐ„
  • "1d" - 1์ผ
API๋ณ„๋กœ ๋‹ค๋ฅธ TTL ์‚ฌ์šฉ:
import { cache } from "sonamu";

export class ProductModel {
  @cache({ ttl: "10m" })  // 10๋ถ„ ์บ์‹œ
  @api()
  static async list() {
    // ...
  }
  
  @cache({ ttl: "1h" })  // 1์‹œ๊ฐ„ ์บ์‹œ
  @api()
  static async detail(id: number) {
    // ...
  }
}

prefix

๋ชจ๋“  ์บ์‹œ ํ‚ค์— ์ถ”๊ฐ€๋  ์ ‘๋‘์‚ฌ๋ฅผ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค. ํƒ€์ž…: string (์„ ํƒ์ ) ๊ธฐ๋ณธ๊ฐ’: ""
export default defineConfig({
  server: {
    cache: {
      default: "main",
      stores: { /* ... */ },
      prefix: "myapp:",  // ๋ชจ๋“  ํ‚ค์— "myapp:" ์ถ”๊ฐ€
    },
  },
});
์˜ˆ์‹œ:
// prefix: "myapp:"๋กœ ์„ค์ • ์‹œ
await cache.set("user:123", data);
// ์‹ค์ œ Redis ํ‚ค: "myapp:user:123"
๊ฐ™์€ Redis ์ธ์Šคํ„ด์Šค๋ฅผ ์—ฌ๋Ÿฌ ์•ฑ์ด ๊ณต์œ ํ•œ๋‹ค๋ฉด prefix๋ฅผ ์„ค์ •ํ•˜์—ฌ ํ‚ค ์ถฉ๋Œ์„ ๋ฐฉ์ง€ํ•˜์„ธ์š”.

๋“œ๋ผ์ด๋ฒ„ ์˜ต์…˜

memory ๋“œ๋ผ์ด๋ฒ„

๋ฉ”๋ชจ๋ฆฌ์— ์บ์‹œ๋ฅผ ์ €์žฅํ•ฉ๋‹ˆ๋‹ค.
drivers.memory({
  maxSize: "100mb",  // ์ตœ๋Œ€ ๋ฉ”๋ชจ๋ฆฌ ํฌ๊ธฐ
})
์˜ต์…˜:
  • maxSize: ์ตœ๋Œ€ ๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ๋Ÿ‰ ("50mb", "100mb", "1gb" ๋“ฑ)

redis ๋“œ๋ผ์ด๋ฒ„

Redis์— ์บ์‹œ๋ฅผ ์ €์žฅํ•ฉ๋‹ˆ๋‹ค.
import { createClient } from "redis";

const redisConnection = createClient({
  url: process.env.REDIS_URL ?? "redis://localhost:6379",
  password: process.env.REDIS_PASSWORD,
});

drivers.redis({
  connection: redisConnection,
})
connection ์„ค์ •:
const redisConnection = createClient({
  url: "redis://localhost:6379",
  password: "your-password",
  socket: {
    connectTimeout: 10000,
  },
});

await redisConnection.connect();

redisBus ๋“œ๋ผ์ด๋ฒ„

Redis Pub/Sub์„ ์‚ฌ์šฉํ•˜์—ฌ ์บ์‹œ ๋ฌดํšจํ™”๋ฅผ ์ „ํŒŒํ•ฉ๋‹ˆ๋‹ค.
drivers.redisBus({
  connection: redisConnection,
})
redisBus๋Š” redis ๋“œ๋ผ์ด๋ฒ„์™€ ๊ฐ™์€ ์—ฐ๊ฒฐ์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์‹ค์ „ ์˜ˆ์‹œ

๋‹จ์ผ ์„œ๋ฒ„: ๋ฉ”๋ชจ๋ฆฌ๋งŒ ์‚ฌ์šฉ

import { defineConfig } from "sonamu";
import { drivers, store } from "sonamu/cache";

export default defineConfig({
  server: {
    cache: {
      default: "main",
      stores: {
        main: store().useL1Layer(drivers.memory({ maxSize: "100mb" })),
      },
      ttl: "5m",
    },
  },
  // ...
});
์šฉ๋„: ์†Œ๊ทœ๋ชจ ์•ฑ, ๋‹จ์ผ ์„œ๋ฒ„

๋‹ค์ธต ์บ์‹œ: ๋ฉ”๋ชจ๋ฆฌ + Redis

import { defineConfig } from "sonamu";
import { drivers, store } from "sonamu/cache";
import { createClient } from "redis";

const redisConnection = createClient({
  url: process.env.REDIS_URL ?? "redis://localhost:6379",
});

await redisConnection.connect();

export default defineConfig({
  server: {
    cache: {
      default: "main",
      stores: {
        main: store()
          .useL1Layer(drivers.memory({ maxSize: "50mb" }))
          .useL2Layer(drivers.redis({ connection: redisConnection })),
      },
      ttl: "1h",
      prefix: "myapp:",
    },
  },
  // ...
});
์šฉ๋„: ์ค‘๊ทœ๋ชจ ์•ฑ, ๋‹จ์ผ ์„œ๋ฒ„, ์˜๊ตฌ ์บ์‹œ ํ•„์š”

๋ถ„์‚ฐ ํ™˜๊ฒฝ: ๋ฉ”๋ชจ๋ฆฌ + Redis + Bus

import { defineConfig } from "sonamu";
import { drivers, store } from "sonamu/cache";
import { createClient } from "redis";

const redisConnection = createClient({
  url: process.env.REDIS_URL ?? "redis://localhost:6379",
  password: process.env.REDIS_PASSWORD,
});

await redisConnection.connect();

export default defineConfig({
  server: {
    cache: {
      default: "main",
      stores: {
        main: store()
          .useL1Layer(drivers.memory({ maxSize: "50mb" }))
          .useL2Layer(drivers.redis({ connection: redisConnection }))
          .useBus(drivers.redisBus({ connection: redisConnection })),
      },
      ttl: "1h",
      prefix: "prod:",
    },
  },
  // ...
});
์šฉ๋„: ๋Œ€๊ทœ๋ชจ ์•ฑ, ๋‹ค์ค‘ ์„œ๋ฒ„(๋กœ๋“œ ๋ฐธ๋Ÿฐ์„œ), ์บ์‹œ ์ผ๊ด€์„ฑ ํ•„์š”

๋‹ค์ค‘ ์Šคํ† ์–ด: ์šฉ๋„๋ณ„ ๋ถ„๋ฆฌ

import { defineConfig } from "sonamu";
import { drivers, store } from "sonamu/cache";

export default defineConfig({
  server: {
    cache: {
      default: "short",
      stores: {
        // ์งง์€ ์บ์‹œ (API ์‘๋‹ต)
        short: store()
          .useL1Layer(drivers.memory({ maxSize: "50mb" })),
        
        // ๊ธด ์บ์‹œ (์ •์  ๋ฐ์ดํ„ฐ)
        long: store()
          .useL1Layer(drivers.memory({ maxSize: "30mb" }))
          .useL2Layer(drivers.redis({ connection: redisConnection })),
      },
      ttl: "5m",
    },
  },
  // ...
});
์‚ฌ์šฉ ์˜ˆ์‹œ:
import { cache } from "sonamu";

export class DataModel {
  // ์งง์€ ์บ์‹œ (๊ธฐ๋ณธ ์Šคํ† ์–ด)
  @cache({ ttl: "1m" })
  @api()
  static async list() { /* ... */ }
  
  // ๊ธด ์บ์‹œ (long ์Šคํ† ์–ด)
  @cache({ store: "long", ttl: "1d" })
  @api()
  static async staticData() { /* ... */ }
}

์บ์‹œ ์‚ฌ์šฉ

์„ค์ • ํ›„ API์—์„œ @cache ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.
import { api, cache } from "sonamu";

export class ProductModel {
  @cache({ ttl: "10m" })
  @api()
  static async list() {
    // ์ฒซ ํ˜ธ์ถœ: DB ์กฐํšŒ ํ›„ ์บ์‹œ ์ €์žฅ
    // ์ดํ›„ 10๋ถ„๊ฐ„: ์บ์‹œ์—์„œ ๋ฐ˜ํ™˜
    return this.getPuri().select("*");
  }
  
  @cache({ key: (id) => `product:${id}`, ttl: "1h" })
  @api()
  static async detail(id: number) {
    // ์ƒํ’ˆ๋ณ„๋กœ 1์‹œ๊ฐ„ ์บ์‹œ
    return this.findById(id);
  }
}
โ†’ ์บ์‹œ ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ ์‚ฌ์šฉ๋ฒ•

Redis ์„ค์น˜

Redis๋ฅผ ์‚ฌ์šฉํ•˜๋ ค๋ฉด ๋จผ์ € ์„ค์น˜๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.

Docker

docker run -d \
  --name redis \
  -p 6379:6379 \
  redis:7

macOS

brew install redis
brew services start redis

Linux

sudo apt-get install redis-server
sudo systemctl start redis

์—ฐ๊ฒฐ ํ™•์ธ

redis-cli ping
# PONG

์ฃผ์˜์‚ฌํ•ญ

1. Redis ์—ฐ๊ฒฐ ๊ด€๋ฆฌ

// โœ… ์ข‹์€ ์˜ˆ: ์—ฐ๊ฒฐ ์žฌ์‚ฌ์šฉ
const redisConnection = createClient({ /* ... */ });
await redisConnection.connect();

export default defineConfig({
  server: {
    cache: {
      stores: {
        main: store()
          .useL2Layer(drivers.redis({ connection: redisConnection }))
          .useBus(drivers.redisBus({ connection: redisConnection })),
      },
    },
  },
});

2. ๋ฉ”๋ชจ๋ฆฌ ํฌ๊ธฐ ์„ค์ •

// ์„œ๋ฒ„ ๋ฉ”๋ชจ๋ฆฌ๋ฅผ ๊ณ ๋ คํ•˜์—ฌ ์„ค์ •
drivers.memory({
  maxSize: "100mb",  // ์„œ๋ฒ„ ๋ฉ”๋ชจ๋ฆฌ์˜ 10-20% ์ •๋„
})

3. TTL ์„ ํƒ

// ๋ฐ์ดํ„ฐ ๋ณ€๊ฒฝ ๋นˆ๋„์— ๋”ฐ๋ผ ์กฐ์ •
ttl: "1m"   // ์ž์ฃผ ๋ณ€๊ฒฝ๋˜๋Š” ๋ฐ์ดํ„ฐ
ttl: "10m"  // ์ผ๋ฐ˜์ ์ธ API ์‘๋‹ต
ttl: "1h"   // ๊ฑฐ์˜ ๋ณ€๊ฒฝ๋˜์ง€ ์•Š๋Š” ๋ฐ์ดํ„ฐ
ttl: "1d"   // ์ •์  ๋ฐ์ดํ„ฐ

๋‹ค์Œ ๋‹จ๊ณ„

์บ์‹œ ์„ค์ •์„ ์™„๋ฃŒํ–ˆ๋‹ค๋ฉด: