feat(@vates/cached-dns.lookup): small DNS cache (#6196)

See https://xcp-ng.org/forum/topic/5775/dns-queries-during-backup-job
This commit is contained in:
Julien Fontanet 2022-04-22 15:27:41 +02:00 committed by GitHub
parent 31d085b6a1
commit c9475ddc65
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 216 additions and 1 deletions

View File

@ -0,0 +1,30 @@
Node does not cache queries to `dns.lookup`, which can lead application doing a lot of connections to have perf issues and to saturate Node threads pool.
This library attempts to mitigate these problems by providing a version of this function with a version short cache, applied on both errors and results.
> Limitation: `verbatim: false` option is not supported.
It has exactly the same API as the native method and can be used directly:
```js
import { createCachedLookup } from '@vates/cached-dns.lookup'
const lookup = createCachedLookup()
lookup('example.net', { all: true, family: 0 }, (error, result) => {
if (error != null) {
return console.warn(error)
}
console.log(result)
})
```
Or it can be used to replace the native implementation and speed up the whole app:
```js
// assign our cached implementation to dns.lookup
const restore = createCachedLookup().patchGlobal()
// to restore the previous implementation
restore()
```

View File

@ -0,0 +1 @@
../../scripts/npmignore

View File

@ -0,0 +1,63 @@
<!-- DO NOT EDIT MANUALLY, THIS FILE HAS BEEN GENERATED -->
# @vates/cached-dns.lookup
[![Package Version](https://badgen.net/npm/v/@vates/cached-dns.lookup)](https://npmjs.org/package/@vates/cached-dns.lookup) ![License](https://badgen.net/npm/license/@vates/cached-dns.lookup) [![PackagePhobia](https://badgen.net/bundlephobia/minzip/@vates/cached-dns.lookup)](https://bundlephobia.com/result?p=@vates/cached-dns.lookup) [![Node compatibility](https://badgen.net/npm/node/@vates/cached-dns.lookup)](https://npmjs.org/package/@vates/cached-dns.lookup)
> Cached implementation of dns.lookup
## Install
Installation of the [npm package](https://npmjs.org/package/@vates/cached-dns.lookup):
```
> npm install --save @vates/cached-dns.lookup
```
## Usage
Node does not cache queries to `dns.lookup`, which can leads application doing a lot of connections to have perf issues and to saturate Node threads pool.
This library attemps to mitigate these problems by providing a version of this function with a version short cache, applied on both errors and results.
> Limitation: `verbatim: false` option is not supported.
I has exactly the same API as the native method and can be used directly:
```js
import { createCachedLookup } from '@vates/cached-dns.lookup'
const lookup = createCachedLookup()
lookup('example.net', { all: true, family: 0 }, (error, result) => {
if (error != null) {
return console.warn(error)
}
console.log(result)
})
```
Or it can be used to replace the native implementation and speed up the whole app:
```js
// assign our cached implementation to dns.lookup
const restore = createCachedLookup().patchGlobal()
// to restore the previous implementation
restore()
```
## Contributions
Contributions are _very_ welcomed, either on the documentation or on
the code.
You may:
- report any [issue](https://github.com/vatesfr/xen-orchestra/issues)
you've encountered;
- fork and create a pull request.
## License
[ISC](https://spdx.org/licenses/ISC) © [Vates SAS](https://vates.fr)

View File

@ -0,0 +1,72 @@
'use strict'
const assert = require('assert/strict')
const dns = require('dns')
const LRU = require('lru-cache')
function reportResults(all, results, callback) {
if (all) {
callback(null, results)
} else {
const first = results[0]
callback(null, first.address, first.family)
}
}
exports.createCachedLookup = function createCachedLookup({ lookup = dns.lookup } = {}) {
const cache = new LRU({
max: 500,
// 1 minute: long enough to be effective, short enough so there is no need to bother with DNS TTLs
ttl: 60e3,
})
function cachedLookup(hostname, options, callback) {
let all = false
let family = 0
if (typeof options === 'function') {
callback = options
} else if (typeof options === 'number') {
family = options
} else if (options != null) {
assert.notEqual(options.verbatim, false, 'not supported by this implementation')
;({ all = all, family = family } = options)
}
// cache by family option because there will be an error if there is no
// entries for the requestion family so we cannot easily cache all families
// and filter on reporting back
const key = hostname + '/' + family
const result = cache.get(key)
if (result !== undefined) {
setImmediate(reportResults, all, result, callback)
} else {
lookup(hostname, { all: true, family, verbatim: true }, function onLookup(error, results) {
// errors are not cached because this will delay recovery after DNS/network issues
//
// there are no reliable way to detect if the error is real or simply
// that there are no results for the requested hostname
//
// there should be much fewer errors than success, therefore it should
// not be a big deal to not cache them
if (error != null) {
return callback(error)
}
cache.set(key, results)
reportResults(all, results, callback)
})
}
}
cachedLookup.patchGlobal = function patchGlobal() {
const previous = dns.lookup
dns.lookup = cachedLookup
return function restoreGlobal() {
assert.equal(dns.lookup, cachedLookup)
dns.lookup = previous
}
}
return cachedLookup
}

View File

@ -0,0 +1,32 @@
{
"engines": {
"node": ">=8"
},
"dependencies": {
"lru-cache": "^7.0.4"
},
"private": false,
"name": "@vates/cached-dns.lookup",
"description": "Cached implementation of dns.lookup",
"keywords": [
"cache",
"dns",
"lookup"
],
"homepage": "https://github.com/vatesfr/xen-orchestra/tree/master/@vates/cached-dns.lookup",
"bugs": "https://github.com/vatesfr/xen-orchestra/issues",
"repository": {
"directory": "@vates/cached-dns.lookup",
"type": "git",
"url": "https://github.com/vatesfr/xen-orchestra.git"
},
"author": {
"name": "Vates SAS",
"url": "https://vates.fr"
},
"license": "ISC",
"version": "0.0.0",
"scripts": {
"postversion": "npm publish --access public"
}
}

View File

@ -4,6 +4,8 @@ require('@xen-orchestra/log/configure.js').catchGlobalErrors(
require('@xen-orchestra/log').createLogger('xo:backups:worker') require('@xen-orchestra/log').createLogger('xo:backups:worker')
) )
require('@vates/cached-dns.lookup').createCachedLookup().patchGlobal()
const Disposable = require('promise-toolbox/Disposable') const Disposable = require('promise-toolbox/Disposable')
const ignoreErrors = require('promise-toolbox/ignoreErrors') const ignoreErrors = require('promise-toolbox/ignoreErrors')
const { compose } = require('@vates/compose') const { compose } = require('@vates/compose')

View File

@ -16,6 +16,7 @@
"postversion": "npm publish --access public" "postversion": "npm publish --access public"
}, },
"dependencies": { "dependencies": {
"@vates/cached-dns.lookup": "^0.0.0",
"@vates/compose": "^2.1.0", "@vates/compose": "^2.1.0",
"@vates/decorate-with": "^2.0.0", "@vates/decorate-with": "^2.0.0",
"@vates/disposable": "^0.1.1", "@vates/disposable": "^0.1.1",

View File

@ -6,6 +6,7 @@ import getopts from 'getopts'
import pRetry from 'promise-toolbox/retry' import pRetry from 'promise-toolbox/retry'
import { catchGlobalErrors } from '@xen-orchestra/log/configure.js' import { catchGlobalErrors } from '@xen-orchestra/log/configure.js'
import { create as createServer } from 'http-server-plus' import { create as createServer } from 'http-server-plus'
import { createCachedLookup } from '@vates/cached-dns.lookup'
import { createLogger } from '@xen-orchestra/log' import { createLogger } from '@xen-orchestra/log'
import { createSecureServer } from 'http2' import { createSecureServer } from 'http2'
import { genSelfSignedCert } from '@xen-orchestra/self-signed' import { genSelfSignedCert } from '@xen-orchestra/self-signed'
@ -15,6 +16,8 @@ import { load as loadConfig } from 'app-conf'
catchGlobalErrors(createLogger('xo:proxy')) catchGlobalErrors(createLogger('xo:proxy'))
createCachedLookup().patchGlobal()
const { fatal, info, warn } = createLogger('xo:proxy:bootstrap') const { fatal, info, warn } = createLogger('xo:proxy:bootstrap')
const APP_DIR = new URL('.', import.meta.url).pathname const APP_DIR = new URL('.', import.meta.url).pathname

View File

@ -27,6 +27,7 @@
"dependencies": { "dependencies": {
"@iarna/toml": "^2.2.0", "@iarna/toml": "^2.2.0",
"@koa/router": "^10.0.0", "@koa/router": "^10.0.0",
"@vates/cached-dns.lookup": "^0.0.0",
"@vates/compose": "^2.1.0", "@vates/compose": "^2.1.0",
"@vates/decorate-with": "^2.0.0", "@vates/decorate-with": "^2.0.0",
"@vates/disposable": "^0.1.1", "@vates/disposable": "^0.1.1",

View File

@ -33,6 +33,7 @@
> >
> In case of conflict, the highest (lowest in previous list) `$version` wins. > In case of conflict, the highest (lowest in previous list) `$version` wins.
- @vates/cached-dns.lookup major
- @vates/event-listeners-manager major - @vates/event-listeners-manager major
- xo-vmdk-to-vhd minor - xo-vmdk-to-vhd minor
- xo-server minor - xo-server minor

View File

@ -29,6 +29,7 @@
"dependencies": { "dependencies": {
"@iarna/toml": "^2.2.1", "@iarna/toml": "^2.2.1",
"@vates/async-each": "^0.1.0", "@vates/async-each": "^0.1.0",
"@vates/cached-dns.lookup": "^0.0.0",
"@vates/compose": "^2.1.0", "@vates/compose": "^2.1.0",
"@vates/decorate-with": "^2.0.0", "@vates/decorate-with": "^2.0.0",
"@vates/disposable": "^0.1.1", "@vates/disposable": "^0.1.1",

View File

@ -4,6 +4,7 @@ import * as SourceMapSupport from 'source-map-support'
import Bluebird from 'bluebird' import Bluebird from 'bluebird'
import execPromise from 'exec-promise' import execPromise from 'exec-promise'
import { catchGlobalErrors } from '@xen-orchestra/log/configure.js' import { catchGlobalErrors } from '@xen-orchestra/log/configure.js'
import { createCachedLookup } from '@vates/cached-dns.lookup'
import { createLogger } from '@xen-orchestra/log' import { createLogger } from '@xen-orchestra/log'
import main from './index.mjs' import main from './index.mjs'
@ -33,4 +34,6 @@ global.Promise = Bluebird
catchGlobalErrors(createLogger('xo:xo-server')) catchGlobalErrors(createLogger('xo:xo-server'))
createCachedLookup().patchGlobal()
execPromise(main) execPromise(main)

View File

@ -12241,6 +12241,11 @@ lru-cache@^6.0.0:
dependencies: dependencies:
yallist "^4.0.0" yallist "^4.0.0"
lru-cache@^7.0.4:
version "7.8.1"
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-7.8.1.tgz#68ee3f4807a57d2ba185b7fd90827d5c21ce82bb"
integrity sha512-E1v547OCgJvbvevfjgK9sNKIVXO96NnsTsFPBlg4ZxjhsJSODoH9lk8Bm0OxvHNm6Vm5Yqkl/1fErDxhYL8Skg==
lru-queue@^0.1.0: lru-queue@^0.1.0:
version "0.1.0" version "0.1.0"
resolved "https://registry.yarnpkg.com/lru-queue/-/lru-queue-0.1.0.tgz#2738bd9f0d3cf4f84490c5736c48699ac632cda3" resolved "https://registry.yarnpkg.com/lru-queue/-/lru-queue-0.1.0.tgz#2738bd9f0d3cf4f84490c5736c48699ac632cda3"
@ -17481,7 +17486,7 @@ tapable@^1.0.0, tapable@^1.1.3:
resolved "https://registry.yarnpkg.com/tapable/-/tapable-1.1.3.tgz#a1fccc06b58db61fd7a45da2da44f5f3a3e67ba2" resolved "https://registry.yarnpkg.com/tapable/-/tapable-1.1.3.tgz#a1fccc06b58db61fd7a45da2da44f5f3a3e67ba2"
integrity sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA== integrity sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==
tar-stream@^2.0.1: tar-stream@^2.0.1, tar-stream@^2.2.0:
version "2.2.0" version "2.2.0"
resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.2.0.tgz#acad84c284136b060dc3faa64474aa9aebd77287" resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.2.0.tgz#acad84c284136b060dc3faa64474aa9aebd77287"
integrity sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ== integrity sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==