Skip to content
This repository has been archived by the owner on Sep 16, 2024. It is now read-only.

Commit

Permalink
feat(ttl): add ttl seconds to lmdb cache as optional configuration
Browse files Browse the repository at this point in the history
This will force records to expire on retrieval rather than manually deleting things on contract evaluation
  • Loading branch information
dtfiedler committed Mar 22, 2024
1 parent e04e68c commit 977bf01
Show file tree
Hide file tree
Showing 3 changed files with 58 additions and 7 deletions.
50 changes: 47 additions & 3 deletions src/cache/lmdb-kv-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,31 +21,75 @@ import { KVBufferStore } from '../types.js';

export class LmdbKVStore implements KVBufferStore {
private db: RootDatabase<Buffer, string>;
private ttlSeconds: number | undefined;

constructor({ dbPath }: { dbPath: string }) {
constructor({ dbPath, ttlSeconds }: { dbPath: string; ttlSeconds?: number }) {
this.db = open({
path: dbPath,
encoding: 'binary',
commitDelay: 100, // 100ms delay - increases writes per transaction to reduce I/O
});
this.ttlSeconds = ttlSeconds;
}

/**
* Attach the TTL to the value.
*/
private serialize(value: Buffer): Buffer {
if (this.ttlSeconds === undefined) return value;
const buffer = Buffer.from(`${this.ttlSeconds}`);
return Buffer.concat([buffer, value]);
}

/**
* Deserialize the value and check the TTL before returning.
*/
private deserialize(value: Buffer): Buffer | undefined {
if (this.ttlSeconds === undefined) return value;
const ttl = value.readUInt32BE(0);
if (ttl < Date.now()) {
return undefined;
}
return value.slice(4);
}

/**
* Get the value from the database and check the TTL.
* If the TTL has expired, remove the key.
*/
async get(key: string): Promise<Buffer | undefined> {
const value = this.db.get(key);
const value = await this.db.get(key);
if (!value) {
return undefined;
}
const buffer = this.deserialize(value);
if (!buffer) {
await this.del(key);
return undefined;
}
return value;
}

/**
* Check if the key exists in the database.
*/
async has(key: string): Promise<boolean> {
return this.db.doesExist(key);
}

/**
* Remove the key from the database.
*/
async del(key: string): Promise<void> {
if (await this.has(key)) {
await this.db.remove(key);
}
}

/**
* Set the value in the database with the TTL.
*/
async set(key: string, buffer: Buffer): Promise<void> {
await this.db.put(key, buffer);
await this.db.put(key, this.serialize(buffer));
}
}
2 changes: 1 addition & 1 deletion src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ dotenv.config();

export const EVALUATION_INTERVAL_MS = +env.varOrDefault(
'EVALUATION_INTERVAL_MS',
`${60 * 60 * 15}`, // 15 mins by default
`${1000 * 60 * 15}`, // 15 mins by default
);

export const RUN_RESOLVER = env.varOrDefault('RUN_RESOLVER', 'true') === 'true';
Expand Down
13 changes: 10 additions & 3 deletions src/system.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,17 +23,18 @@ import log from './log.js';
import { ContractTxId } from './types.js';

let lastEvaluationTimestamp: number | undefined;
export const getLastEvaluatedTimestamp = () => lastEvaluationTimestamp;
export const contract = new ArIO({
contract: new RemoteContract({
contractTxId: config.CONTRACT_TX_ID,
url: config.CONTRACT_CACHE_URL,
}),
});

export const getLastEvaluatedTimestamp = () => lastEvaluationTimestamp;
// TODO: this could be done using any KV store - or in memory. For now, we are using LMDB for persistence.
export const cache = new LmdbKVStore({
dbPath: config.ARNS_CACHE_PATH,
ttlSeconds: config.EVALUATION_INTERVAL_MS / 1000,
});

export async function evaluateArNSNames() {
Expand All @@ -53,7 +54,7 @@ export async function evaluateArNSNames() {
// create a map of the contract records for O(1) lookup
const contractRecordMap: Record<
ContractTxId,
{ owner: string; records: Record<string, ANTRecord> }
{ owner: string | undefined; records: Record<string, ANTRecord> }
> = {};
// TODO: wrap this in p-limit to avoid overloading the node process
await Promise.all(
Expand All @@ -69,7 +70,13 @@ export async function evaluateArNSNames() {

if (Object.keys(antRecords).length) {
contractRecordMap[contractTxId] = {
owner: await antContract.getOwner(),
owner: await antContract.getOwner().catch((err) => {
log.error('Failed to get owner for contract', {
contractTxId,
error: err,
});
return undefined;
}),
records: antRecords,
};
}
Expand Down

0 comments on commit 977bf01

Please sign in to comment.