-
Notifications
You must be signed in to change notification settings - Fork 89
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
feat(backend): improve PSQL balance calculation #2881
Conversation
✅ Deploy Preview for brilliant-pasca-3e80ec canceled.
|
deps.logger.error( | ||
{ | ||
err, | ||
accountId: account.id | ||
}, | ||
'Could not fetch balances for account' | ||
) |
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.
not doing much with the error here (not expecting it to happen), just logging the accountId we were trying to query
(state === LedgerTransferState.PENDING | ||
? new Date(Date.now() + 86_400_000) | ||
: undefined), |
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.
this is only used for tests, we just want to avoid breaking the newly added constraint above
@@ -16,38 +14,45 @@ export async function getAccountBalances( | |||
account: LedgerAccount, | |||
trx?: TransactionOrKnex | |||
): Promise<AccountBalance> { | |||
const { credits, debits } = await getAccountTransfers( |
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.
We can tackle this in another issue but can we get rid of getAccountTransfers
now? I think it's only exposed in a recently added gql resolver. That method still brings the records into code and iterate over all of them (so like half of the total problem you fixed here). So I would still expect performance issues as transfer count grows where its used (?).
IDK if we want to:
A) leave it as-is because we dont think the performance issue will matter much. It's possible. I dont think the use-case for the resolver is well defined. I guess its just so people can see each transfer from the admin ui (not currently implemented and not sure what they would do with that info).
B) Remove it
C) Also rework that query. Not exactly sure how much we would leverage this one since its aggregating it a bit differently.
I guess I would say B or C but im not sure its worth the trouble of refactoring if we dont have a clear use case for consuming it, so leaning B.
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.
We can tackle this in another issue but can we get rid of getAccountTransfers now? I think it's only exposed in a recently added gql resolver
Yes, I think the gql is worth keeping and since its used more from dev/admin point of view and not something getting called every trx, its like you said, not a big deal, and we can leave as is for now.
Adding pagination (so we grab only a limited set of transactions) would be a good first step in improving it IMO (also just having a default low limit is not bad as well)
exports.up = function (knex) { | ||
return knex.schema.table('ledgerTransfers', function (table) { | ||
table.check( | ||
`("state" != 'PENDING') OR ("expiresAt" IS NOT NULL)`, |
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.
Does the new query error if some record doesnt satisfy the constraint (hence why you added it)? Just wondering if we can guaruntee all existing records will pass this constraint... migration will fail otherwise. Im also wondering if the error is intelligible if we try to add one that doesnt pass this constraint. Not really sure where that would happen or how it should surface...
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.
Checking rafiki money would be a good test, but I think it's not actually something that should have ever happened, this is how we map all transfers before creating them:
rafiki/packages/backend/src/accounting/psql/ledger-transfer/index.ts
Lines 326 to 342 in 750f414
function prepareTransfer( | |
transfer: CreateLedgerTransferArgs | |
): Partial<LedgerTransfer> { | |
return { | |
amount: transfer.amount, | |
transferRef: transfer.transferRef, | |
creditAccountId: transfer.creditAccount.id, | |
debitAccountId: transfer.debitAccount.id, | |
ledger: transfer.creditAccount.ledger, | |
state: transfer.timeoutMs | |
? LedgerTransferState.PENDING | |
: LedgerTransferState.POSTED, | |
expiresAt: transfer.timeoutMs | |
? new Date(Date.now() + Number(transfer.timeoutMs)) | |
: undefined, | |
type: transfer.type | |
} |
i.e. if the timeout is provided, we set PENDING with expiryAt always
In terms of error legibility, I think it should be properly serviced up when we create the transfers in the psql transaction.
I also think this is mostly something to protect against refactoring code - this would be more a dev error than anything
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.
rafiki.money db:
rafiki_backend=# select count(*) from "ledgerTransfers" where state = 'PENDING' and "expiresAt" is null;
count
-------
0
(1 row)
} else if (credit.state === LedgerTransferState.PENDING) { | ||
creditsPending += credit.amount | ||
if (queryResult?.rows < 1) { | ||
throw new Error('No results when fetching balance for account') |
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.
I was always wondered how do we decide if we should throw error, or just return empty response (in case of a balance, 0)?
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.
I think your question still stands but I played around with it because I wasnt sure how it would work and wanted to add some context. When there are no records with the associated id it still aggregates a result, its just 0 for all the balances. So I think like Max mentioned in his other comment, this could probably never happen. But yeah I think it still begs the question, do we need to error or can we just return 0 balances (logging for sure either way).
I guess since it's totally unexpected I'm not sure indicating the balances are 0 would be correct. Not entirely sure how this is consumed but it might send the wrong signal.
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.
This check is if results of the query weren't able to be returned at all - so missing information unexpectedly, a result of a psql issue. So in that case we should throw and stop everything IMO
Changes proposed in this pull request
Running the perf test script with PSQL DB (graph showing transaction counts):
Before:
After:
Context
Fixes #2879
Checklist
fixes #number