e566bef1cc
* cache did docs in redis * drop table * expire from redis * fix tests * add cache class * update api * refactor * filter negative labels * fix up dev-env * refactor did cache to use new redis cache class * tidy * ensure caching negatives * redis cache tests * remove timeout on did cache * fix ns in test * rename driver * add timeout & fail open * add test for timeout & fail open * small pr feedback * refactor caches * bugfixg * test for caching negative values * little more to cache * wire up cache cfg * switch from redis scratch to redis * fix build issues * use different redis clients for tests * fix test * fix flaky test * use separate db for redis cache
232 lines
5.4 KiB
TypeScript
232 lines
5.4 KiB
TypeScript
import { wait } from '@atproto/common'
|
|
import { Redis } from '../src/'
|
|
import { ReadThroughCache } from '../src/cache/read-through'
|
|
|
|
describe('redis cache', () => {
|
|
let redis: Redis
|
|
|
|
beforeAll(async () => {
|
|
redis = new Redis({ host: process.env.REDIS_HOST || '' })
|
|
})
|
|
|
|
afterAll(async () => {
|
|
await redis.destroy()
|
|
})
|
|
|
|
it('caches according to namespace', async () => {
|
|
const ns1 = redis.withNamespace('ns1')
|
|
const ns2 = redis.withNamespace('ns2')
|
|
await Promise.all([
|
|
ns1.set('key', 'a'),
|
|
ns2.set('key', 'b'),
|
|
redis.set('key', 'c'),
|
|
])
|
|
const got = await Promise.all([
|
|
ns1.get('key'),
|
|
ns2.get('key'),
|
|
redis.get('key'),
|
|
])
|
|
expect(got[0]).toEqual('a')
|
|
expect(got[1]).toEqual('b')
|
|
expect(got[2]).toEqual('c')
|
|
|
|
await Promise.all([
|
|
ns1.setMulti({ key1: 'a', key2: 'b' }),
|
|
ns2.setMulti({ key1: 'c', key2: 'd' }),
|
|
redis.setMulti({ key1: 'e', key2: 'f' }),
|
|
])
|
|
const gotMany = await Promise.all([
|
|
ns1.getMulti(['key1', 'key2']),
|
|
ns2.getMulti(['key1', 'key2']),
|
|
redis.getMulti(['key1', 'key2']),
|
|
])
|
|
expect(gotMany[0]['key1']).toEqual('a')
|
|
expect(gotMany[0]['key2']).toEqual('b')
|
|
expect(gotMany[1]['key1']).toEqual('c')
|
|
expect(gotMany[1]['key2']).toEqual('d')
|
|
expect(gotMany[2]['key1']).toEqual('e')
|
|
expect(gotMany[2]['key2']).toEqual('f')
|
|
})
|
|
|
|
it('caches values when empty', async () => {
|
|
const vals = {
|
|
'1': 'a',
|
|
'2': 'b',
|
|
'3': 'c',
|
|
}
|
|
let hits = 0
|
|
const cache = new ReadThroughCache<string>(redis.withNamespace('test1'), {
|
|
staleTTL: 60000,
|
|
maxTTL: 60000,
|
|
fetchMethod: async (key) => {
|
|
hits++
|
|
return vals[key]
|
|
},
|
|
})
|
|
const got = await Promise.all([
|
|
cache.get('1'),
|
|
cache.get('2'),
|
|
cache.get('3'),
|
|
])
|
|
expect(got[0]).toEqual('a')
|
|
expect(got[1]).toEqual('b')
|
|
expect(got[2]).toEqual('c')
|
|
expect(hits).toBe(3)
|
|
|
|
const refetched = await Promise.all([
|
|
cache.get('1'),
|
|
cache.get('2'),
|
|
cache.get('3'),
|
|
])
|
|
expect(refetched[0]).toEqual('a')
|
|
expect(refetched[1]).toEqual('b')
|
|
expect(refetched[2]).toEqual('c')
|
|
expect(hits).toBe(3)
|
|
})
|
|
|
|
it('skips and refreshes cache when requested', async () => {
|
|
let val = 'a'
|
|
let hits = 0
|
|
const cache = new ReadThroughCache<string>(redis.withNamespace('test2'), {
|
|
staleTTL: 60000,
|
|
maxTTL: 60000,
|
|
fetchMethod: async () => {
|
|
hits++
|
|
return val
|
|
},
|
|
})
|
|
|
|
const try1 = await cache.get('1')
|
|
expect(try1).toEqual('a')
|
|
expect(hits).toBe(1)
|
|
|
|
val = 'b'
|
|
|
|
const try2 = await cache.get('1')
|
|
expect(try2).toEqual('a')
|
|
expect(hits).toBe(1)
|
|
|
|
const try3 = await cache.get('1', { revalidate: true })
|
|
expect(try3).toEqual('b')
|
|
expect(hits).toBe(2)
|
|
|
|
const try4 = await cache.get('1')
|
|
expect(try4).toEqual('b')
|
|
expect(hits).toBe(2)
|
|
})
|
|
|
|
it('accurately reports stale entries & refreshes the cache', async () => {
|
|
let val = 'a'
|
|
let hits = 0
|
|
const cache = new ReadThroughCache<string>(redis.withNamespace('test3'), {
|
|
staleTTL: 1,
|
|
maxTTL: 60000,
|
|
fetchMethod: async () => {
|
|
hits++
|
|
return val
|
|
},
|
|
})
|
|
|
|
const try1 = await cache.get('1')
|
|
expect(try1).toEqual('a')
|
|
|
|
await wait(5)
|
|
|
|
val = 'b'
|
|
|
|
const try2 = await cache.get('1')
|
|
// cache gives us stale value while it revalidates
|
|
expect(try2).toEqual('a')
|
|
|
|
await wait(5)
|
|
|
|
const try3 = await cache.get('1')
|
|
expect(try3).toEqual('b')
|
|
expect(hits).toEqual(3)
|
|
})
|
|
|
|
it('does not return expired dids & refreshes the cache', async () => {
|
|
let val = 'a'
|
|
let hits = 0
|
|
const cache = new ReadThroughCache<string>(redis.withNamespace('test4'), {
|
|
staleTTL: 0,
|
|
maxTTL: 1,
|
|
fetchMethod: async () => {
|
|
hits++
|
|
return val
|
|
},
|
|
})
|
|
|
|
const try1 = await cache.get('1')
|
|
expect(try1).toEqual('a')
|
|
|
|
await wait(5)
|
|
|
|
val = 'b'
|
|
|
|
const try2 = await cache.get('1')
|
|
expect(try2).toEqual('b')
|
|
expect(hits).toBe(2)
|
|
})
|
|
|
|
it('caches negative values', async () => {
|
|
let val: string | null = null
|
|
let hits = 0
|
|
const cache = new ReadThroughCache<string>(redis.withNamespace('test5'), {
|
|
staleTTL: 60000,
|
|
maxTTL: 60000,
|
|
fetchMethod: async () => {
|
|
hits++
|
|
return val
|
|
},
|
|
})
|
|
|
|
const try1 = await cache.get('1')
|
|
expect(try1).toEqual(null)
|
|
expect(hits).toBe(1)
|
|
|
|
val = 'b'
|
|
|
|
const try2 = await cache.get('1')
|
|
// returns cached negative value
|
|
expect(try2).toEqual(null)
|
|
expect(hits).toBe(1)
|
|
|
|
const try3 = await cache.get('1', { revalidate: true })
|
|
expect(try3).toEqual('b')
|
|
expect(hits).toEqual(2)
|
|
|
|
const try4 = await cache.get('1')
|
|
expect(try4).toEqual('b')
|
|
expect(hits).toEqual(2)
|
|
})
|
|
|
|
it('times out and fails open', async () => {
|
|
let val = 'a'
|
|
let hits = 0
|
|
const cache = new ReadThroughCache<string>(redis.withNamespace('test6'), {
|
|
staleTTL: 60000,
|
|
maxTTL: 60000,
|
|
fetchMethod: async () => {
|
|
hits++
|
|
return val
|
|
},
|
|
})
|
|
|
|
const try1 = await cache.get('1')
|
|
expect(try1).toEqual('a')
|
|
|
|
const orig = cache.redis.driver.get
|
|
cache.redis.driver.get = async (key) => {
|
|
await wait(600)
|
|
return orig(key)
|
|
}
|
|
|
|
val = 'b'
|
|
|
|
const try2 = await cache.get('1')
|
|
expect(try2).toEqual('b')
|
|
expect(hits).toBe(2)
|
|
})
|
|
})
|