2025.07.02
RedisからValkeyへのアップグレード
こんにちは。次世代システム研究室のL.W.です。
Redis は高速で信頼性の高いインメモリデータストアとして広く使われていますが、ライセンスの変更やコミュニティの動向により、Valkey(Redis のフォーク)への移行が注目されています。AWS ElastiCache を使用した Redis 6.2.6 から Valkey 8.0 への インプレースアップグレード(In-Place Engine Upgrade)を実施しました。データ損失リスクを最小限に抑えつつ、安定した移行を実現する方法を共有します。
1.なぜ Valkey へのアップグレードが必要か
Redis の最近のライセンス変更(Redis 7.0 以降の SSPL への移行)により、コミュニティはオープンソースの代替として Valkey を採用しています。Valkey は Redis のプロトコルとデータ構造を完全にサポートし、AWS ElastiCache でも利用可能です。そして、Redis からValkeyへアップグレードすることで、 最大 33%オフの低価格となります。以下は移行の主な理由です:
-
オープンソースの継続性:Valkey は BSD ライセンスを採用し、コミュニティ主導で開発。
-
AWS のサポート:ElastiCache は Redis 5.0.6 以降から Valkey 8.0 への原地アップグレードをサポート。
-
互換性:Redis のクライアント(例:@node-redis/client)は Valkey と互換性があり、コード変更が最小限。接続変更が不要です。
- コスト:クラウドの場合、Valkeyは他のエンジンと比較して低価格です。
2.開発環境での検証
開発環境ではNestjs+redisの構造です。dockerで検証されています。
2.1. 既存の設定
docker-compose.ymlファイルでのredisの設定は以下の通りです。
既存のデータは./docker/redis/data:/dataに保持されています。
redis: image: redis:6.2.6-alpine platform: linux/amd64 ports: - 6379:6379 command: redis-server --appendonly yes --requirepass adam volumes: - ./docker/redis/data:/data
2.2. Valkeyへ切り替え
サービス名はredisでいいです。これでサーバ側での接続エンドポイントを変えなくても済みます。
REDIS_READER_URL="redis://redis:6379/0" REDIS_WRITER_URL="redis://redis:6379/0"
既存のredisのコンテナを停止し、削除する。redisのサービスを以下のように修正します。
redis: #image: redis:6.2.6-alpine image: valkey/valkey:8.1.2-alpine platform: linux/amd64 ports: - 6379:6379 #command: redis-server --appendonly yes --requirepass adam command: valkey-server --appendonly yes --requirepass adam volumes: - ./docker/redis/data:/data
「docker-compose up -d」コマンドで該当redisを起動することだけで、アプリケーションサーバとうまく接続されていることが確認できます。
「docker compose exec redis redis-cli -a adam」でredisに入って、「info server」でValkeyのバージョンを確認できます。
info server # Server redis_version:7.2.4 server_name:valkey valkey_version:8.1.2
データロスなし、redisのコマンドはValkeyで実行できることが確認できます。
サーバ側の接続エンドポイントも変更が必要がありません。
2.3. redisのダウンタイムへの対処
サーバ側が以下のような形で、redisと繋がっています。
import { createClient } from '@node-redis/client'; export class RedisConnectionModule implements OnModuleDestroy { constructor(@Inject(REDIS_CLIENTS) private readonly clients: RedisClients) {} static async register( readerOptions: RedisConnectionOptions, writerOptions: RedisConnectionOptions, ): Promise<DynamicModule> { return { module: RedisConnectionModule, providers: [ { provide: REDIS_CLIENTS, useFactory: async () => { const logger = new Logger(RedisConnectionModule.name); // データの読み取り専用 const reader = createClient({ ...readerOptions, disableOfflineQueue: true, }); // データ記入専用 const writer = createClient({ ...writerOptions, disableOfflineQueue: true, }); // retryの戦略を考える let retryReader = 0; let retryWriter = 0; reader.on('error', (e) => { retryReader++; if (retryReader < 10) { logger.error( `Redis reader connection is closed. retry: ${retryReader} / ${e}`, ); } }); reader.on('connect', () => { if (retryReader > 0) { logger.log( `[RECONNECTED] Redis reader connection is established.`, ); } else { logger.log(`Redis reader connection is established.`); } retryReader = 0; }); writer.on('error', (e) => { retryWriter++; if (retryWriter < 10) { logger.error( `Redis writer connection is closed. retry: ${retryWriter} / ${e}`, ); } }); writer.on('connect', () => { if (retryWriter > 0) { logger.log( `[RECONNECTED] Redis writer connection is established.`, ); } else { logger.log(`Redis writer connection is established.`); } retryWriter = 0; }); await reader.connect().catch((e) => { throw new Error(`Failed to connect to redis reader. ${e}`); }); await writer.connect().catch((e) => { throw new Error(`Failed to connect to redis writer. ${e}`); }); return { reader, writer, }; }, }, ], exports: [RedisService], }; } async onModuleDestroy() { await this.clients.reader.disconnect(); await this.clients.writer.disconnect(); } }
もちろん、切り替え期間で、redisのサービスダウンにより、「Redis reader connection is closed」が出てきましたね。そして、redisの読み取り、記入の操作と関わるところを呼び出されると、すぐに「getaddrinfo ENOTFOUND redis」のようなエラーも飛び出しました。
短期間のタイムダウン中に、ユーザに感知させないように、「getaddrinfo ENOTFOUND redis」のようなエラーを出たくないですね。
‘@node-redis/client’でのdisableOfflineQueue
: true
に設定すると、クライアントが再接続中の場合にコマンドが拒否されます。データのロスにつながる可能性があります。
disableOfflineQueue
: falseにすることことで
再接続まで、コマンドの実行を待つことができます。
接続する途端、コマンドをロスなく実行でき、呼び出し側にもエラーを出ないことも検証できました。
3.AWS ElastiCacheでのアップグレード
awsでredisからValkeyへのアップグレードが簡単です。コンソール画面で操作できます。
アップグレード中に、ダウンタイムが発生しました。
[ERROR] 2025-07-01T04:41:14.862Z [RedisConnectionModule] Redis reader connection is closed. retry: 1 / Error: read ETIMEDOUT undefined [LOG] 2025-07-01T04:41:14.876Z [RedisConnectionModule] [RECONNECTED] Redis reader connection is established. undefined [ERROR] 2025-07-01T04:43:56.184Z [RedisConnectionModule] Redis writer connection is closed. retry: 1 / Error: Socket closed unexpectedly undefined [LOG] 2025-07-01T04:43:56.202Z [RedisConnectionModule] [RECONNECTED] Redis writer connection is established. undefined
既存のredisのKeyspaceによりますが、ログからすると、ミリ秒のダウンタイムが分かりますね。disableOfflineQueue
: falseにすることで、影響を最小限にお抑えるかと思います。
ちなみに、アップグレード後でも、接続するエンドポイントの変更が要りません。そして、接続コマンドも変わらないです。
redis-cli -c --tls -a '<your password>' -h <endpoint> -p 6379
もちろん、データもそのまま引き継がれたことも検証できました。
4.まとめ
今回のawsのRedis 6.2.6 から Valkey 8.0 へのアップグレードには、データ損失リスクを最小限に抑えつつ、安定した移行を実現できました。
既存のRedisのバージョンは6.2.6ですが、エンジンの更新推奨日がすでに経過しています。Valkey 8.0 へのアップグレードには、コスト削減だけではなく、クラスターを最新にもできました。
一方通行なので、ValkeyからRedisにロールバックすることができません。アップグレードする前に、きちんとスナップショットを取って、必要な場合、新たなredisのクラスターの再作成が可能です。
5.最後に
次世代システム研究室では、グループ全体のインテグレーションを支援してくれるアーキテクトを募集しています。アプリケーション開発者の方、次世代システム研究室にご興味を持って頂ける方がいらっしゃいましたら、ぜひ募集職種一覧からご応募をお願いします。
皆さんのご応募をお待ちしています。
グループ研究開発本部の最新情報をTwitterで配信中です。ぜひフォローください。
Follow @GMO_RD