Skip to content

Commit ad795ca

Browse files
committed
Optimize connection strategy with multiple hosts through ordered caching of active and inactive database hosts
1 parent a9fef73 commit ad795ca

File tree

3 files changed

+133
-3
lines changed

3 files changed

+133
-3
lines changed

doc/src/release_notes.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,12 @@ Thin Mode Changes
3939
matching. The search order is: the host name, then the subject alternative
4040
name (SAN), and then the service name.
4141

42+
#) Connection Optimization(Internal):
43+
When connecting to a host, if the host is found to be unreachable,
44+
add that host to a cache(mark it as down). Subsequent requests to get
45+
a connection will reorder the list of hosts to attempt making a connection
46+
to so that the hosts which are marked as down are at the end of the list.
47+
4248
#) Fixed bug when ``sslServerDNMatch`` is set and the connect string is in Easy
4349
Connect format but a value for ``SSL_SERVER_DN_MATCH`` is not set in that
4450
connect string.

lib/thin/sqlnet/connStrategy.js

Lines changed: 124 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ class ConnectDescription {
5454
class ConnStrategy {
5555
constructor() {
5656
this.nextOptToTry = 0;
57+
this.reorderDescriptionList = 0;
5758
this.retryCount = 0;
5859
this.lastRetryCounter = 0;
5960
this.lastRetryConnectDescription = 0;
@@ -95,6 +96,10 @@ class ConnStrategy {
9596
this.retryCount = config.retryCount;
9697
}
9798
}
99+
if (!this.reorderDescriptionList) {
100+
this.descriptionList = SOLE_INST_DHCACHE.reorderDescriptionList(this.descriptionList);
101+
this.reorderDescriptionList = true;
102+
}
98103
/* We try the address list at least once and upto (1 + retryCount) times */
99104
for (let d = this.lastRetryConnectDescription; d < this.descriptionList.length; d++) {
100105
const desc = this.descriptionList[d];
@@ -108,6 +113,11 @@ class ConnStrategy {
108113
}
109114
}
110115
for (let i = this.lastRetryCounter; i <= this.retryCount; ++i) {
116+
//Conn options must be reordered only when all options are tried
117+
// i.e for retry and before the first try.
118+
if (this.nextOptToTry == 0) {
119+
cOpts = SOLE_INST_DHCACHE.reorderAddresses(cOpts);
120+
}
111121
while (this.nextOptToTry < cOpts.length) {
112122
const copt = cOpts[this.nextOptToTry];
113123
this.lastRetryCounter = i;
@@ -174,4 +184,117 @@ async function createNode(str) {
174184
await navobj.navigate(cs);
175185
return cs;
176186
}
177-
module.exports = { createNode };
187+
188+
189+
class DownHostsCache {
190+
191+
constructor() {
192+
// Timeout for each item in the cache
193+
this.DOWN_HOSTS_TIMEOUT = 600;
194+
// Minimum amount of time between each refresh
195+
this.MIN_TIME_BETWEEN_REFRESH = 60;
196+
// DownHostsCache Map
197+
this.downHostsCacheMap = new Map();
198+
// Last Refresh Time
199+
this.lastRefreshTime = 0;
200+
}
201+
202+
/**
203+
* Add an address to the cache
204+
*
205+
* @param connOption
206+
* address to be cached
207+
* @return Map with address as key and time of insertion as value
208+
*/
209+
markDownHost(addr) {
210+
return this.downHostsCacheMap.set(addr, Date.now());
211+
}
212+
213+
// Remove elements older than DownHostsTimeout
214+
refreshCache() {
215+
if (Date.now() - this.MIN_TIME_BETWEEN_REFRESH * 1000 > this.lastRefreshTime) {
216+
this.downHostsCacheMap.forEach((value, key) => {
217+
const entryTime = value;
218+
if (entryTime != null && ((Date.now() - this.DOWN_HOSTS_TIMEOUT * 1000) > entryTime)) {
219+
this.downHostsCacheMap.delete(key);
220+
}
221+
});
222+
this.lastRefreshTime = Date.now();
223+
}
224+
}
225+
226+
/**
227+
* Reorder addresses such that cached elements
228+
* occur at the end of the array.
229+
*/
230+
reorderAddresses(cOpts) {
231+
this.refreshCache();
232+
233+
let topIdx = 0, btmIdx = cOpts.length - 1;
234+
235+
while (topIdx < btmIdx) {
236+
237+
// increment topIdx if the address is not cached
238+
while (topIdx <= btmIdx
239+
&& !this.isDownHostsCached(cOpts[topIdx]))
240+
topIdx++;
241+
242+
// decrement btmIdx if address is cached
243+
while (btmIdx >= topIdx
244+
&& this.isDownHostsCached(cOpts[btmIdx]))
245+
btmIdx--;
246+
247+
// swap cached with uncached
248+
if (topIdx < btmIdx)
249+
[cOpts[topIdx], cOpts[btmIdx]] = [cOpts[btmIdx], cOpts[topIdx]];
250+
251+
}
252+
return cOpts;
253+
}
254+
255+
isDownDescCached(desc) {
256+
//const desc = this.descriptionList[d];
257+
//let cOpts = new Array();
258+
const cOpts = desc.getConnectOptions();
259+
for (let i = 0; i < cOpts.length; i++) {
260+
if (!this.isDownHostsCached(cOpts[i]))
261+
return false;
262+
}
263+
return true;
264+
}
265+
/**
266+
* Reorder description list such that description with all connection options in downcache
267+
* is pushed to the end of the description list
268+
*/
269+
reorderDescriptionList(descs) {
270+
this.refreshCache();
271+
272+
let topIdx = 0, btmIdx = descs.length - 1;
273+
274+
while (topIdx < btmIdx) {
275+
276+
// increment topIdx if the address is not cached
277+
while (topIdx <= btmIdx
278+
&& !this.isDownDescCached(descs[topIdx]))
279+
topIdx++;
280+
281+
// decrement btmIdx if address is cached
282+
while (btmIdx >= topIdx
283+
&& this.isDownDescCached(descs[btmIdx]))
284+
btmIdx--;
285+
286+
// swap cached with uncached
287+
if (topIdx < btmIdx) {
288+
[descs[topIdx], descs[btmIdx]] = [descs[btmIdx], descs[topIdx]];
289+
}
290+
}
291+
return descs;
292+
}
293+
// Return if a host is cached
294+
isDownHostsCached(copt) {
295+
return this.downHostsCacheMap.has(copt.host);
296+
}
297+
}
298+
// Single instance
299+
const SOLE_INST_DHCACHE = new DownHostsCache();
300+
module.exports = { createNode, SOLE_INST_DHCACHE };

lib/thin/sqlnet/networkSession.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ const { Buffer } = require('buffer');
3737
const EzConnect = require("./ezConnectResolver.js");
3838
const { NLParamParser, tnsnamesFilePath } = require("./paramParser.js");
3939
const process = require('process');
40-
40+
const downHostInstance = require("./connStrategy.js").SOLE_INST_DHCACHE;
4141
/**
4242
*
4343
* @param {string} userConfig
@@ -319,7 +319,8 @@ class NetworkSession {
319319
connected = await this.connect2(address);
320320
}
321321
} catch (err) {
322-
if (err.message.startsWith('NJS-510')) {
322+
if (err.message.startsWith('NJS-510') && !this.ntAdapter.connected) {
323+
downHostInstance.markDownHost(address.host, Date.now()); // mark the host as down
323324
this.ntAdapter.connected = true; // Pretend as connected
324325
}
325326
if (this.ntAdapter) {

0 commit comments

Comments
 (0)