Skip to content

Commit

Permalink
feat: batch cache lookups with cache.getMulti
Browse files Browse the repository at this point in the history
Get multiple cache values in one call:

```javascript

import cache from "@fly/cache"

const results = await cache.getMultiString(["key1", "key1"])
// results = ["value1", "value2"]

const buffers = await cache.getMulti(["key1", "key2"])
// buffers = [ArrayBuffer, ArrayBuffer]
```
  • Loading branch information
mrkurt authored Sep 10, 2018
1 parent 8eb4eda commit f3efbd2
Show file tree
Hide file tree
Showing 6 changed files with 88 additions and 0 deletions.
26 changes: 26 additions & 0 deletions src/bridge/fly/cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,13 +61,39 @@ registerBridge('flyCacheGet',

bridge.cacheStore.get(rt.app.id, key).then((buf) => {
rt.reportUsage("cache:get", { size: buf ? buf.byteLength : 0 })
const b = transferInto(buf)
callback.applyIgnored(null, [null, transferInto(buf)])
}).catch((err) => {
log.error("got err in cache.get", err)
callback.applyIgnored(null, [null, null]) // swallow errors on get for now
})
})

registerBridge('flyCacheGetMulti',
function cacheGet(rt: Runtime, bridge: Bridge, keys: string | string[], callback: ivm.Reference<Function>) {
if (!bridge.cacheStore) {
callback.applyIgnored(null, [errCacheStoreUndefined.toString()])
return
}

if (typeof keys === "string") {
keys = JSON.parse(keys) as string[]
}
bridge.cacheStore.getMulti(rt.app.id, keys).then((result) => {
let byteLength = 0
const toTransfer: (null | ivm.Copy<ArrayBuffer>)[] = result.map((b) => {
byteLength += b ? b.byteLength : 0
return transferInto(b)
})
toTransfer.unshift(null)
rt.reportUsage("cache:get", { size: byteLength, keys: result.length })
callback.applyIgnored(null, toTransfer)
}).catch((err) => {
log.error("got err in cache.getMulti", err)
callback.applyIgnored(null, [null, null]) // swallow errors on get for now
})
})

registerBridge('flyCacheDel',
function cacheDel(rt: Runtime, bridge: Bridge, key: string, callback: ivm.Reference<Function>) {
if (!bridge.cacheStore) {
Expand Down
1 change: 1 addition & 0 deletions src/cache_store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export interface CacheSetOptions {
}
export interface CacheStore {
get(ns: string, key: string): Promise<Buffer | null>
getMulti(ns: string, keys: string[]): Promise<(Buffer | null)[]>
set(ns: string, key: string, value: any, options?: CacheSetOptions | number): Promise<boolean>
del(ns: string, key: string): Promise<boolean>
expire(ns: string, key: string, ttl: number): Promise<boolean>
Expand Down
6 changes: 6 additions & 0 deletions src/memory_cache_store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,12 @@ export class MemoryCacheStore implements CacheStore {
return Buffer.from(buf)
}

async getMulti(ns: string, keys: string[]) {
keys = keys.map((k) => keyFor(ns, k))
const bufs = await this.redis.mget(...keys)
return bufs.map((b: any) => !b ? null : Buffer.from(b))
}

async set(ns: string, key: string, value: any, options?: CacheSetOptions | number): Promise<boolean> {
const k = keyFor(ns, key)
const pipeline = this.redis.pipeline()
Expand Down
8 changes: 8 additions & 0 deletions src/redis_cache_store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,12 @@ export class RedisCacheStore implements CacheStore {
return ret
}

async getMulti(ns: string, keys: string[]) {
keys = keys.map((k) => keyFor(ns, k))
const bufs = await this.redis.getMultiBufferAsync(keys)
return bufs.map((b: any) => !b ? null : Buffer.from(b))
}

async set(ns: string, key: string, value: any, options?: CacheSetOptions | number): Promise<boolean> {
const k = keyFor(ns, key)
let ttl: number | undefined
Expand Down Expand Up @@ -154,6 +160,7 @@ async function* setScanner(redis: FlyRedis, key: string) {

class FlyRedis {
getBufferAsync: (key: Buffer | string) => Promise<Buffer>
getMultiBufferAsync: (keys: string[]) => Promise<Buffer[]>
setAsync: (key: string, value: Buffer, mode?: number | string, duration?: number, exists?: string) => Promise<"OK" | undefined>
expireAsync: (key: string, ttl: number) => Promise<boolean>
ttlAsync: (key: string) => Promise<number>
Expand All @@ -168,6 +175,7 @@ class FlyRedis {

const p = promisify
this.getBufferAsync = p(redis.get).bind(redis)
this.getMultiBufferAsync = p(redis.mget).bind(redis)
this.setAsync = p(redis.set).bind(redis)
this.expireAsync = p(redis.expire).bind(redis)
this.ttlAsync = p(redis.ttl).bind(redis)
Expand Down
32 changes: 32 additions & 0 deletions v8env/src/fly/cache/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,36 @@ export async function getString(key: string) {
}
}

/**
* Get multiple values from the cache.
* @param keys list of keys to retrieve
* @returns List of results in the same order as the provided keys
*/
export function getMulti(keys: string[]): Promise<(ArrayBuffer | null)[]> {
return new Promise<(ArrayBuffer | null)[]>(function cacheGetMultiPromise(resolve, reject) {
bridge.dispatch(
"flyCacheGetMulti",
JSON.stringify(keys),
function cacheGetMultiCallback(err: string | null | undefined, ...values: (ArrayBuffer | null)[]) {
if (err != null) {
reject(err)
return
}
resolve(values)
})
})
}

/**
* Get multiple string values from the cache
* @param keys list of keys to retrieve
* @returns list of results in the same order as the provided keys
*/
export async function getMultiString(keys: string[]) {
const raw = await getMulti(keys)
return raw.map((b) => b ? new TextDecoder("utf-8").decode(b) : null)
}

/**
* Sets a value at the specified key, with an optional ttl
* @param key The key to add or overwrite
Expand Down Expand Up @@ -178,6 +208,8 @@ import { default as global } from "./global"
const cache = {
get,
getString,
getMulti,
getMultiString,
set,
expire,
del,
Expand Down
15 changes: 15 additions & 0 deletions v8env/test/fly.cache.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,4 +58,19 @@ describe("@fly/cache", () => {
expect(setResult).to.eq(false)
expect(v).to.eq("asdf")
})

it("gets multiple values", async () => {
const k = `cache-test${Math.random()}`
const k2 = `cache-test${Math.random()}`

await cache.set(k, "multi-1")
await cache.set(k2, "multi-2")

const result = await cache.getMultiString([k, k2])
expect(result).to.be.an('array')

const [r1, r2] = result;
expect(r1).to.eq("multi-1")
expect(r2).to.eq("multi-2")
})
})

0 comments on commit f3efbd2

Please sign in to comment.