Skip to content

NOTE

Bun の Redis クライアントは Redis サーバーバージョン 7.2 以降をサポートしています。

Bun は、モダンで Promise ベースの API を備えた Redis データベースを操作するためのネイティブバインディングを提供します。このインターフェースは、組み込みの接続管理、完全型付けされたレスポンス、TLS サポートを備え、シンプルでパフォーマンスに優れるように設計されています。

ts
import { redis } from "bun";

// キーを設定
await redis.set("greeting", "Hello from Bun!");

// キーを取得
const greeting = await redis.get("greeting");
console.log(greeting); // "Hello from Bun!"

// カウンターをインクリメント
await redis.set("counter", 0);
await redis.incr("counter");

// キーが存在するか確認
const exists = await redis.exists("greeting");

// キーを削除
await redis.del("greeting");

始め方

Redis クライアントを使用するには、まず接続を作成する必要があります:

ts
import { redis, RedisClient } from "bun";

// デフォルトクライアントを使用(環境から接続情報を読み取り)
// デフォルトでは process.env.REDIS_URL が使用されます
await redis.set("hello", "world");
const result = await redis.get("hello");

// カスタムクライアントを作成
const client = new RedisClient("redis://username:password@localhost:6379");
await client.set("counter", "0");
await client.incr("counter");

デフォルトでは、クライアントは以下の環境変数から接続情報を読み取ります(優先順):

  • REDIS_URL
  • VALKEY_URL
  • 設定されていない場合、デフォルトは "redis://localhost:6379"

接続のライフサイクル

Redis クライアントはバックグラウンドで接続を自動的に処理します:

ts
// コマンドが実行されるまで接続は行われません
const client = new RedisClient();

// 最初のコマンドで接続を開始
await client.set("key", "value");

// その後のコマンドのために接続は開いたままになります
await client.get("key");

// 完了したら接続を明示的に閉じます
client.close();

接続のライフサイクルを手動で制御することもできます:

ts
const client = new RedisClient();

// 明示的に接続
await client.connect();

// コマンドを実行
await client.set("key", "value");

// 完了時に切断
client.close();

基本操作

文字列操作

ts
// キーを設定
await redis.set("user:1:name", "Alice");

// キーを取得
const name = await redis.get("user:1:name");

// キーを Uint8Array として取得
const buffer = await redis.getBuffer("user:1:name");

// キーを削除
await redis.del("user:1:name");

// キーが存在するか確認
const exists = await redis.exists("user:1:name");

// 有効期限を設定(秒単位)
await redis.set("session:123", "active");
await redis.expire("session:123", 3600); // 1 時間で有効期限切れ

// 有効期限(秒単位)を取得
const ttl = await redis.ttl("session:123");

数値操作

ts
// 初期値を設定
await redis.set("counter", "0");

// 1 増やす
await redis.incr("counter");

// 1 減らす
await redis.decr("counter");

ハッシュ操作

ts
// ハッシュに複数のフィールドを設定
await redis.hmset("user:123", ["name", "Alice", "email", "alice@example.com", "active", "true"]);

// ハッシュから複数のフィールドを取得
const userFields = await redis.hmget("user:123", ["name", "email"]);
console.log(userFields); // ["Alice", "alice@example.com"]

// ハッシュから単一のフィールドを取得(値を直接返す、ない場合は null)
const userName = await redis.hget("user:123", "name");
console.log(userName); // "Alice"

// ハッシュの数値フィールドをインクリメント
await redis.hincrby("user:123", "visits", 1);

// ハッシュの浮動小数点フィールドをインクリメント
await redis.hincrbyfloat("user:123", "score", 1.5);

セット操作

ts
// セットにメンバーを追加
await redis.sadd("tags", "javascript");

// セットからメンバーを削除
await redis.srem("tags", "javascript");

// メンバーがセットに存在するか確認
const isMember = await redis.sismember("tags", "javascript");

// セットのすべてのメンバーを取得
const allTags = await redis.smembers("tags");

// ランダムなメンバーを取得
const randomTag = await redis.srandmember("tags");

// ランダムなメンバーをポップ(削除して返す)
const poppedTag = await redis.spop("tags");

Pub/Sub

Bun は Redis Pub/Sub プロトコルのネイティブバインディングを提供します。Bun 1.2.23 で新規追加

基本的な使い方

メッセージのパブリッシュを開始するには、publisher.ts でパブリッシャーを設定します:

typescript
import { RedisClient } from "bun";

const writer = new RedisClient("redis://localhost:6739");
await writer.connect();

writer.publish("general", "Hello everyone!");

writer.close();

別のファイルで、subscriber.ts でサブスクライバーを作成します:

typescript
import { RedisClient } from "bun";

const listener = new RedisClient("redis://localhost:6739");
await listener.connect();

await listener.subscribe("general", (message, channel) => {
  console.log(`受信:${message}`);
});

1 つのシェルで、サブスクライバーを実行します:

bash
bun run subscriber.ts

そしてもう 1 つのシェルで、パブリッシャーを実行します:

bash
bun run publisher.ts

NOTE

