Node-redis: retry_strategy์—์„œ ๋ฐ˜ํ™˜๋œ ์˜ค๋ฅ˜๋Š” ์—ฌ์ „ํžˆ ํฌ์ฐฉ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

์— ๋งŒ๋“  2017๋…„ 02์›” 27์ผ  ยท  19์ฝ”๋ฉ˜ํŠธ  ยท  ์ถœ์ฒ˜: NodeRedis/node-redis

์•„๋ž˜ ์ฝ”๋“œ๊ฐ€ ์žˆ๊ณ  iptables -A OUTPUT -p tcp --dport 6379 -j REJECT ์‚ฌ์šฉํ•˜์—ฌ ์—ฐ๊ฒฐ ๋Š๊น€์„ ๋ชจ๋ฐฉํ•˜๋ ค๊ณ  ํ•ฉ๋‹ˆ๋‹ค.

self.client = redis.createClient(self.cfg.port, self.cfg.host, {
    retry_strategy: function (options) {
        console.log('retry strategy check');
        console.log(options);
        if (options.error) {
            if (options.error.code === 'ECONNREFUSED') {
                // End reconnecting on a specific error and flush all commands with a individual error
                return new Error('The server refused the connection');
            }
            if (options.error.code === 'ECONNRESET') {
                return new Error('The server reset the connection');
            }
            if (options.error.code === 'ETIMEDOUT') {
                return new Error('The server timeouted the connection');
            }
        }
        if (options.total_retry_time > 1000 * 60 * 60) {
            // End reconnecting after a specific timeout and flush all commands with a individual error
            return new Error('Retry time exhausted');
        }
        if (options.attempt > 5) {
            // End reconnecting with built in error
            return new Error('Retry attempts ended');
        }
        // reconnect after
        return 1000;
    }
});
self.client.on('ready', function () {
    log.trace('Redis client: ready');
});
self.client.on('connect', function () {
    log.trace('Redis client: connect');
});
self.client.on('reconnecting', function () {
    log.trace('Redis client: reconnecting');
});
self.client.on('error', function (err) {
    log.error({err: err}, 'Listener.redis.client error: %s', err);
    process.exit(1);
});
self.client.on('end', function () {
    log.trace('Redis client: end');
});
self.client.on('warning', function () {
    log.trace('Redis client: warning');
});

๋ชจ๋“  redis ์˜ค๋ฅ˜๋Š” error ์ด๋ฒคํŠธ์—์„œ ๋ฐœ์ƒํ•œ๋‹ค๊ณ  ๊ฐ€์ •ํ•ฉ๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ๋‹ค์Œ์€ ์ฝ˜์†” ์ถœ๋ ฅ์— ์žˆ๋Š” ๋‚ด์šฉ์ž…๋‹ˆ๋‹ค.

21:00:14.666Z TRACE ์Šคํฌ๋ฆฝํŠธ: Redis ํด๋ผ์ด์–ธํŠธ: ์—ฐ๊ฒฐ
21:00:14.695Z TRACE ์Šคํฌ๋ฆฝํŠธ: Redis ํด๋ผ์ด์–ธํŠธ: ์ค€๋น„๋จ
21:10:23.837Z TRACE ์Šคํฌ๋ฆฝํŠธ: Redis ํด๋ผ์ด์–ธํŠธ: ์ข…๋ฃŒ
์žฌ์‹œ๋„ ์ „๋žต ํ™•์ธ
{ ์‹œ๋„: 1,
error: { [์˜ค๋ฅ˜: redis.callision์— ๋Œ€ํ•œ Redis ์—ฐ๊ฒฐ. ์ •๋ณด:6379 ์‹คํŒจ - ECONNRESET ์ฝ๊ธฐ] ์ฝ”๋“œ: 'ECONNRESET', errno: 'ECONNRESET', ์‹œ์Šคํ…œ ํ˜ธ์ถœ: '์ฝ๊ธฐ' },
total_retry_time: 0,
times_connected: 1 }

