-
Notifications
You must be signed in to change notification settings - Fork 201
perf(postgres:subaccounts): add partial indices and optimize query fo… #3318
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
Changes from all commits
f3ad822
c99dddf
b39486d
04c39a5
3819467
9cb7ebe
994087d
02600f0
b52f00e
c510cf6
b0a98b9
1f1b68a
3129539
229f75f
1f219fa
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,47 @@ | ||
| import * as Knex from 'knex'; | ||
|
|
||
| export async function up(knex: Knex): Promise<void> { | ||
| await knex.raw( | ||
| 'DROP INDEX IF EXISTS "transfers_sendersubaccountid_createdatheight_index";', | ||
| ); | ||
|
|
||
| await knex.raw( | ||
| `CREATE INDEX CONCURRENTLY IF NOT EXISTS transfers_sender_id_height_nn | ||
| ON transfers("senderSubaccountId", "createdAtHeight") | ||
| WHERE "senderSubaccountId" IS NOT NULL;`, | ||
| ); | ||
|
|
||
| await knex.raw( | ||
| 'DROP INDEX IF EXISTS "transfers_recipientsubaccountid_createdatheight_index";', | ||
| ); | ||
|
|
||
| await knex.raw( | ||
| `CREATE INDEX CONCURRENTLY IF NOT EXISTS transfers_recipient_id_height_nn | ||
| ON transfers("recipientSubaccountId", "createdAtHeight") | ||
| WHERE "recipientSubaccountId" IS NOT NULL;`, | ||
| ); | ||
| } | ||
|
|
||
| export async function down(knex: Knex): Promise<void> { | ||
| await knex.raw( | ||
| 'DROP INDEX CONCURRENTLY IF EXISTS "transfers_sender_id_height_nn";', | ||
| ); | ||
|
|
||
| await knex.raw( | ||
| `CREATE INDEX CONCURRENTLY IF NOT EXISTS transfers_sendersubaccountid_createdatheight_index | ||
| ON transfers("senderSubaccountId", "createdAtHeight");`, | ||
| ); | ||
|
|
||
| await knex.raw( | ||
| 'DROP INDEX CONCURRENTLY IF EXISTS "transfers_recipient_id_height_nn";', | ||
| ); | ||
|
|
||
| await knex.raw( | ||
| `CREATE INDEX CONCURRENTLY IF NOT EXISTS transfers_recipientsubaccountid_createdatheight_index | ||
| ON transfers("recipientSubaccountId", "createdAtHeight");`, | ||
| ); | ||
| } | ||
|
|
||
| export const config = { | ||
| transaction: false, | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -12,14 +12,14 @@ import Transaction from '../helpers/transaction'; | |
| import { getUuid } from '../helpers/uuid'; | ||
| import SubaccountModel from '../models/subaccount-model'; | ||
| import { | ||
| QueryConfig, | ||
| SubaccountFromDatabase, | ||
| SubaccountQueryConfig, | ||
| SubaccountColumns, | ||
| SubaccountCreateObject, | ||
| Options, | ||
| Ordering, | ||
| QueryableField, | ||
| QueryConfig, | ||
| SubaccountColumns, | ||
| SubaccountCreateObject, | ||
| SubaccountFromDatabase, | ||
| SubaccountQueryConfig, | ||
| SubaccountUpdateObject, | ||
| } from '../types'; | ||
|
|
||
|
|
@@ -107,23 +107,44 @@ export async function getSubaccountsWithTransfers( | |
| createdBeforeOrAtHeight: string, | ||
| options: Options = DEFAULT_POSTGRES_OPTIONS, | ||
| ): Promise<SubaccountFromDatabase[]> { | ||
| const queryString: string = ` | ||
| SELECT * | ||
| FROM subaccounts | ||
| WHERE id IN ( | ||
| SELECT "senderSubaccountId" FROM transfers | ||
| WHERE "createdAtHeight" <= '${createdBeforeOrAtHeight}' | ||
| UNION | ||
| SELECT "recipientSubaccountId" FROM transfers | ||
| WHERE "createdAtHeight" <= '${createdBeforeOrAtHeight}' | ||
| ) | ||
| `; | ||
|
|
||
| const result: { | ||
| rows: SubaccountFromDatabase[], | ||
| } = await rawQuery(queryString, options); | ||
| const txId: number = await Transaction.start(); | ||
| const txOptions: Options = { | ||
| ...options, | ||
| txId, | ||
| }; | ||
|
|
||
| try { | ||
| await rawQuery('SET LOCAL work_mem = \'64MB\';', txOptions); | ||
|
|
||
| const result: { rows: SubaccountFromDatabase[] } = await rawQuery(` | ||
| SELECT s.* | ||
| FROM subaccounts s | ||
| WHERE EXISTS ( | ||
| SELECT 1 | ||
| FROM transfers t | ||
| WHERE t."senderSubaccountId" = s.id | ||
| AND t."createdAtHeight" <= :createdBeforeOrAtHeight::bigint | ||
| ) | ||
| OR EXISTS ( | ||
| SELECT 1 | ||
| FROM transfers t | ||
| WHERE t."recipientSubaccountId" = s.id | ||
| AND t."createdAtHeight" <= :createdBeforeOrAtHeight::bigint | ||
| )`, | ||
| { | ||
| ...txOptions, | ||
| bindings: { createdBeforeOrAtHeight }, | ||
| }); | ||
|
|
||
| await Transaction.commit(txId); | ||
|
|
||
| return result.rows; | ||
| return result.rows; | ||
|
|
||
| } catch (error) { | ||
| await Transaction.rollback(txId); | ||
| throw error; | ||
| } | ||
| } | ||
|
Comment on lines
+111
to
148
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: rg -n -B 2 -A 5 "getSubaccountsWithTransfers" --type tsRepository: dydxprotocol/v4-chain Length of output: 7682 🏁 Script executed: rg -n -B 10 -A 10 'getSubaccountsWithTransfers.*txId' indexer/services/roundtable/src/helpers/pnl-ticks-helper.tsRepository: dydxprotocol/v4-chain Length of output: 843 🏁 Script executed: rg -n -B 30 'getMostRecentPnlTicksForEachAccount\(\)' indexer/services/roundtable/src/helpers/pnl-ticks-helper.ts | head -60Repository: dydxprotocol/v4-chain Length of output: 2107 🏁 Script executed: rg -n -B 5 -A 10 'function getPnlTicksCreateObjects' indexer/services/roundtable/src/helpers/pnl-ticks-helper.ts | head -40Repository: dydxprotocol/v4-chain Length of output: 700 🏁 Script executed: # Check how BlockTable.getLatest handles txId
rg -n -A 15 'export async function getLatest' indexer/packages/postgres/src/stores/block-table.tsRepository: dydxprotocol/v4-chain Length of output: 609 🏁 Script executed: # Check the Transaction class to understand txId behavior
rg -n -A 20 'class Transaction|export.*function start' indexer/packages/postgres/src/utils/transaction.ts | head -50Repository: dydxprotocol/v4-chain Length of output: 155 🏁 Script executed: # Check how setupBaseQuery uses txId to verify if it's reused or created
rg -n -A 15 'setupBaseQuery' indexer/packages/postgres/src/utils/helpers.ts | head -40Repository: dydxprotocol/v4-chain Length of output: 151 🏁 Script executed: # Find Transaction-related files
fd -t f 'transaction' indexer/packages/postgres/src/Repository: dydxprotocol/v4-chain Length of output: 393 🏁 Script executed: # Find helper files
fd -t f 'helper' indexer/packages/postgres/src/ | head -20Repository: dydxprotocol/v4-chain Length of output: 484 🏁 Script executed: # Check the Transaction class implementation
rg -n -A 20 'export.*class Transaction|export.*function start' indexer/packages/postgres/src/helpers/transaction.ts | head -80Repository: dydxprotocol/v4-chain Length of output: 913 🏁 Script executed: # Check how other similar functions in subaccount-table.ts handle the txId parameter
rg -n -B 3 -A 10 'export async function' indexer/packages/postgres/src/stores/subaccount-table.ts | head -100Repository: dydxprotocol/v4-chain Length of output: 2900 🏁 Script executed: # Check how getPnlTicksCreateObjects is called
rg -n -B 5 -A 5 'getPnlTicksCreateObjects' indexer/services/roundtable/src/tasks/pnl-instrumentation.tsRepository: dydxprotocol/v4-chain Length of output: 47 🏁 Script executed: # Search for calls to getPnlTicksCreateObjects across the codebase
rg -n 'getPnlTicksCreateObjects' indexer/Repository: dydxprotocol/v4-chain Length of output: 1556 🏁 Script executed: # Check the create-pnl-ticks.ts to understand the transaction context
rg -n -B 10 -A 10 'getPnlTicksCreateObjects\(txId\)' indexer/services/roundtable/src/tasks/create-pnl-ticks.tsRepository: dydxprotocol/v4-chain Length of output: 830 Fix transaction handling: reuse existing The function creates a new transaction and ignores any Update the function to check if 🤖 Prompt for AI Agents |
||
|
|
||
| export async function create( | ||
|
|
@@ -229,7 +250,7 @@ export async function findIdsForParentSubaccount( | |
| // (subaccountNumber - parentSubaccountNumber) % MAX_PARENT_SUBACCOUNTS = 0 | ||
| return subaccounts | ||
| .filter((subaccount) => (subaccount.subaccountNumber - parentSubaccount.subaccountNumber) % | ||
| MAX_PARENT_SUBACCOUNTS === 0, | ||
| MAX_PARENT_SUBACCOUNTS === 0, | ||
| ) | ||
| .map((subaccount) => subaccount.id); | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🩺 Stability & Availability | 🔴 Critical
🧩 Analysis chain
🏁 Script executed:
Repository: dydxprotocol/v4-chain
Length of output: 50378
🏁 Script executed:
Repository: dydxprotocol/v4-chain
Length of output: 9236
🏁 Script executed:
Repository: dydxprotocol/v4-chain
Length of output: 1392
🏁 Script executed:
Repository: dydxprotocol/v4-chain
Length of output: 9162
🏁 Script executed:
Repository: dydxprotocol/v4-chain
Length of output: 2120
🏁 Script executed:
Repository: dydxprotocol/v4-chain
Length of output: 1926
Transaction started on primary connection conflicts with caller's read-replica routing preference.
Line 111 unconditionally starts a transaction on the primary connection using
Transaction.start(), which callsModel.startTransaction()(bound globally toknexPrimary). When callers pass{ readReplica: true }in options (as seen atindexer/services/roundtable/src/helpers/pnl-ticks-helper.ts:73), the resultingtxOptionscontains bothtxId(primary transaction) andreadReplica: true.In
rawQuery(), this creates a routing mismatch: the connection is selected as the read replica, but the transaction object is bound to the primary. This prevents proper transaction isolation on the replica or forces the query to the primary despite the caller's read-replica intent.Either:
readReplica: truewhen a transaction ID is provided, orreadReplica: trueis passed and notxIdis already present.🤖 Prompt for AI Agents