サブスクリプションモードは `RedisClient` 接続を占有します。サブスクリプションを持つクライアントは `RedisClient.prototype.subscribe()` のみ呼び出すことができます。 つまり、Redis にメッセージを送信する必要があるアプリケーションは、別の接続が必要で、`.duplicate()` を介して取得できます:
ts
import { RedisClient } from "bun";

const redis = new RedisClient("redis://localhost:6379");
await redis.connect();
const subscriber = await redis.duplicate(); 

await subscriber.subscribe("foo", () => {});
await redis.set("bar", "baz");

パブリッシュ

メッセージのパブリッシュは publish() メソッドを介して行われます:

typescript
await client.publish(channelName, message);

サブスクリプション

Bun RedisClient を使用すると、.subscribe() メソッドを介してチャンネルをサブスクライブできます:

typescript
await client.subscribe(channel, (message, channel) => {});

.unsubscribe() メソッドを介してサブスクライブを解除できます:

typescript
await client.unsubscribe(); // すべてのチャンネルからサブスクライブ解除
await client.unsubscribe(channel); // 特定のチャンネルからサブスクライブ解除
await client.unsubscribe(channel, listener); // 特定のリスナーからサブスクライブ解除

高度な使い方

コマンド実行とパイプライン

クライアントはコマンドを自動的にパイプライン化し、複数のコマンドをバッチで送信し、レスポンスが到着するたびに処理することでパフォーマンスを向上させます。

ts
// コマンドはデフォルトで自動的にパイプライン化されます
const [infoResult, listResult] = await Promise.all([redis.get("user:1:name"), redis.get("user:2:email")]);

自動パイプライン化を無効にするには、enableAutoPipelining オプションを false に設定できます:

ts
const client = new RedisClient("redis://localhost:6379", {
  enableAutoPipelining: false, 
});

生コマンド

簡易メソッドがないコマンドを使用する必要がある場合、send メソッドを使用できます:

ts
// 任意の Redis コマンドを実行
const info = await redis.send("INFO", []);

// リストに LPUSH
await redis.send("LPUSH", ["mylist", "value1", "value2"]);

// リストの範囲を取得
const list = await redis.send("LRANGE", ["mylist", "0", "-1"]);

send メソッドを使用すると、クライアントに専用メソッドがないものを含む任意の Redis コマンドを使用できます。最初の引数はコマンド名で、2 番目の引数は文字列引数の配列です。

接続イベント

接続イベントのハンドラーを登録できます:

ts
const client = new RedisClient();

// Redis サーバーに正常に接続されたときに呼び出されます
client.onconnect = () => {
  console.log("Redis サーバーに接続されました");
};

// Redis サーバーから切断されたときに呼び出されます
client.onclose = error => {
  console.error("Redis サーバーから切断されました:", error);
};

// 手動で接続/切断
await client.connect();
client.close();

接続ステータスとモニタリング

ts
// 接続されているか確認
console.log(client.connected); // 接続ステータスを示すブール値

// バッファリングされたデータ量(バイト単位)を確認
console.log(client.bufferedAmount);

型変換

Redis クライアントは Redis レスポンスの自動型変換を処理します:

  • 整数レスポンスは JavaScript の数値として返されます
  • バルク文字列は JavaScript の文字列として返されます
  • 簡易文字列は JavaScript の文字列として返されます
  • null バルク文字列は null として返されます
  • 配列レスポンスは JavaScript の配列として返されます
  • エラーレスポンスは適切なエラーコードで JavaScript エラーをスローします
  • ブール値レスポンス(RESP3)は JavaScript のブール値として返されます
  • マップレスポンス(RESP3)は JavaScript オブジェクトとして返されます
  • セットレスポンス(RESP3)は JavaScript の配列として返されます

特定のコマンドの特別な処理:

  • EXISTS は数値の代わりにブール値を返します(1 は true、0 は false になります)
  • SISMEMBER はブール値を返します(1 は true、0 は false になります)

以下のコマンドは自動パイプライン化を無効にします:

  • AUTH
  • INFO
  • QUIT
  • EXEC
  • MULTI
  • WATCH
  • SCRIPT
  • SELECT
  • CLUSTER
  • DISCARD
  • UNWATCH
  • PIPELINE
  • SUBSCRIBE
  • UNSUBSCRIBE
  • UNPSUBSCRIBE

接続オプション

クライアントを作成する際、接続を構成するためにさまざまなオプションを渡すことができます:

ts
const client = new RedisClient("redis://localhost:6379", {
  // ミリ秒単位の接続タイムアウト(デフォルト:10000)
  connectionTimeout: 5000,

  // ミリ秒単位のアイドルタイムアウト(デフォルト:0 = タイムアウトなし)
  idleTimeout: 30000,

  // 切断時に自動的に再接続するかどうか(デフォルト:true)
  autoReconnect: true,

  // 再接続試行の最大回数(デフォルト:10)
  maxRetries: 10,

  // 切断時にコマンドをキューに入れるかどうか(デフォルト:true)
  enableOfflineQueue: true,

  // コマンドを自動的にパイプライン化するかどうか(デフォルト:true)
  enableAutoPipelining: true,

  // TLS オプション(デフォルト:false)
  tls: true,
  // または、カスタム TLS 設定を提供:
  // tls: {
  //   rejectUnauthorized: true,
  //   ca: "path/to/ca.pem",
  //   cert: "path/to/cert.pem",
  //   key: "path/to/key.pem",
  // }
});

