Redis が無限に再接続し続けた原因はたった1文字のタイプミスでした

投稿: 2023年1月8日 (最終更新: 2023年5月21日) • 読了目安: 4 分
Redis が無限に再接続し続けた原因はたった1文字のタイプミスでした

現在作っている学校用の 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 の不備なんじゃないかとも思いながらも、ドキュメントを眺めていたらふと気が付きました。

redis:// じゃなくて rediss:// じゃね?

先程のコードの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 を有効にした気が…

TLS (SSL) が有効になっている

ちゃんと有効になってました。

ウェブサイトでも 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_ENVdevelopment、すなわち開発環境であれば redis:// を、そうでなければ rediss:// を使うという動作になります。

以上、数日間悩み続けたトラブルがたった1文字加えるだけで解決してしまった話でした。

この記事が、同じトラブルに遭遇している方の一助になれば幸いです。

余談と愚痴

誤ったコードで再接続を無限に繰り返した結果、昨日のリクエスト数がすごいことになりました。

リクエスト数の推移; 昨日3931、本日87

それほど長くは動かしていないのですが、ループの速度がものすごく、ログが残像しか見えない程の速度で流れていたのでこのくらい行っちゃうよなぁという感じです。

で、なぜこんなに悩まされたのかというと、Upstash の管理画面からコピペできる URL が SSL/TLS 非対応のもの (redis:// から始まるもの) だったんですよね。

もしかして CLI からアクセスする場合は rediss:// ではなく redis:// を使う必要があるんでしょうかね?

そこまで調べきれていないのでなんとも言えませんが、ちょっと不親切だなと思いました。

ちなみに、Upstash さんはご親切に各言語・ライブラリでのコードの書き方の例を表示してくれるのですが、そちらはちゃんと SSL/TLS 対応の URL になっていました。

うん、そっちをよく確認しなかった僕の方が悪い。

See also

Colaboratory を楽しくする設定

2022年11月26日 • 読了目安: 1 分

Colaboratory を楽しくする設定

ローカリゼーションの難しさ

2022年9月25日 • 読了目安: 4 分

ローカリゼーションの難しさ

三項演算子が便利すぎて感動した

2021年10月20日 • 読了目安: 2 分

三項演算子が便利すぎて感動した

GitHub の草、17週連続フルコンボ達成!

2021年9月18日 • 読了目安: 1 分

GitHub の草、17週連続フルコンボ達成!