Slide 1

Slide 1 text

deno-redis の紹介とJSR パッケージの運 用について (toranoana.deno #21)

Slide 2

Slide 2 text

今日話すこと deno-redis というJSR パッケージを題材に、JSR パッケージの開発や運 用に関する話をします。

Slide 3

Slide 3 text

自己紹介 @uki00a deno-weekly というWeb サ イトを運営しています RevComm Inc.

Slide 4

Slide 4 text

deno-redis ( jsr:@db/redis ) について Deno で実装されたRedis クライアント JSR ( jsr:@db/redis ) と deno.land/x (https://deno.land/x/redis) で 公開 作者はkeroxp さんです 2020 年ぐらいからdenodrivers というコミュニティ内で細々とメン テナンスを続けています

Slide 5

Slide 5 text

deno-redis ( jsr:@db/redis ) について import { connect } from "jsr:@db/redis"; const redis = await connect({ hostname: "127.0.0.1", port: 6379, }); const ok = await redis.set("key", "foo"); const result = await redis.get("key");

Slide 6

Slide 6 text

1. Deno v2 における破壊的変更への対応

Slide 7

Slide 7 text

Deno v1 におけるIO ( Deno.{Reader,Writer} ) // Deno.Reader const buf = new Uint8Array(128); doSomethingWithInput(await conn.read(buf)); // Deno.Writer const data = new TextEncoder().encode("foo"); await conn.write(data);

Slide 8

Slide 8 text

Deno v2 におけるIO (Stream API) // ReadableStream const reader = conn.readable.getReader(); doSomethingWithInput(await reader.read()); reader.releaseLock(); // WritableStream const writer = conn.writable.getWriter(); const data = new TextEncoder().encode("foo"); await writer.write(data); writer.releaseLock();

Slide 9

Slide 9 text

Deno.{Reader,Writer} からStream API への移行 deno-redis においても Deno.Reader 及び Deno.Writer からStream API への移行対応を実施することに しかし、移行にあたってビッグバンリリースはできれば避けたいで す...

Slide 10

Slide 10 text

Deno.{Reader,Writer} からStream API への移行 段階的に移行するために、 Protocol という抽象を用意することにしま した。 export interface Protocol { sendCommand( command: string, args: Array, returnsUint8Arrays?: boolean, ): Promise; readReply(returnsUint8Array?: boolean): Promise; writeCommand(command: Command): Promise; // ... }

Slide 11

Slide 11 text

Deno.{Reader,Writer} からStream API への移行 // `Deno.Reader` & `Deno.Writer` ベースの実装 (Deno v1 向け) export class DenoStreamBasedProtocol implements Protocol { #reader: BufReader; #writer: BufWriter; constructor(conn: Deno.Conn) { this.#reader = new BufReader(conn); this.#writer = new BufWriter(conn); } // 実装... } // Stream API ベースの実装 (Deno v2 向け) export class WebStreamBasedProtocol implements Protocol { #readable: BufferedReadableStream; #writable: WritableStream; constructor(conn: Deno.Conn) { this.#readable = new BufferedReadableStream(conn.readable); this.#writable = conn.writable; } // 実装... }

Slide 12

Slide 12 text

Deno.{Reader,Writer} からStream API への移行 ユーザーが任意でそれぞれの実装を切り替えられるようにします: // Deno v1 向け (Deno.Reader & Deno.Writer) import { connect as connectV1 } from "jsr:@db/redis"; // Deno v2 向け (Stream API) import { connect as connectV2 } from "jsr:@db/redis/experimental/web-streams-connection"

Slide 13

Slide 13 text

Deno.{Reader,Writer} からStream API への移行 互換性を担保するために、それぞれの実装に対して同一のテストを実 行します Deno.test("commands", async (t) => { for (const [type, connector] of [["v1", connectV1], ["v2", connectV2]]) { await t.step(type, async (t) => { await t.step("SET", async () => { using client = await connector(); assertEquals(await client.set("key", "foo"), "OK"); }); }); } });

Slide 14

Slide 14 text

2. パフォーマンスチューニング

Slide 15

Slide 15 text

Stream API への移行に伴うパフォーマンスの劣化につ いて なんとかStream API への対応については実施できました しかし、Stream API ベースの実装は Deno.Reader & Deno.Writer ベー スの実装と比較してパフォーマンスが悪いことが分かりました パフォーマンス改善による効果を計測できるよう、ベンチマークの仕 組みがあると便利です

Slide 16

Slide 16 text

ベンチマーク Deno には deno bench というベンチマークのための仕組みあります const value = "bar".repeat(10); Deno.bench("get & set", async (b) => { const client = await connect(options); b.start(); const key = "foo"; await client.set(key, value); await client.get(key); b.end(); });

Slide 17

Slide 17 text

ベンチマーク やりたいこと Deno ( deno-redis ) とNode.js ( ioredis ) で同じベンチマークコード を実行したい 課題 deno bench はNode.js では動作しません...

Slide 18

Slide 18 text

ベンチマーク benny はDeno とNode.js の両方で動きます (benchmark/benchmark.js) import { add, suite, /* ... */ } from "benny"; export const run = ({ driver, // "deno-redis" or "ioredis" client, // `Redis` object of `deno-redis` or `ioredis` }) => suite(driver, add("set & get", () => { return async () => { await client.set("foo", "bar"); await client.get("foo"); }; }), );

Slide 19

Slide 19 text

解決策 BYOB (ReadableStreamBYOBReader) を使用することでパフォーマ ンスをかなり改善できることが判明しました ( Deno.Reader & Deno.Writer と同等程度のパフォーマンスを発揮してくれる) // ReadableStreamBYOBReader を使いたい場合、`mode: "byob"` を指定する readableStream.getReader({ mode: "byob" }); バッファリングの仕組みが欲しかったので、自前で用意しています (BufferedReadableStream)

Slide 20

Slide 20 text

3. deno lint の活用

Slide 21

Slide 21 text

おすすめの設定 JSR パッケージを開発する際は no-console ルールを無効化しておく とおすすめです ( パッケージ内に意図せぬデバッグ用のログが残っ てしまうことを防止できます) { "lint": { "rules": { "include": ["no-console"] }, "plugins": ["jsr:@uki00a/[email protected]"] } } Deno v2.2 でプラグインシステムがサポートされました (deno-lint- plugin-extra-rules という自作プラグインを使っています)

Slide 22

Slide 22 text

4. CI

Slide 23

Slide 23 text

依存パッケージをキャッシュする denoland/[email protected] から依存パッケージのキャッシュがサポ ートされています: jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Set up Deno uses: denoland/setup-deno@e95548e56dfa95d4e1a28d6f422fafe75c4c26fb # v2.0.3 with: deno-version: 2.3.3 cache: true # キャッシュを有効化 cache-hash: ${{ hashFiles('.deno-version', 'deno.lock') }}

Slide 24

Slide 24 text

5. 今後について

Slide 25

Slide 25 text

Deno Deploy Early Access https://x.com/deno_land/status/1929971203512435189 より引用 Soon! (but early access is now open) Opt in via your dashboard http://dash.deno.com “ “

Slide 26

Slide 26 text

Deno Deploy Early Access とは? Databases: Coming soon Logs: Supported Tracing: Supported Metrics: Supported OpenTelemetry export: Work in progress 公式ドキュメントより引用

Slide 27

Slide 27 text

deno-redis の今後について OpenTelemetry のサポートを強化したい (Deno Deploy Early Access 向け) Cloudflare Workers のサポート RESP3 のサポートなど

Slide 28

Slide 28 text

まとめ. Deno ライブラリの開発で意識すると良 いこと 1. ベンチマークやLint 、テストなどのCI 周りを整備すると、メンテナ ンスが継続しやすくなると思います 2. 大掛かりな移行や改修をする場合、抽象の導入やベンチマークによ る計測などを活用して段階的に移行すると安全だと思います 3. Deno Deploy Early Access によってOpenTelemetry などの重要性が より増すかもしれないと思っています