[web] add LRU cache for geoip details (#34521)

GitOrigin-RevId: 0c96b53228b3e4fd764ac09e029db8b3f2b45de7
This commit is contained in:
Jakob Ackermann
2026-06-15 08:07:55 +00:00
committed by Copybot
parent 6c9bf3774d
commit d5384d9fdb
3 changed files with 40 additions and 1 deletions
@@ -3,12 +3,26 @@
import settings from '@overleaf/settings'
import logger from '@overleaf/logger'
import { fetchJson } from '@overleaf/fetch-utils'
import LRU from 'lru-cache'
/**
* @typedef {import('../../../types/subscription/currency').CurrencyCode} CurrencyCode
*/
const DEFAULT_CURRENCY_CODE = /** @type {const} */ 'USD'
const cache = new LRU({
max: settings.apis.geoIpLookup.cacheSize,
})
/**
* Cache details per /24 subnet, which is the smallest subnet routed on the public internet.
* IPv6 is not supported by GCP. We could cache by /48.
* @param {string} ip
*/
function networkCacheKey(ip) {
const octets = ip.split('.')
return octets.length === 4 ? octets.slice(0, 3).join('.') : ip
}
/** @type {Record<string, CurrencyCode>} */
const currencyMappings = {
@@ -99,10 +113,17 @@ async function getDetails(ip, callback) {
return
}
ip = ip.trim().split(' ')[0]
const cacheKey = networkCacheKey(ip)
const cached = cache.get(cacheKey)
if (cached) {
return cached
}
const url = new URL(settings.apis.geoIpLookup.url)
url.pathname += ip
logger.debug({ ip, url }, 'getting geo ip details')
return await fetchJson(url, { signal: AbortSignal.timeout(1_000) })
const details = await fetchJson(url, { signal: AbortSignal.timeout(1_000) })
cache.set(cacheKey, details)
return details
}
/**
+3
View File
@@ -232,6 +232,9 @@ module.exports = {
'127.0.0.1'
}:3003`,
},
geoIpLookup: {
cacheSize: intFromEnv('GEO_IP_LOOKUP_CACHE_SIZE', 10_000),
},
docstore: {
url: `http://${process.env.DOCSTORE_HOST || '127.0.0.1'}:3016`,
pubUrl: `http://${process.env.DOCSTORE_HOST || '127.0.0.1'}:3016`,
@@ -31,6 +31,7 @@ describe('GeoIpLookup', function () {
apis: {
geoIpLookup: {
url: 'http://lookup.com/',
cacheSize: 10_000,
},
},
}
@@ -87,6 +88,20 @@ describe('GeoIpLookup', function () {
new URL(ctx.settings.apis.geoIpLookup.url + ctx.ipAddress)
)
})
it('should cache lookups by /24 network', async function (ctx) {
const first = await ctx.GeoIpLookup.promises.getDetails('12.34.56.78')
const second = await ctx.GeoIpLookup.promises.getDetails('12.34.56.99')
ctx.fetchUtils.fetchJson.should.have.been.calledOnce
assert.equal(first, second)
assert.deepEqual(second, ctx.stubbedResponse)
})
it('should not share cache entries across different /24 networks', async function (ctx) {
await ctx.GeoIpLookup.promises.getDetails('12.34.56.78')
await ctx.GeoIpLookup.promises.getDetails('12.34.99.1')
ctx.fetchUtils.fetchJson.should.have.been.calledTwice
})
})
})