再接続の動作

接続が失われると、クライアントは指数バックオフで自動的に再接続を試みます:

  1. クライアントは小さな遅延(50ms)で開始し、各試行で 2 倍にします
  2. 再接続の遅延は 2000ms(2 秒)に制限されています
  3. クライアントは maxRetries 回(デフォルト:10)まで再接続を試みます
  4. 切断中に実行されたコマンドは:
    • enableOfflineQueue が true の場合キューに入れられます(デフォルト)
    • enableOfflineQueue が false の場合即座に拒否されます

サポートされている URL 形式

Redis クライアントはさまざまな URL 形式をサポートしています:

ts
// 標準の Redis URL
new RedisClient("redis://localhost:6379");
new RedisClient("redis://localhost:6379");

// 認証付き
new RedisClient("redis://username:password@localhost:6379");

// データベース番号付き
new RedisClient("redis://localhost:6379/0");

// TLS 接続
new RedisClient("rediss://localhost:6379");
new RedisClient("rediss://localhost:6379");
new RedisClient("redis+tls://localhost:6379");
new RedisClient("redis+tls://localhost:6379");

// Unix ソケット接続
new RedisClient("redis+unix:///path/to/socket");
new RedisClient("redis+unix:///path/to/socket");

// Unix ソケット経由の TLS
new RedisClient("redis+tls+unix:///path/to/socket");
new RedisClient("redis+tls+unix:///path/to/socket");

エラーハンドリング

Redis クライアントはさまざまなシナリオに対して型付けされたエラーをスローします:

ts
try {
  await redis.get("non-existent-key");
} catch (error) {
  if (error.code === "ERR_REDIS_CONNECTION_CLOSED") {
    console.error("Redis サーバーへの接続が閉じられました");
  } else if (error.code === "ERR_REDIS_AUTHENTICATION_FAILED") {
    console.error("認証に失敗しました");
  } else {
    console.error("予期しないエラー:", error);
  }
}

一般的なエラーコード:

  • ERR_REDIS_CONNECTION_CLOSED - サーバーへの接続が閉じられました
  • ERR_REDIS_AUTHENTICATION_FAILED - サーバーでの認証に失敗しました
  • ERR_REDIS_INVALID_RESPONSE - サーバーから無効なレスポンスを受信しました

使用例

キャッシング

ts
async function getUserWithCache(userId) {
  const cacheKey = `user:${userId}`;

  // まずキャッシュから取得
  const cachedUser = await redis.get(cacheKey);
  if (cachedUser) {
    return JSON.parse(cachedUser);
  }

  // キャッシュにない場合、データベースから取得
  const user = await database.getUser(userId);

  // 1 時間キャッシュに保存
  await redis.set(cacheKey, JSON.stringify(user));
  await redis.expire(cacheKey, 3600);

  return user;
}

レート制限

ts
async function rateLimit(ip, limit = 100, windowSecs = 3600) {
  const key = `ratelimit:${ip}`;

  // カウンターをインクリメント
  const count = await redis.incr(key);

  // ウィンドウ内の最初のリクエストの場合、有効期限を設定
  if (count === 1) {
    await redis.expire(key, windowSecs);
  }

  // 制限を超えたか確認
  return {
    limited: count > limit,
    remaining: Math.max(0, limit - count),
  };
}

セッションストレージ

ts
async function createSession(userId, data) {
  const sessionId = crypto.randomUUID();
  const key = `session:${sessionId}`;

  // 有効期限付きでセッションを保存
  await redis.hmset(key, ["userId", userId.toString(), "created", Date.now().toString(), "data", JSON.stringify(data)]);
  await redis.expire(key, 86400); // 24 時間

  return sessionId;
}

async function getSession(sessionId) {
  const key = `session:${sessionId}`;

  // セッションデータを取得
  const exists = await redis.exists(key);
  if (!exists) return null;

  const [userId, created, data] = await redis.hmget(key, ["userId", "created", "data"]);

  return {
    userId: Number(userId),
    created: Number(created),
    data: JSON.parse(data),
  };
}

実装ノート

Bun の Redis クライアントは Zig で実装され、Redis シリアライゼーションプロトコル(RESP3)を使用しています。接続を効率的に管理し、指数バックオフによる自動再接続を提供します。

クライアントはコマンドのパイプライン化をサポートしており、複数のコマンドを前のコマンドのレスポンスを待たずに送信できます。これにより、複数のコマンドを連続して送信する際のパフォーマンスが大幅に向上します。

制限と将来の計画

将来のバージョンで対応を予定している Redis クライアントの現在の制限:

  • トランザクション(MULTI/EXEC)は現時点では生コマンドを介して行う必要があります

サポートされていない機能:

  • Redis Sentinel
  • Redis Cluster

Bun by www.bunjs.com.cn 編集