Dedupe did cache refreshes ()

* dedupe refreshes to did cache

* handle expired cache entries as well

* apply in pds as well

* changeset
This commit is contained in:
Daniel Holmgren 2023-10-25 15:53:08 -05:00 committed by GitHub
parent ce28725e17
commit bb039d8e4c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 88 additions and 46 deletions

@ -0,0 +1,5 @@
---
'@atproto/identity': minor
---
Pass stale did doc into refresh cache functions

@ -17,28 +17,42 @@ export class DidSqlCache implements DidCache {
this.pQueue = new PQueue()
}
async cacheDid(did: string, doc: DidDocument): Promise<void> {
await this.db.db
.insertInto('did_cache')
.values({ did, doc, updatedAt: Date.now() })
.onConflict((oc) =>
oc.column('did').doUpdateSet({
doc: excluded(this.db.db, 'doc'),
updatedAt: excluded(this.db.db, 'updatedAt'),
}),
)
.executeTakeFirst()
async cacheDid(
did: string,
doc: DidDocument,
prevResult?: CacheResult,
): Promise<void> {
if (prevResult) {
await this.db.db
.updateTable('did_cache')
.set({ doc, updatedAt: Date.now() })
.where('did', '=', did)
.where('updatedAt', '=', prevResult.updatedAt)
.execute()
} else {
await this.db.db
.insertInto('did_cache')
.values({ did, doc, updatedAt: Date.now() })
.onConflict((oc) =>
oc.column('did').doUpdateSet({
doc: excluded(this.db.db, 'doc'),
updatedAt: excluded(this.db.db, 'updatedAt'),
}),
)
.executeTakeFirst()
}
}
async refreshCache(
did: string,
getDoc: () => Promise<DidDocument | null>,
prevResult?: CacheResult,
): Promise<void> {
this.pQueue?.add(async () => {
try {
const doc = await getDoc()
if (doc) {
await this.cacheDid(did, doc)
await this.cacheDid(did, doc, prevResult)
} else {
await this.clearEntry(did)
}
@ -55,20 +69,17 @@ export class DidSqlCache implements DidCache {
.selectAll()
.executeTakeFirst()
if (!res) return null
const now = Date.now()
const updatedAt = new Date(res.updatedAt).getTime()
const expired = now > updatedAt + this.maxTTL
if (expired) {
return null
}
const stale = now > updatedAt + this.staleTTL
return {
doc: res.doc,
updatedAt,
did,
stale,
expired,
}
}

@ -1,6 +1,12 @@
import * as crypto from '@atproto/crypto'
import { check } from '@atproto/common-web'
import { DidCache, AtprotoData, DidDocument, didDocument } from '../types'
import {
DidCache,
AtprotoData,
DidDocument,
didDocument,
CacheResult,
} from '../types'
import * as atprotoData from './atproto-data'
import { DidNotFoundError, PoorlyFormattedDidDocumentError } from '../errors'
@ -25,20 +31,25 @@ export abstract class BaseResolver {
return this.validateDidDoc(did, got)
}
async refreshCache(did: string): Promise<void> {
await this.cache?.refreshCache(did, () => this.resolveNoCache(did))
async refreshCache(did: string, prevResult?: CacheResult): Promise<void> {
await this.cache?.refreshCache(
did,
() => this.resolveNoCache(did),
prevResult,
)
}
async resolve(
did: string,
forceRefresh = false,
): Promise<DidDocument | null> {
let fromCache: CacheResult | null = null
if (this.cache && !forceRefresh) {
const fromCache = await this.cache.checkCache(did)
if (fromCache?.stale) {
await this.refreshCache(did)
}
if (fromCache) {
fromCache = await this.cache.checkCache(did)
if (fromCache && !fromCache.expired) {
if (fromCache?.stale) {
await this.refreshCache(did, fromCache)
}
return fromCache.doc
}
}
@ -48,7 +59,7 @@ export abstract class BaseResolver {
await this.cache?.clearEntry(did)
return null
}
await this.cache?.cacheDid(did, got)
await this.cache?.cacheDid(did, got, fromCache ?? undefined)
return got
}

@ -35,13 +35,12 @@ export class MemoryCache implements DidCache {
if (!val) return null
const now = Date.now()
const expired = now > val.updatedAt + this.maxTTL
if (expired) return null
const stale = now > val.updatedAt + this.staleTTL
return {
...val,
did,
stale,
expired,
}
}

@ -30,14 +30,20 @@ export type CacheResult = {
doc: DidDocument
updatedAt: number
stale: boolean
expired: boolean
}
export interface DidCache {
cacheDid(did: string, doc: DidDocument): Promise<void>
cacheDid(
did: string,
doc: DidDocument,
prevResult?: CacheResult,
): Promise<void>
checkCache(did: string): Promise<CacheResult | null>
refreshCache(
did: string,
getDoc: () => Promise<DidDocument | null>,
prevResult?: CacheResult,
): Promise<void>
clearEntry(did: string): Promise<void>
clear(): Promise<void>

@ -15,28 +15,42 @@ export class DidSqlCache implements DidCache {
this.pQueue = new PQueue()
}
async cacheDid(did: string, doc: DidDocument): Promise<void> {
await this.db.db
.insertInto('did_cache')
.values({ did, doc: JSON.stringify(doc), updatedAt: Date.now() })
.onConflict((oc) =>
oc.column('did').doUpdateSet({
doc: excluded(this.db.db, 'doc'),
updatedAt: excluded(this.db.db, 'updatedAt'),
}),
)
.executeTakeFirst()
async cacheDid(
did: string,
doc: DidDocument,
prevResult?: CacheResult,
): Promise<void> {
if (prevResult) {
await this.db.db
.updateTable('did_cache')
.set({ doc: JSON.stringify(doc), updatedAt: Date.now() })
.where('did', '=', did)
.where('updatedAt', '=', prevResult.updatedAt)
.execute()
} else {
await this.db.db
.insertInto('did_cache')
.values({ did, doc: JSON.stringify(doc), updatedAt: Date.now() })
.onConflict((oc) =>
oc.column('did').doUpdateSet({
doc: excluded(this.db.db, 'doc'),
updatedAt: excluded(this.db.db, 'updatedAt'),
}),
)
.executeTakeFirst()
}
}
async refreshCache(
did: string,
getDoc: () => Promise<DidDocument | null>,
prevResult?: CacheResult,
): Promise<void> {
this.pQueue?.add(async () => {
try {
const doc = await getDoc()
if (doc) {
await this.cacheDid(did, doc)
await this.cacheDid(did, doc, prevResult)
} else {
await this.clearEntry(did)
}
@ -55,18 +69,14 @@ export class DidSqlCache implements DidCache {
if (!res) return null
const now = Date.now()
const updatedAt = new Date(res.updatedAt).getTime()
const expired = now > updatedAt + this.maxTTL
if (expired) {
return null
}
const stale = now > updatedAt + this.staleTTL
return {
doc: JSON.parse(res.doc) as DidDocument,
updatedAt,
did,
stale,
expired,
}
}