/node_modules/q/q.js:155
๋˜์ง€๋‹ค ์ „์ž;
^^
AbortError: ์ŠคํŠธ๋ฆผ ์—ฐ๊ฒฐ์ด ์ข…๋ฃŒ๋˜๊ณ  ๋ช…๋ น์ด ์ค‘๋‹จ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ์ฒ˜๋ฆฌ๋˜์—ˆ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
RedisClient.flush_and_error์—์„œ (/node_modules/redis/index.js:350:23)
RedisClient.connection_gone์—์„œ (/node_modules/redis/index.js:612:18)
RedisClient.on_error์—์„œ (/node_modules/redis/index.js:398:10)
์†Œ์ผ“์—์„œ.(/node_modules/redis/index.js:272:14)
emitOne์—์„œ (events.js:90:13)
Socket.emit์—์„œ(events.js:182:7)
emitErrorNT์—์„œ (net.js:1255:8)
nextTickCallbackWith2Args์—์„œ (node.js:474:9)
process._tickCallback์—์„œ (node.js:388:17)

๊ทธ๋ฆฌ๊ณ  ์งˆ๋ฌธ์œผ๋กœ: ์—ฐ๊ฒฐ์ด ๋Š์–ด์ง„ ๊ฒƒ์„ ๊ฐ์ง€ํ•˜๋Š” ๋ฐ ์•ฝ 10๋ถ„์ด ๊ฑธ๋ฆฌ๋Š” ์ด์œ ๋Š” ๋ฌด์—‡์ž…๋‹ˆ๊นŒ? 10์ดˆ ์ด๋‚ด์— ์‘๋‹ต์ด ์—†์„ ๋•Œ ์˜ค๋ฅ˜๋ฅผ ๋ฐœ์ƒ์‹œํ‚ค๋Š” ๋ฐฉ๋ฒ•์ด ์žˆ์Šต๋‹ˆ๊นŒ? response_timeout ๋“ฑ๊ณผ ๊ฐ™์€ ์˜ต์…˜์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

  • ๋ฒ„์ „ : node_redis v.2.6.5 ๋ฐ Redis 3.0.7
  • ํ”Œ๋žซํผ : Ubuntu 14.04.4 LTS์˜ Node.js v5.5.0
  • ์„ค๋ช… : retry_strategy์˜ ์˜ค๋ฅ˜๊ฐ€ ์—ฌ์ „ํžˆ ํฌ์ฐฉ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
pending-author-input

๊ฐ€์žฅ ์œ ์šฉํ•œ ๋Œ“๊ธ€

์–ด๋–ค ์†Œ์‹์ด ์žˆ์Šต๋‹ˆ๊นŒ? ๋‚˜๋Š” ๊ฐ™์€ ๋ฌธ์ œ๋ฅผ ๊ฐ€์ง€๊ณ ์žˆ๋‹ค.

๋ชจ๋“  19 ๋Œ“๊ธ€

@pavelsc ๋‚˜๋Š” ์ด๊ฒƒ์„ ์žฌํ˜„ํ•˜๋ ค๊ณ  ์‹œ๋„ํ–ˆ์ง€๋งŒ ์ง€๊ธˆ๊นŒ์ง€๋Š” ํ•  ์ˆ˜ ์—†์—ˆ์Šต๋‹ˆ๋‹ค.

ํƒ€์‚ฌ ๋ชจ๋“ˆ ์—†์ด ๋ฌธ์ œ๋ฅผ ์žฌํ˜„ํ•ด ๋ณด์‹ญ์‹œ์˜ค. ํ˜„์žฌ ์ตœ์†Œํ•œ q ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค.

๊ฐ™์€ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค. ์˜๋„์ ์œผ๋กœ Redis ํด๋ผ์ด์–ธํŠธ์— ์ž˜๋ชป๋œ URL์„ ์ œ๊ณตํ•˜๋ฉด on.error ๋ฉ”์„œ๋“œ๊ฐ€ ํ˜ธ์ถœ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๋‹ค์Œ์€ ๊ฐ„๋‹จํ•œ ์˜ˆ์ž…๋‹ˆ๋‹ค.

