Skip to content

Commit 0bb979b

Browse files
committed
Add optional exponential backoff config
Allow configuring and enabling exponential backoff for failing connections, instead of relying on reconnections in regular intervals. Exponential backoff can be configured in order to lighten server load if multiple clients are reconnecting. The new configuration parameter is called `exponentialBackoff` and requires two parameters `backoffRate` and `backoffLimit`.
1 parent ed40a6c commit 0bb979b

File tree

2 files changed

+78
-0
lines changed

2 files changed

+78
-0
lines changed

README.md

+62
Original file line numberDiff line numberDiff line change
@@ -352,6 +352,68 @@ If you think that there is a potential case for you ending up queuing at least
352352
wrap `sarus.send` function calls in a try/catch statement, so as to handle
353353
those messages, should they occur.
354354

355+
### Exponential backoff
356+
357+
Configure exponential backoff like so:
358+
359+
```typescript
360+
import Sarus from '@anephenix/sarus';
361+
362+
const sarus = new Sarus({
363+
url: 'wss://ws.anephenix.com',
364+
exponentialBackoff: {
365+
// Exponential factor, here 2 will result in
366+
// 1 s, 2 s, 4 s, and so on increasing delays
367+
backoffRate: 2,
368+
// Never wait more than 2000 seconds
369+
backoffLimit: 2000,
370+
},
371+
});
372+
```
373+
374+
When a connection attempt repeatedly fails, decreasing the delay
375+
exponentially between each subsequent reconnection attempt is called
376+
[Exponential backoff](https://en.wikipedia.org/wiki/Exponential_backoff). The
377+
idea is that if a connection attempt failed after 1 second, and 2 seconds, then it is
378+
not necessary to check it on the 3rd second, since the probability of a
379+
reconnection succeeding on the third attempt is most likely not going up.
380+
Therefore, increasing the delay between each attempt factors in the assumption
381+
that a connection is not more likely to succeed by repeatedly probing in regular
382+
intervals.
383+
384+
This decreases both the load on the client, as well as on the server. For
385+
a client, fewer websocket connection attempts decrease the load on the client
386+
and on the network connection. For the server, should websocket requests fail
387+
within, then the load for handling repeatedly failing requests will fall
388+
as well. Furthermore, the burden on the network will also be decreased. Should
389+
for example a server refuse to accept websocket connections for one client,
390+
then there is the possibility that other clients will also not be able to connect.
391+
392+
Sarus implements _truncated exponential backoff_, meaning that the maximum
393+
reconnection delay is capped by another factor `backoffLimit` and will never
394+
exceed it. The exponential backoff rate itself is determined by `backoffRate`.
395+
If `backoffRate` is 2, then the delays will be 1 s, 2 s, 4 s, and so on.
396+
397+
The algorithm for reconnection looks like this in pseudocode:
398+
399+
```javascript
400+
// Configurable
401+
const backoffRate = 2;
402+
// The maximum delay will be 400s
403+
const backoffLimit = 400;
404+
let notConnected = false;
405+
let connectionAttempts = 1;
406+
while (notConnected) {
407+
const delay = Math.min(
408+
Math.pow(connectionAttempts, backoffRate),
409+
backoffLimit,
410+
);
411+
await delay(delay);
412+
notConnected = tryToConnect();
413+
connectionAttempts += 1;
414+
}
415+
```
416+
355417
### Advanced options
356418

357419
Sarus has a number of other options that you can pass to the client during

src/index.ts

+16
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,11 @@ const validateWebSocketUrl = (rawUrl: string): URL => {
7373
return url;
7474
};
7575

76+
export interface ExponentialBackoffParams {
77+
backoffRate: number;
78+
backoffLimit: number;
79+
}
80+
7681
export interface SarusClassParams {
7782
url: string;
7883
binaryType?: BinaryType;
@@ -81,6 +86,7 @@ export interface SarusClassParams {
8186
retryProcessTimePeriod?: number;
8287
reconnectAutomatically?: boolean;
8388
retryConnectionDelay?: boolean | number;
89+
exponentialBackoff?: ExponentialBackoffParams;
8490
storageType?: string;
8591
storageKey?: string;
8692
}
@@ -96,6 +102,7 @@ export interface SarusClassParams {
96102
* @param {boolean} param0.reconnectAutomatically - An optional boolean flag to indicate whether to reconnect automatically when a websocket connection is severed
97103
* @param {number} param0.retryProcessTimePeriod - An optional number for how long the time period between retrying to send a messgae to a WebSocket server should be
98104
* @param {boolean|number} param0.retryConnectionDelay - An optional parameter for whether to delay WebSocket reconnection attempts by a time period. If true, the delay is 1000ms, otherwise it is the number passed. The default value when this parameter is undefined will be interpreted as 1000ms.
105+
* @param {ExponentialBackoffParams} param0.exponentialBackoff - An optional containing configuration for exponential backoff. If this parameter is undefined, exponential backoff is disabled. The minimum delay is determined by retryConnectionDelay. If retryConnectionDelay is set is false, this setting will not be in effect.
99106
* @param {string} param0.storageType - An optional string specifying the type of storage to use for persisting messages in the message queue
100107
* @param {string} param0.storageKey - An optional string specifying the key used to store the messages data against in sessionStorage/localStorage
101108
* @returns {object} The class instance
@@ -109,6 +116,7 @@ export default class Sarus {
109116
retryProcessTimePeriod?: number;
110117
reconnectAutomatically?: boolean;
111118
retryConnectionDelay: number;
119+
exponentialBackoff?: ExponentialBackoffParams;
112120
storageType: string;
113121
storageKey: string;
114122

@@ -164,6 +172,7 @@ export default class Sarus {
164172
reconnectAutomatically,
165173
retryProcessTimePeriod, // TODO - write a test case to check this
166174
retryConnectionDelay,
175+
exponentialBackoff,
167176
storageType = "memory",
168177
storageKey = "sarus",
169178
} = props;
@@ -208,6 +217,13 @@ export default class Sarus {
208217
? undefined
209218
: retryConnectionDelay) ?? 1000;
210219

220+
/*
221+
When a exponential backoff parameter object is provided, reconnection
222+
attemptions will be increasingly delayed by an exponential factor.
223+
This feature is disabled by default.
224+
*/
225+
this.exponentialBackoff = exponentialBackoff;
226+
211227
/*
212228
Sets the storage type for the messages in the message queue. By default
213229
it is an in-memory option, but can also be set as 'session' for

0 commit comments

Comments
 (0)