Redis が無限に再接続し続けた原因はたった1文字のタイプミスでした
投稿: 2023年1月8日 (最終更新: 2023年12月1日) • 4 分 で読了 • 1,680 語現在作っている学校用の Node.js (Express) アプリで Upstash の Redis を使おうとしたところ、切断→再接続を超高速で無限に繰り返す謎の不具合に悩まされたのですが、結果あっさり解決してしまった話です。
※この記事では本来は「URI」と表記するものであっても、わかりやすさを重視し「URL」に統一して表記しています。予めご了承下さい。
では早速、問題になったコードです。
ライブラリは ioredis と connect-redis を使って書きました。
また、以下のコードは Redis の部分のみを抜粋しているので、実際に動かす場合はその他の必要なコードを追加する必要があります。
const Redis = require('ioredis');
const connectRedis = require('connect-redis');
// Redis
const RedisStore = connectRedis(session);
const redisClient = new Redis(`redis://${process.env.REDIS_USER}:${process.env.REDIS_PASSWORD}@${process.env.REDIS_ENDPOINT}:${process.env.REDIS_PORT}`);
redisClient.on('error', e => {
console.error('Redis error:', e);
});
redisClient.on('connect', () => {
console.log('Redis connected');
});
redisClient.on('reconnecting', () => {
console.log('Redis reconnecting');
});
redisClient.on('end', () => {
console.log('Redis disconnected');
});
このプログラムを実行した結果、
Redis connected
Redis reconnecting
Redis connected
Redis reconnecting
Redis connected
Redis reconnecting
Redis connected
Redis reconnecting
Redis connected
Redis reconnecting
Redis connected
Redis reconnecting
Redis connected
Redis reconnecting
Redis connected
Redis reconnecting
Redis connected
Redis reconnecting
・
・
・
(以下、無限ループ)
こんな感じで、切断と再接続を超高速で無限に繰り返すという謎の挙動になってしまいました。
数日間悩み、しまいには Upstash の不備なんじゃないかとも思いながらも、ドキュメントを眺めていたらふと気が付きました。
先程のコードの6行目
const redisClient = new Redis(`redis://${process.env.REDIS_USER}:${process.env.REDIS_PASSWORD}@${process.env.REDIS_ENDPOINT}:${process.env.REDIS_PORT}`);
の redis:// の部分を rediss:// と、s を1つ多くしたらすんなり動いてしまいました。
そういえばデータベースを作るときに SSL/TLS を有効にした気が…
ちゃんと有効になってました。
ウェブサイトでも SSL/TLS が有効なページは、URL が http:// ではなく https:// と、s が1個多くついた文字列で始まるように、Redis も、SSL/TLS が有効だと rediss:// と s が1個多くつくようです。
先程のコードで指定した Redis の URL は redis:// から始まっており、SSL/TLS が有効になっていませんでした。
だから、Upstash 側で弾かれていたわけです。
以上のことをまとめると、正しいコードは以下のようになります。
const Redis = require('ioredis');
const connectRedis = require('connect-redis');
// Redis
const RedisStore = connectRedis(session);
const redisClient = new Redis(`rediss://${process.env.REDIS_USER}:${process.env.REDIS_PASSWORD}@${process.env.REDIS_ENDPOINT}:${process.env.REDIS_PORT}`);
redisClient.on('error', e => {
console.error('Redis error:', e);
});
redisClient.on('connect', () => {
console.log('Redis connected');
});
redisClient.on('reconnecting', () => {
console.log('Redis reconnecting');
});
redisClient.on('end', () => {
console.log('Redis disconnected');
});
6行目で指定している URL を、redis://
ではなく rediss://
で始めるようにしました。
ただ、開発環境に用意した Redis は SSL/TLS に対応していないことが多いと思います。
その場合、以下のようにして開発環境と本番環境で SSL/TLS の有効・無効を切り替えるといい感じになります。
const Redis = require('ioredis');
const connectRedis = require('connect-redis');
// Redis
const RedisStore = connectRedis(session);
const redisClient = new Redis(`${app.get('env') === 'development' ? 'redis://' : 'rediss://'}${process.env.REDIS_USER}:${process.env.REDIS_PASSWORD}@${process.env.REDIS_ENDPOINT}:${process.env.REDIS_PORT}`);
redisClient.on('error', e => {
console.error('Redis error:', e);
});
redisClient.on('connect', () => {
console.log('Redis connected');
});
redisClient.on('reconnecting', () => {
console.log('Redis reconnecting');
});
redisClient.on('end', () => {
console.log('Redis disconnected');
});
6行目の
app.get('env') === 'development' ? 'redis://' : 'rediss://'
の部分がポイントです。
app.get('env')
で、環境変数 NODE_ENV
の値を読み取り、開発環境か本番環境かを判定します。
NODE_ENV
が development
、すなわち開発環境であれば redis://
を、そうでなければ rediss://
を使うという動作になります。
以上、数日間悩み続けたトラブルがたった1文字加えるだけで解決してしまった話でした。
この記事が、同じトラブルに遭遇している方の一助になれば幸いです。
誤ったコードで再接続を無限に繰り返した結果、昨日のリクエスト数がすごいことになりました。
それほど長くは動かしていないのですが、ループの速度がものすごく、ログが残像しか見えない程の速度で流れていたのでこのくらい行っちゃうよなぁという感じです。
で、なぜこんなに悩まされたのかというと、Upstash の管理画面からコピペできる URL が SSL/TLS 非対応のもの (redis:// から始まるもの) だったんですよね。
もしかして CLI からアクセスする場合は rediss:// ではなく redis:// を使う必要があるんでしょうかね?
そこまで調べきれていないのでなんとも言えませんが、ちょっと不親切だなと思いました。
ちなみに、Upstash さんはご親切に各言語・ライブラリでのコードの書き方の例を表示してくれるのですが、そちらはちゃんと SSL/TLS 対応の URL になっていました。
うん、そっちをよく確認しなかった僕の方が悪い。
関連記事