var redis = require("redis");

exports.handler = function (event, context, callback) {

    console.log("Executing test lambda for diagnosing redis issues");

    var redisInfo = {
        HOST: process.env.REDIS_HOST,
        PORT: process.env.REDIS_PORT
    };

    console.log(process.env.REDIS_HOST);
    console.log(process.env.REDIS_PORT);

    console.log("Connecting to Redis...");

    var client = redis.createClient({
        host: redisInfo.HOST,
        port: redisInfo.PORT,
        retry_strategy: function (options) {

            if (options.total_retry_time > 2000) {
                console.log("throwing an error...");
                return new Error('Retry time exhausted');
            }

            return 200;
        }
    });

    // if you'd like to select database 3, instead of 0 (default), call
    // client.select(3, function() { /* ... */ });

    client.on("error", function (err) {
        console.log("Error " + err);
        callback(null, "Error with Redis");
    });

    client.on('connect', function() {
        console.log("Connected to Redis");
    });

    client.on('end', function() {
        console.log("Redis end");
    });

    client.set("string key", "string val", redis.print);
    client.hset("hash key", "hashtest 1", "some value", redis.print);
    client.hset(["hash key", "hashtest 2", "some other value"], redis.print);
    client.hkeys("hash key", function (err, replies) {
        console.log(replies.length + " replies:");
        replies.forEach(function (reply, i) {
            console.log("    " + i + ": " + reply);
        });
        client.quit();
    });

    client.quit();

    callback(null, "Success");
};

์ง€๊ธˆ์€ ์—ฐ๊ฒฐ ์ œํ•œ ์‹œ๊ฐ„์ด ๋งŒ๋ฃŒ๋œ ํ›„ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ '์˜ค๋ฅ˜'๊ฐ€ ๋ฐœ์ƒํ•˜๋Š” connect_timeout์„ ์‚ฌ์šฉํ•˜๋„๋ก ๋˜๋Œ๋ฆฌ๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

๋™์ผํ•œ ๋ฌธ์ œ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. ์ž˜๋ชป๋œ ๋์ ์ด ์žˆ๋Š” ์‚ฌ์šฉ์ž ์ง€์ • retry_strategy๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด "AbortError:"๊ฐ€ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค.

์ด๊ฒƒ์€ ์˜ค๋Š˜๋„ ๋‚˜๋ฅผ ์‚ฌ๋กœ์žก์•˜๋‹ค. ์ฝ”๋“œ๋ฅผ ๊ฐ„๋‹จํžˆ ์‚ดํŽด๋ณด๋ฉด ์ด๊ฒƒ์€ ์˜๋„์ ์ธ ๋™์ž‘์œผ๋กœ ๋ณด์ž…๋‹ˆ๋‹ค. https://github.com/NodeRedis/node_redis/blob/79558c524ff783000a6027fb159739770f98b10e/index.js#L405 ๋Š” retry_strategy ๊ฐ€ ์„ค์ •๋˜๋ฉด ์˜ค๋ฅ˜๋ฅผ ๋‚ด๋ณด๋‚ด์ง€ ์•Š๊ณ  ๋Œ€์‹  ๊ณ„์† ๋˜์ง„๋‹ค๊ณ  ๋ช…์‹œ์ ์œผ๋กœ ๋ช…์‹œํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ์™œ ์ด๋Ÿฌ๋Š”์ง€ ๊ถ๊ธˆํ•˜๊ธด ํ•œ๋ฐ, ํœ™ํœ™ ๋˜์ง„๋‹ค๊ธฐ ๋ณด๋‹ค๋Š” ๋ฐœ์‚ฐํ•˜์ง€ ๋ชปํ•˜๋Š” ์ด์œ ๊ฐ€ ์—†๋Š” ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค. ์ด ์กฐ๊ฑด๋ฌธ์ด ์ œ๊ฑฐ๋˜์ง€ ์•Š์•„ ํ•ญ์ƒ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•˜๋Š” ์ด์œ ๊ฐ€ ์žˆ์Šต๋‹ˆ๊นŒ?

