Skip to content

Commit 920daec

Browse files
authored
Redis hardening: retry strategy (#18565)
* Add an error handler to ensure the Redis server connection is forcibly closed * Explain unknown errors are usually server idle timeouts * Add an automatic retry_strategy * Explicitly set connect_timeout to the default value of 1 hour
1 parent aeda8ec commit 920daec

1 file changed

Lines changed: 35 additions & 3 deletions

File tree

lib/redis/create-client.js

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,13 @@ const { REDIS_MIN_DB, REDIS_MAX_DB } = process.env
66
const redisMinDb = REDIS_MIN_DB || 0
77
const redisMaxDb = REDIS_MAX_DB || 15
88

9+
// Maximum delay between reconnection attempts after backoff
10+
const maxReconnectDelay = 5000
11+
912
function formatRedisError (error) {
1013
const errorCode = error ? error.code : null
11-
const errorName = error ? error.constructor.name : 'Error'
12-
const errorMsg = error ? error.toString() : 'unknown error'
14+
const errorName = error ? error.constructor.name : 'Server disconnection'
15+
const errorMsg = error ? error.toString() : 'unknown (commonly a server idle timeout)'
1316
const preamble = errorName + (errorCode ? ` with code "${errorCode}"` : '')
1417
return preamble + ': ' + errorMsg
1518
}
@@ -51,6 +54,35 @@ module.exports = function createClient (options = {}) {
5154
// This is also critical to preventing a backend pile-up!
5255
enable_offline_queue: false,
5356

57+
// This timeout value will be applied to both the initial connection
58+
// and any auto-reconnect attempts (if the `retry_strategy` option is
59+
// provided). If not using the `retry_strategy` option, this value can be
60+
// set to a very low number. If using the `retry_strategy` option to allow
61+
// more than one reconnection attempt, this value must be set to a higher
62+
// number. Defaults to 1 hour if not configured!
63+
connect_timeout: 60 * 60 * 1000, // 60 minutes
64+
65+
// Be aware that this retry (NOT just reconnection) strategy appears to
66+
// be a major point of confusion (and possibly legitimate issues) between
67+
// reconnecting and retrying failed commands.
68+
retry_strategy:
69+
function ({
70+
attempt,
71+
error,
72+
total_retry_time: totalRetryTime,
73+
times_connected: timesConnected
74+
}) {
75+
let delayPerAttempt = 100
76+
77+
// If the server appears to be unavailable, slow down faster
78+
if (error && error.code === 'ECONNREFUSED') {
79+
delayPerAttempt *= 5
80+
}
81+
82+
// Reconnect after delay
83+
return Math.min(attempt * delayPerAttempt, maxReconnectDelay)
84+
},
85+
5486
// Expand whatever other options and overrides were provided
5587
...options
5688
})
@@ -72,6 +104,7 @@ module.exports = function createClient (options = {}) {
72104
// Add event listeners for basic logging
73105
client.on('connect', () => { console.log(logPrefix, 'Connection opened') })
74106
client.on('ready', () => { console.log(logPrefix, 'Ready to receive commands') })
107+
client.on('end', () => { console.log(logPrefix, 'Connection closed') })
75108
client.on(
76109
'reconnecting',
77110
({
@@ -93,7 +126,6 @@ module.exports = function createClient (options = {}) {
93126
)
94127
}
95128
)
96-
client.on('end', () => { console.log(logPrefix, 'Connection closed') })
97129
client.on('warning', (msg) => { console.warn(logPrefix, 'Warning:', msg) })
98130
client.on('error', (error) => { console.error(logPrefix, formatRedisError(error)) })
99131

0 commit comments

Comments
 (0)