Valkey(Redis)と etcd を試す

Valkey(Redis)と etcd を試す

どちらも今後使うかもしれないため、初めて触ってみました。

Valkey と etcd について #

どちらも著名な KVS(Key-Value Store)ですが、その用途は全く異なります。

Valkey #

Valkey はインメモリデータベースです。ディスクにデータを保存する通常のデータベースと異なり、メモリ上にデータを保持します。そのため極めて高速にアクセスできます。インメモリデータベースはキャッシュやセッション管理などに使われます。

Valkey は Redis 7.2.4 からフォークされて開発されました。もともと Redis は個人が開発した OSS でしたが後に企業に権利が譲渡されライセンスが変更されました。この変更によりクラウドプロバイダー(例:AWS)が Redis をマネージドサービスとして提供する際に制限がかかるようになりました。

Valkey は The Linux Foundation の支援のもと、OSS ライセンスを維持するために開発されています。今後は Redis ではなく Valkey が主流になっていくことが予想されます。

etcd #

etcd は分散型のデータベースです。Kubernetes のデータストアとして採用されているのが有名です。

分散型データベースとは、複数のサーバにデータを分散して保存することができるデータベースです。利用者には1つのデータベースのように見せつつ、内部では複数台のサーバーが通信しながらデータの複製・整合性の確認を行います。

etcd は当初、CoreOS という OS が利用するデータベースとして開発されました。その後 Kubernetes チームが etcd を Kubernetes のデータストアとして採用したことで広く知られるようになりました。

etcd は Cloud Native Computing Foundation(CNCF)プロジェクトとして開発が進められています。ちなみに CNCF は The Linux Foundation の傘下にある組織です。

コラム:「単一ノード型データベース」と「分散型データベース」 #

例えば PostgreSQL は、基本的には単一ノードを中心に設計されたデータベースですが、拡張機能やミドルウェアを使うことで、レプリケーション(データを複数ノードに複製保存する仕組み)やシャーディング(データを複数ノードに分割保存する仕組み)が可能です。

これらの機能を使えば PostgreSQL でもシステムとしては分散データベースを構成できます。このように「単一ノード型データベース」でもミドルウェアなどを用いることで分散データベースは実現可能です。

その意味では「単一ノード型データベース」と「分散型データベース」の境界は明確ではありませんが、一般に「分散型データベース」と呼ばれる製品は、分散型データベースとして満たすべき機能要件(データ配置、レプリケーション、クエリルーティング、障害時の切り替え、分散トランザクションなど)を製品自身の機能としてあらかじめ備えているものを指していると理解するのがよいでしょう。

Valkey を触ってみる #

valkey-cli で操作してみる #

Valkey コンテナの準備 #

インストール手順はこちらに記載されています:https://valkey.io/topics/installation/

ここでは Docker Hub で提供されている Valkey 公式イメージを使って進めます:https://hub.docker.com/r/valkey/valkey/

Docker イメージを取得、起動します。Valkey は 6379 ポート(Redis と同じ)で起動するため、ホストの 6379 ポートにバインドしましょう。

docker run --name my-valkey -d -p 127.0.0.1:6379:6379 valkey/valkey

Valkey コンテナにログインし、valkey-cli(コンテナに同梱)を起動しましょう。

docker exec -it my-valkey valkey-cli

ping と打ってみましょう。PONG と返ってくれば Valkey が正常に動いています。

127.0.0.1:6379> ping

PONG

データの登録 #

データを設定してみましょう。以下のコマンドでは、キー user:1000 に値 "Alice" という String 型のデータを登録しています。OK と返ってくれば登録成功です。

127.0.0.1:6379> SET user:1000 "Alice"

OK

続いてハッシュ型のデータを登録してみましょう。以下のコマンドでは、キー user:2000 にハッシュ型のデータを登録しています。(integer) 3 と返ってくれば登録成功です。

HSET user:2000 name "Bob" email "[email protected]" age "30"

(integer) 3

データの取得 #

以下のコマンドで登録されているキーの一覧を取得できます。

KEYS *

1) "user:1000"
2) "user:2000"

user:1000 に登録した String 型のデータを取得してみましょう。

127.0.0.1:6379> GET user:1000

"Alice"

ハッシュ型のデータは次のように取得できます。

127.0.0.1:6379> HGETALL user:2000

1) "name"
2) "Bob"
3) "email"
4) "[email protected]"
5) "age"
6) "30"

コンテナの停止と削除 #

次のステップに進む前に一度コンテナを片付け、イメージを削除しておきます。

docker stop my-valkey
docker rm my-valkey
docker rmi valkey/valkey

Node.js から操作してみる #