์ €๋„ ์ด ๋ฌธ์ œ๋ฅผ ์ง€์†์ ์œผ๋กœ ๊ฒช๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

๋‚˜๋„ ENOTFOUND ๋ฐ›์„ ๋•Œ ์˜ค๋ฅ˜๋ฅผ ์žก์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.

{
    host: "foo",
    retry_strategy: function (options) {
        if (options.error && options.error.code === "ENOTFOUND") {
            return new Error("The server was not found");
        }

        // reconnect after
        return 1000;
}

์™€ ํ•จ๊ป˜:

redis.on("error", err => {
    console.error("Cache Error: " + err);
});

์‘์šฉ ํ”„๋กœ๊ทธ๋žจ์„ ๋””๋ฒ„๊น…ํ•  ๋•Œ retry_strategy ์—์„œ ์œ„์—์„œ ์–ธ๊ธ‰ํ•œ ๋Œ€๋กœ ENOTFOUND ๊ฒ€์‚ฌ์— ๋“ค์–ด๊ฐ€๊ณ  ์žˆ์ง€๋งŒ ์˜ค๋ฅ˜ ์ด๋ฒคํŠธ ์ฒ˜๋ฆฌ๊ธฐ๋ฅผ ํ˜ธ์ถœํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

๋™์ผํ•œ ๋ฌธ์ œ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. ์†Œ์Šค ์ฝ”๋“œ๋ฅผ ์กฐ์‚ฌํ•œ ํ›„ ๋ณ€๊ฒฝํ•˜๋ฉด
์ด ์ค„(๋˜๋Š” ๋””๋ฒ„๊ทธ ๋ชจ๋“œ ํ™œ์„ฑํ™”)
https://github.com/NodeRedis/node_redis/blob/009479537eb920d2c34045026a55d31febd1edd7/index.js#L381 -L382

๊ทธ๋ฆฌ๊ณ  ์—ฌ๊ธฐ์— ์ด โ€‹โ€‹์ฝ”๋“œ๋ฅผ ์‚ฝ์ž…ํ•˜์‹ญ์‹œ์˜ค(์ฆ‰์‹œ ๋ฐฐ์—ด์— ์˜ค๋ฅ˜๋ฅผ ์ถ”๊ฐ€ํ•˜์‹ญ์‹œ์˜ค)
https://github.com/NodeRedis/node_redis/blob/009479537eb920d2c34045026a55d31febd1edd7/index.js#L352 -L353

    if (options.error) {
      aggregated_errors.push(options.error);
    }

์ œ๋Œ€๋กœ ์ž‘๋™ํ•˜๊ณ  '์˜ค๋ฅ˜'๊ฐ€ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค.

ํ•ด๋‹น ํ•จ์ˆ˜์˜ ์ค‘์ฒฉ ๋ฃจํ”„๋Š” ์‹คํ–‰๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. 'command_queue'๊ฐ€ ๋น„์–ด ์žˆ๊ณ  error never get์ด ๋ฐฐ์—ด์— ์ถ”๊ฐ€๋˜์–ด ๋ฐฉ์ถœ๋˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. ๋‚ด๊ฐ€ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ์ดํ•ดํ•œ๋‹ค๋ฉด ๊ฝค ์˜ค๋ž˜๋œ ์ฝ”๋“œ์ด๋ฏ€๋กœ ์œ ์ง€ ๊ด€๋ฆฌ์ž ๋˜๋Š” @BridgeAR์˜ ์ž…๋ ฅ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.

๋‚˜๋Š” ๋˜ํ•œ ์ฒซ ๋ฒˆ์งธ ์‹คํŒจํ•œ ์—ฐ๊ฒฐ์—์„œ 'end' ์ด๋ฒคํŠธ๋ฅผ ๋‚ด๋ณด๋‚ด๋Š” ๊ฒƒ์„ ๋ณด์•˜์Šต๋‹ˆ๋‹ค. ์ด๋Š” ๋ฌด์–ธ๊ฐ€๋ฅผ ์˜๋ฏธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค(๋˜๋Š” ๊ทธ๋ ‡์ง€ ์•Š์„ ์ˆ˜๋„ ์žˆ์Œ). ์ดํ‹€ ์ „์— Redis๋ฅผ ์ง‘์–ด ๋“ค์—ˆ๊ธฐ ๋•Œ๋ฌธ์— ๋‚ด๋ถ€๊ฐ€ ์•„์ง ์–ด๋–ป๊ฒŒ ์ž‘๋™ํ•˜๋Š”์ง€ ์ž˜ ๋ชจ๋ฅด๊ฒ ์Šต๋‹ˆ๋‹ค. ์‹œ๊ฐ„์ด ์žˆ์„ ๋•Œ ํฌํฌํ•˜๊ณ  ์กฐ๊ธˆ ๋” ํŒŒํ—ค์ณ๋ณด๋ ค๊ณ  ํ•ฉ๋‹ˆ๋‹ค.

๊ทธ๋ฆฌ๊ณ  ๋ง ๊ทธ๋Œ€๋กœ ๋‹ค์Œ ๋ฌธ์ œ๋Š” ์ด ๋ฌธ์ œ #1198๊ณผ ์—ฐ๊ฒฐ๋œ ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค.

@v1adko ์ €๋Š” ํ˜„์žฌ ์—ฌํ–‰ ์ค‘์ด์ง€๋งŒ ์˜ค๋Š˜์ด๋‚˜ ๋‚ด์ผ(Ruben์ด ์ €๋ฅผ

์˜ค๋ฅ˜ ์‹œ๋‚˜๋ฆฌ์˜ค๋ฅผ ํ…Œ์ŠคํŠธํ•˜๊ธฐ ์œ„ํ•ด ์˜๋„์ ์œผ๋กœ ์ž˜๋ชป๋œ redis URL์ด ์žˆ์ง€๋งŒ redis์— ์—ฐ๊ฒฐํ•˜๋ ค๊ณ  ํ•  ๋•Œ retry_strategy๊ฐ€ ํ˜ธ์ถœ๋˜์ง€ ์•Š๋Š” ๊ฒƒ์„ ๋ด…๋‹ˆ๋‹ค. retry_strategy๋Š” ์—ฐ๊ฒฐ์ด ๋‹ซํž ๋•Œ๋งŒ ํ˜ธ์ถœ๋ฉ๋‹ˆ๋‹ค.

`const redis = require('redis');
const ๋กœ๊ทธ = require('./logUtil').logger;

module.exports.connect = () => {

var redisRetryStrategy = function(options) {
    if (options.error && options.error.code === 'ECONNREFUSED') {
        // End reconnecting on a specific error and flush all commands with 
        // a individual error
        log.error('The redis server refused the connection');
        return new Error('The redis server refused the connection');
    }

    log.info(`Already spent ${options.total_retry_time} milliseconds to re-establish connection with redis`);
    if (options.total_retry_time > 2000) {
        // End reconnecting after a specific timeout and flush all commands 
        // with a individual error 
        log.error('Retry time exhausted');
        return new Error('Retry time exhausted');
    }
    log.info(`Attempting ${options.attempt} time to establish connection with redis`);
    if (options.attempt > 5) {
        // End reconnecting with built in error 
        log.error('Exhausted the retry attempts to establish connection to redis');
        return undefined;
    }
    // reconnect after 
    return 100;
}


log.info(`Redis connection url is :${process.env.REDIS_URL}`);
var redisClient = redis.createClient(qualifyUrl(process.env.REDIS_URL), {
    retry_strategy: redisRetryStrategy
});

redisClient.offline_queue_length = 3;

redisClient.on('connect', function() {
    console.log('Connected to Redis');
});

redisClient.on('reconnecting', function() {
    console.log('Re-Connecting to Redis');
});

redisClient.on('error', (err)=> {
    console.log(`Error trying to create redis connection: ${JSON.stringify(err)}`);
});
return redisClient;

}

const qualifyUrl = (URL) => {
return '//' + url.replace(/^\/+/,"");
};

`

๋ˆ„๊ตฐ๊ฐ€๊ฐ€์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๋Š” ๋ฐ ๋„์›€์„ ์ค„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๊นŒ?

์—ฌ๊ธฐ๋„ ๋งˆ์ฐฌ๊ฐ€์ง€์ž…๋‹ˆ๋‹ค. ์ด ๋ถˆ์พŒํ•œ ํ•ดํ‚น์€ ์˜ˆ์ƒ๋˜๋Š” ๋™์ž‘์„ ์ƒ์„ฑํ•˜๋Š” ๊ฒƒ ๊ฐ™์ง€๋งŒ ๋” ๋„“์€ ์˜๋ฏธ๊ฐ€ ์žˆ๋Š”์ง€ ํ™•์‹คํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

const client = redis.createClient({
  retry_strategy: ({error}) => client.emit('error', error)
});

client.on('error', console.error);

๋‚˜๋Š” ํ˜„์žฌ ๊ฐ™์€ ๋ฌธ์ œ๋ฅผ ๊ฒช๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. retry_strategy๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ Readme์˜ ์˜ˆ์ œ์— ํ‘œ์‹œ๋œ ๋Œ€๋กœ ์˜ค๋ฅ˜๋ฅผ ๋ฐ˜ํ™˜ํ•˜์ง€๋งŒ ํด๋ผ์ด์–ธํŠธ์—์„œ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. @v1adko๊ฐ€ ์ œ์•ˆํ•œ ์ˆ˜์ • ์‚ฌํ•ญ์€ ์ ์–ด๋„ ์•ก๋ฉด ๊ทธ๋Œ€๋กœ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•ฉ๋‹ˆ๋‹ค.

์—ฌ๊ธฐ์— ์–ธ๊ธ‰๋œ ์ด์ „ ๋ฒ„์ „๊ณผ์˜ ๋น„ํ˜ธํ™˜์„ฑ์ด ๋ฌด์—‡์ธ์ง€ ๊ถ๊ธˆํ•ฉ๋‹ˆ๋‹ค.
https://github.com/NodeRedis/node_redis/blob/009479537eb920d2c34045026a55d31febd1edd7/index.js#L380

@maael์ด ์ง€์ ํ•œ ๊ฒƒ์ฒ˜๋Ÿผ ๋™์ž‘์€ retry_strategy๊ฐ€ ์„ค์ •๋œ ๊ฒฝ์šฐ ์˜๋„์ ์ธ ๊ฒƒ์œผ๋กœ ๋ณด์ž…๋‹ˆ๋‹ค. ์˜ˆ์ƒ๋˜๋Š” ๋™์ž‘์ด์ง€๋งŒ ๋ฌธ์„œ๊ฐ€ ์˜ฌ๋ฐ”๋ฅด์ง€ ์•Š์Šต๋‹ˆ๊นŒ? @c24w์—์„œ ์ œ์•ˆํ•œ ๋Œ€๋กœ ์ˆ˜๋™์œผ๋กœ ํด๋ผ์ด์–ธํŠธ์— ๋Œ€ํ•œ ์˜ค๋ฅ˜๋ฅผ ๋‚ด

ํŽธ์ง‘: ํŒจํ‚ค์ง€๋ฅผ ํŒŒํ—ค์น˜๋ฉด์„œ ์ˆ˜๋™์œผ๋กœ ๋ฐฉ์ถœํ•˜๋Š” ๊ฒƒ์ด ์•„๋งˆ๋„ ์•ž์œผ๋กœ ๋‚˜์•„๊ฐˆ ๊ธธ์ด ์•„๋‹ˆ๋ผ๋Š” ๊ฒƒ์„ ๊นจ๋‹ซ๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ์–ธ๊ธ‰๋œ ์ฃผ์š” ๋ณ€๊ฒฝ ์‚ฌํ•ญ์„ ์ดํ•ดํ•ด์•ผ ํ•˜๋Š” ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค.

์–ด๋–ค ์†Œ์‹์ด ์žˆ์Šต๋‹ˆ๊นŒ? ๋‚˜๋Š” ๊ฐ™์€ ๋ฌธ์ œ๋ฅผ ๊ฐ€์ง€๊ณ ์žˆ๋‹ค.

์†Œ์‹์ด ์žˆ๋‚˜์š”?

ํ•˜๋Š” ๊ฒƒ์€ ์ž˜๋ชป๋œ ์ƒ๊ฐ์ž…๋‹ˆ๋‹ค.
js if (options.error && options.error.code === 'ECONNREFUSED') { // End reconnecting on a specific error and flush all commands with // a individual error return Math.min(options.attempt * 100, 3000); }

๋™์ผํ•œ ๋ฌธ์ œ๊ฐ€ ์žˆ๋Š”๋ฐ retry_Strategy๊ฐ€ ์˜ค๋ฅ˜ ์ด๋ฒคํŠธ๋ฅผ ํŠธ๋ฆฌ๊ฑฐํ•˜์ง€ ์•Š๊ณ  ์•„์ง ์ˆ˜์ •๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๊นŒ?

์„ฑ๊ณตํ•˜์‹  ๋ถ„ ๊ณ„์‹ ๊ฐ€์š”?

๋Œ€์‹  ๊ตฌํ˜„์„ https://github.com/luin/ioredis ๋กœ ์ „ํ™˜ํ•˜์—ฌ ๋ช‡ ๊ฐ€์ง€ ๊ฐœ์„  ์‚ฌํ•ญ์„ ๊ฐ€์ ธ์™”์Šต๋‹ˆ๋‹ค(๋„ค์ดํ‹ฐ๋ธŒ Promises, lazyConnect(redis ํด๋ผ์ด์–ธํŠธ๋ฅผ ์ธ์Šคํ„ด์Šคํ™”ํ•  ๋•Œ ์—ฐ๊ฒฐ์„ ์—ด์ง€ ์•Š๊ณ  ํ•„์š”ํ•œ ์œ„์น˜์—์„œ ์˜ค๋ฅ˜๋ฅผ ์ •ํ™•ํ•˜๊ฒŒ ์ฒ˜๋ฆฌํ•˜๋Š” ๋ฐ ๋„์›€์ด ๋จ)). ๋‹ค์Œ ์ฝ”๋“œ๋ฅผ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

let cachedItem;

  try {
    logger.debug(`Fetching GraphCMS query in redis cache...`);
    // XXX If fetching data from redis fails, we will fall back to running the query against GraphCMS API in order to ensure the client gets the data anyway
    cachedItem = await redisClient.get(body);
  } catch (e) {
    logger.debug(`An exception occurred while fetching redis cache.`);
    logger.error(e);
    epsagon.setError(e);
  }

๋‹ค์Œ utils/redis.js :

import { createLogger } from '@unly/utils-simple-logger';
import Redis from 'ioredis';
import epsagon from './epsagon';

const logger = createLogger({
  label: 'Redis client',
});

/**
 * Creates a redis client
 *
 * <strong i="11">@param</strong> url Url of the redis client, must contain the port number and be of the form "localhost:6379"
 * <strong i="12">@param</strong> password Password of the redis client
 * <strong i="13">@param</strong> maxRetriesPerRequest By default, all pending commands will be flushed with an error every 20 retry attempts.
 *          That makes sure commands won't wait forever when the connection is down.
 *          Set to null to disable this behavior, and every command will wait forever until the connection is alive again.
 * <strong i="14">@return</strong> {Redis}
 */
export const getClient = (url = process.env.REDIS_URL, password = process.env.REDIS_PASSWORD, maxRetriesPerRequest = 20) => {
  const client = new Redis(`redis://${url}`, {
    password,
    showFriendlyErrorStack: true, // See https://github.com/luin/ioredis#error-handling
    lazyConnect: true, // XXX Don't attempt to connect when initializing the client, in order to properly handle connection failure on a use-case basis
    maxRetriesPerRequest,
  });

  client.on('connect', function () {
    logger.info('Connected to redis instance');
  });

  client.on('ready', function () {
    logger.info('Redis instance is ready (data loaded from disk)');
  });

  // Handles redis connection temporarily going down without app crashing
  // If an error is handled here, then redis will attempt to retry the request based on maxRetriesPerRequest
  client.on('error', function (e) {
    logger.error(`Error connecting to redis: "${e}"`);
    epsagon.setError(e);
  });

  return client;
};

๊ทธ๋ฆฌ๊ณ  utils/redis.test.js ํŒŒ์ผ:

import { getClient } from './redis';

let redisClient;
let redisClientFailure;

describe('utils/redis.js', () => {
  beforeAll(() => {
    redisClient = getClient();
    redisClientFailure = getClient('localhost:5555', null, 0); // XXX This shouldn't throw an error because we're using lazyConnect:true which doesn't automatically connect to redis
  });

  afterAll(async () => {
    await redisClient.quit();
    await redisClientFailure.quit();
  });

  describe('should successfully init the redis client', () => {
    test('when provided connection info are correct', async () => {
      // Environment variables are from the .env.test file - This tests a localhost connection only
      expect(redisClient.options.host).toEqual(process.env.REDIS_URL.split(':')[0]);
      expect(redisClient.options.port).toEqual(parseInt(process.env.REDIS_URL.split(':')[1], 10));
      expect(redisClient.options.password).toEqual(process.env.REDIS_PASSWORD);
    });

    test('when connection info are incorrect', async () => {
      expect(redisClientFailure.options.host).toEqual('localhost');
      expect(redisClientFailure.options.port).toEqual(5555);
    });
  });

  describe('should successfully perform native operations (read/write/delete/update)', () => {
    test('when using async/await (using native node.js promises)', async () => {
      const setResult = await redisClient.set('key-1', 'value-1');
      expect(setResult).toEqual('OK');

      const result = await redisClient.get('key-1');
      expect(result).toEqual('value-1');

      const delResult = await redisClient.del('key-1');
      expect(delResult).toEqual(1);

      const setResultB = await redisClient.set('key-1', 'value-1b');
      expect(setResultB).toEqual('OK');

      const resultB = await redisClient.get('key-1');
      expect(resultB).toEqual('value-1b');

      const setResultC = await redisClient.set('key-1', 'value-1c');
      expect(setResultC).toEqual('OK');

      const resultC = await redisClient.get('key-1');
      expect(resultC).toEqual('value-1c');
    });
  });

  describe('should allow to catch an error when failing to open a connection to redis, in order to gracefully handle the error instead of crashing the app', () => {
    test('when connection info are incorrect', async () => {
      expect(redisClientFailure.options.host).toEqual('localhost');
      expect(redisClientFailure.options.port).toEqual(5555);

      try {
        await redisClientFailure.set('key-1', 'value-1'); // This should throw an error, because the connection to redis will be made when executing the
        expect(true).toBe(false); // This shouldn't be called, or the test will fail
      } catch (e) {
        expect(e).toBeDefined();
        expect(e.message).toContain('Reached the max retries per request limit');
      }
      await redisClientFailure.quit();
    });
  });
});

ํ™˜๊ฒฝ ๋ณ€์ˆ˜:

REDIS_URL=localhost:6379
REDIS_PASSWORD=mypasswordissostrong
์ด ํŽ˜์ด์ง€๊ฐ€ ๋„์›€์ด ๋˜์—ˆ๋‚˜์š”?
0 / 5 - 0 ๋“ฑ๊ธ‰