Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[OTE-753] add new persistent cache table #2175

Merged
merged 4 commits into from
Aug 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions indexer/packages/postgres/__tests__/helpers/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ import {
TransactionCreateObject,
TransferCreateObject,
WalletCreateObject,
PersistentCacheCreateObject,
} from '../../src/types';
import { denomToHumanReadableConversion } from './conversion-helpers';

Expand Down Expand Up @@ -940,3 +941,15 @@ export const defaultLeaderboardPnlOneDayToUpsert: LeaderboardPnlCreateObject = {
currentEquity: '1000',
rank: 1,
};

// ============== Persistent cache Data ==============

export const defaultKV: PersistentCacheCreateObject = {
key: 'someKey',
value: 'someValue',
};

export const defaultKV2: PersistentCacheCreateObject = {
key: 'otherKey',
value: 'otherValue',
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { PersistentCacheFromDatabase } from '../../src/types';
import { clearData, migrate, teardown } from '../../src/helpers/db-helpers';
import { defaultKV, defaultKV2 } from '../helpers/constants';
import * as PersistentCacheTable from '../../src/stores/persistent-cache-table';

describe('Persistent cache store', () => {
beforeAll(async () => {
await migrate();
});

afterEach(async () => {
await clearData();
});

afterAll(async () => {
await teardown();
});

it('Successfully creates a key value pair', async () => {
await PersistentCacheTable.create(defaultKV);
});

it('Successfully upserts a kv pair multiple times', async () => {
const newKv = {
...defaultKV,
value: 'someOtherValue',
};
await PersistentCacheTable.upsert(newKv);
let kv: PersistentCacheFromDatabase | undefined = await PersistentCacheTable.findById(
defaultKV.key,
);
expect(kv).toEqual(expect.objectContaining(newKv));

const newKv2 = {
...defaultKV,
value: 'someOtherValue2',
};
await PersistentCacheTable.upsert(newKv2);
kv = await PersistentCacheTable.findById(defaultKV.key);

expect(kv).toEqual(expect.objectContaining(newKv2));
});

it('Successfully finds all kv pairs', async () => {
await Promise.all([
PersistentCacheTable.create(defaultKV),
PersistentCacheTable.create(defaultKV2),
]);

const kvs: PersistentCacheFromDatabase[] = await PersistentCacheTable.findAll(
{},
[],
{ readReplica: true },
);

expect(kvs.length).toEqual(2);
expect(kvs).toEqual(expect.arrayContaining([
expect.objectContaining(defaultKV),
expect.objectContaining(defaultKV2),
]));
});

it('Successfully finds a kv pair', async () => {
await PersistentCacheTable.create(defaultKV);

const kv: PersistentCacheFromDatabase | undefined = await PersistentCacheTable.findById(
defaultKV.key,
);

expect(kv).toEqual(expect.objectContaining(defaultKV));
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import * as Knex from 'knex';

export async function up(knex: Knex): Promise<void> {
return knex.schema.createTable('persistent_cache', (table) => {
table.string('key').primary().notNullable();
table.string('value').notNullable();
});
}
Comment on lines +3 to +8
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM! Consider adding a timestamp column.

The function correctly defines the schema for the persistent_cache table. However, it might be beneficial to add a timestamp column to track when entries are created or updated.

Consider adding the following lines to the table definition:

table.timestamps(true, true);
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export async function up(knex: Knex): Promise<void> {
return knex.schema.createTable('persistent_cache', (table) => {
table.string('key').primary().notNullable();
table.string('value').notNullable();
});
}
export async function up(knex: Knex): Promise<void> {
return knex.schema.createTable('persistent_cache', (table) => {
table.string('key').primary().notNullable();
table.string('value').notNullable();
table.timestamps(true, true);
});
}


export async function down(knex: Knex): Promise<void> {
return knex.schema.dropTable('persistent_cache');
}
1 change: 1 addition & 0 deletions indexer/packages/postgres/src/helpers/db-helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ const layer1Tables = [
'trading_rewards',
'trading_reward_aggregations',
'compliance_status',
'persistent_cache',
];

/**
Expand Down
2 changes: 2 additions & 0 deletions indexer/packages/postgres/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export { default as TradingRewardModel } from './models/trading-reward-model';
export { default as TradingRewardAggregationModel } from './models/trading-reward-aggregation-model';
export { default as SubaccountUsernamesModel } from './models/subaccount-usernames-model';
export { default as LeaderboardPnlModel } from './models/leaderboard-pnl-model';
export { default as PersistentCacheModel } from './models/persistent-cache-model';

export * as AssetTable from './stores/asset-table';
export * as AssetPositionTable from './stores/asset-position-table';
Expand All @@ -43,6 +44,7 @@ export * as TradingRewardTable from './stores/trading-reward-table';
export * as TradingRewardAggregationTable from './stores/trading-reward-aggregation-table';
export * as LeaderboardPnlTable from './stores/leaderboard-pnl-table';
export * as SubaccountUsernamesTable from './stores/subaccount-usernames-table';
export * as PersistentCacheTable from './stores/persistent-cache-table';

export * as perpetualMarketRefresher from './loops/perpetual-market-refresher';
export * as assetRefresher from './loops/asset-refresher';
Expand Down
31 changes: 31 additions & 0 deletions indexer/packages/postgres/src/models/persistent-cache-model.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import UpsertQueryBuilder from '../query-builders/upsert';
import BaseModel from './base-model';

export default class PersistentCacheModel extends BaseModel {
static get tableName() {
return 'persistent_cache';
}

static get idColumn() {
return 'key';
}

static relationMappings = {};

static get jsonSchema() {
return {
type: 'object',
required: ['key', 'value'],
properties: {
key: { type: 'string' },
value: { type: 'string' },
},
};
}

QueryBuilderType!: UpsertQueryBuilder<this>;

key!: string;

value!: string;
}
94 changes: 94 additions & 0 deletions indexer/packages/postgres/src/stores/persistent-cache-table.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import { QueryBuilder } from 'objection';

import { DEFAULT_POSTGRES_OPTIONS } from '../constants';
import { setupBaseQuery, verifyAllRequiredFields } from '../helpers/stores-helpers';
import Transaction from '../helpers/transaction';
import PersistentCacheModel from '../models/persistent-cache-model';
import {
Options,
Ordering,
QueryableField,
QueryConfig,
PersistentCacheColumns,
PersistentCacheCreateObject,
PersistentCacheFromDatabase,
PersistentCacheQueryConfig,
} from '../types';

export async function findAll(
{
key,
limit,
}: PersistentCacheQueryConfig,
requiredFields: QueryableField[],
options: Options = DEFAULT_POSTGRES_OPTIONS,
): Promise<PersistentCacheFromDatabase[]> {
verifyAllRequiredFields(
{
key,
limit,
} as QueryConfig,
requiredFields,
);

let baseQuery: QueryBuilder<PersistentCacheModel> = setupBaseQuery<PersistentCacheModel>(
PersistentCacheModel,
options,
);

if (key) {
baseQuery = baseQuery.where(PersistentCacheColumns.key, key);
}

if (options.orderBy !== undefined) {
for (const [column, order] of options.orderBy) {
baseQuery = baseQuery.orderBy(
column,
order,
);
}
} else {
baseQuery = baseQuery.orderBy(
PersistentCacheColumns.key,
Ordering.ASC,
);
}

if (limit) {
baseQuery = baseQuery.limit(limit);
}

return baseQuery.returning('*');
}

export async function create(
kvToCreate: PersistentCacheCreateObject,
options: Options = { txId: undefined },
): Promise<PersistentCacheFromDatabase> {
return PersistentCacheModel.query(
Transaction.get(options.txId),
).insert(kvToCreate).returning('*');
}

export async function upsert(
kvToUpsert: PersistentCacheCreateObject,
options: Options = { txId: undefined },
): Promise<PersistentCacheFromDatabase> {
const kvs: PersistentCacheModel[] = await PersistentCacheModel.query(
Transaction.get(options.txId),
).upsert(kvToUpsert).returning('*');
// should only ever be one key value pair
return kvs[0];
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: whitespace

export async function findById(
kv: string,
options: Options = DEFAULT_POSTGRES_OPTIONS,
): Promise<PersistentCacheFromDatabase | undefined> {
const baseQuery: QueryBuilder<PersistentCacheModel> = setupBaseQuery<PersistentCacheModel>(
PersistentCacheModel,
options,
);
return baseQuery
.findById(kv)
.returning('*');
}
5 changes: 5 additions & 0 deletions indexer/packages/postgres/src/types/db-model-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,11 @@ export interface LeaderboardPnlFromDatabase {
rank: number,
}

export interface PersistentCacheFromDatabase {
key: string,
value: string,
}

export type SubaccountAssetNetTransferMap = { [subaccountId: string]:
{ [assetId: string]: string }, };
export type SubaccountToPerpetualPositionsMap = { [subaccountId: string]:
Expand Down
1 change: 1 addition & 0 deletions indexer/packages/postgres/src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,5 @@ export * from './trading-reward-aggregation-types';
export * from './pagination-types';
export * from './subaccount-usernames-types';
export * from './leaderboard-pnl-types';
export * from './persistent-cache-types';
export { PositionSide } from './position-types';
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export interface PersistentCacheCreateObject {
key: string,
value: string,
}

export enum PersistentCacheColumns {
key = 'key',
value = 'value',
}
5 changes: 5 additions & 0 deletions indexer/packages/postgres/src/types/query-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ export enum QueryableField {
USERNAME = 'username',
TIMESPAN = 'timeSpan',
RANK = 'rank',
KEY = 'key',
IS_WHITELIST_AFFILIATE = 'isWhitelistAffiliate',
}

Expand Down Expand Up @@ -323,3 +324,7 @@ export interface LeaderboardPnlQueryConfig extends QueryConfig {
[QueryableField.TIMESPAN]?: string[],
[QueryableField.RANK]?: number[],
}

export interface PersistentCacheQueryConfig extends QueryConfig {
[QueryableField.KEY]?: string,
}
Loading