さきほどは CLI で操作しましたが、実際にはアプリケーションから Valkey を操作することがほとんどでしょう。ここでは Node.js から Valkey を操作してみます。

Node.js から Valkey を操作するには Valkey が提供する公式クライアントライブラリ GLIDE を使います:https://glide.valkey.io/

コンテナの準備 #

以下のファイルを用意します。

my-valkey-app/
├── compose.yaml
├── Dockerfile
├── package.json
└── index.js

compose.yaml

services:
  valkey:
    image: valkey/valkey:latest
    ports:
      - "127.0.0.1:6379:6379"
    healthcheck:
      test: ["CMD", "valkey-cli", "ping"]
      interval: 3s
      timeout: 1s
      retries: 10

  nodejs:
    build:
      context: .
    depends_on:
      valkey:
        condition: service_healthy

Dockerfile

FROM node:24-bookworm-slim

WORKDIR /app

COPY package.json ./
RUN npm install --omit=dev

COPY index.js ./

CMD ["sleep", "infinity"]

package.json

{
  "name": "my-valkey-app",
  "version": "1.0.0",
  "private": true,
  "type": "module",
  "dependencies": {
    "@valkey/valkey-glide": "2.5.0"
  }
}

index.js

import { GlideClient } from "@valkey/valkey-glide";

await main().catch(console.error);

async function main() {
  const host = "valkey";
  const port = 6379;

  let client;

  try {
    client = await GlideClient.createClient({
      addresses: [{ host, port }],
    });

    await myCommands(client);
  } catch (error) {
    console.error("エラーが発生しました:", error);
    process.exitCode = 1;
  } finally {
    client?.close();
  }
}

async function myCommands(client) {
  const pingResponse = await client.ping();
  console.log(pingResponse);

  const setResponse1 = await client.set("user:1000", "Alice");
  console.log(setResponse1);
  const getResponse1 = await client.get("user:1000");
  console.log(getResponse1);

  const setResponse2 = await client.hset("user:2000", {
    name: "Bob",
    email: "[email protected]",
    age: "30",
  });
  console.log(setResponse2);
  const getResponse2 = await client.hgetall("user:2000");
  console.log(getResponse2);
}

処理の実行 #

プロジェクトのディレクトリで次のコマンドでコンテナを起動します。

docker compose up -d --build

Node.js コンテナにログインします。

docker compose exec nodejs sh

index.js を実行します。

node index.js

PONG
OK
Alice
3
[
  { field: 'name', value: 'Bob' },
  { field: 'email', value: '[email protected]' },
  { field: 'age', value: '30' }
]

データの登録、取得ができていることが確認できました。

コンテナの後片付け #

コンテナを停止し、イメージを削除します。

docker compose down
docker compose down --rmi all

etcd を触ってみる #

本記事作成時点の etcd 最新バージョン 3.7 の前提で進めていきます

Go のアプリから操作する想定でさくっと触ってみます。

Go から etcd を操作するには etcd が提供する公式クライアントライブラリを使います:https://etcd.io/docs/v3.7/integrations/

以下のファイルを用意します。

my-etcd-app/
├── compose.yaml
├── go.mod
└── main.go

compose.yaml

services:
  etcd:
    image: gcr.io/etcd-development/etcd:v3.7.0
    command:
      - /usr/local/bin/etcd
      - --listen-client-urls=http://0.0.0.0:2379
      - --advertise-client-urls=http://etcd:2379

  go:
    image: golang:1.25-bookworm
    working_dir: /workspace
    command: ["sleep", "infinity"]
    volumes:
      - .:/workspace

go.mod

module my-etcd-app

go 1.26

require go.etcd.io/etcd/client/v3 v3.7.0

main.go

package main

import (
 "context"
 "fmt"
 "log"

 clientv3 "go.etcd.io/etcd/client/v3"
)

func main() {
 client, err := clientv3.NewFromURL("http://etcd:2379")
 if err != nil {
  log.Fatal(err)
 }
 defer client.Close()

 ctx := context.Background()

 if _, err := client.Put(ctx, "my-greeting", "Hello World!"); err != nil {
  log.Fatal(err)
 }

 response, err := client.Get(ctx, "my-greeting")
 if err != nil {
  log.Fatal(err)
 }

 fmt.Println(string(response.Kvs[0].Value))
}

プロジェクトのディレクトリで次のコマンドを実行して Go モジュールの依存関係を解決します。

docker compose run --rm --no-deps go go mod tidy

次のコマンドでコンテナを起動します。

docker compose up -d

Go コンテナにログインします。

docker compose exec go sh

main.go を実行します。

go run main.go

Hello World!

データの登録、取得ができていることが確認できました。

最後にコンテナを停止し、イメージを削除しておきましょう。

docker compose down
docker compose down --rmi all