Skip to content

Conversation

FriesischScott
Copy link
Contributor

As discussed in #234 the goal of this PR is to drop the separate balance tables for users and groups and instead compute the balances from all expenses in the database.

I agree with @krokosik that we need to be certain we can handle the required loads for large instances with lots of expenses but I think postgresql will be up for the task.

This is a list of the required balances:

  1. Friend balances
  2. Overall group balances for one user
  3. All balances for a specific group

here I've implemented the second and third and I think @krokosik has started working on the first.

Because prisma can't handle the necessary computations through it's javascript interface I've chosen to use typedSQL to write native SQL queries and still benifit from prisma's typing capabilities.

One minor downside is that typedSQL currently requires a live database connection to generate the types. However with prisma 7 the location of the generated client will move out of node_modules and we will have to set an output directory like this.

We could then commit the generated sql types in the sql subfolder of the prisma client and only generate the regular client during build. This should already be possible now (only in prisma 7 it's mandatory) and I'm working on setting it up.

@guenzd
Copy link

guenzd commented Jun 9, 2025

I get this error when I try to run with existing data:
grafik

@FriesischScott
Copy link
Contributor Author

I get this error when I try to run with existing data: grafik

Yes, I haven't finished fixing a few type issues. You should be able to run it with pnpm dev but build is not yet working.

@guenzd
Copy link

guenzd commented Jun 9, 2025

I get this error when I try to run with existing data: grafik

Yes, I haven't finished fixing a few type issues. You should be able to run it with pnpm dev but build is not yet working.

Then there are more runtime issues :/

grafik

@FriesischScott
Copy link
Contributor Author

I get this error when I try to run with existing data: grafik

Yes, I haven't finished fixing a few type issues. You should be able to run it with pnpm dev but build is not yet working.

Then there are more runtime issues :/
grafik

Can you try again now?

Turns out I wasn't up to date on the migrations and for some reason when the column is bigint the sum is returned as numeric. I'm now casting explicitly to bigint.

@FriesischScott
Copy link
Contributor Author

Not sure why the linting fails when it doesn't for me but otherwise it should now build without errors.

@FriesischScott
Copy link
Contributor Author

I've changed the seed.ts so you can use it check the performance yourself.

I think with roughly 80ms for 100k expenses we are well within the performance we require. I don't believe any group will reach that high.

Should be done with the eventual update to prisma 7.
@FriesischScott
Copy link
Contributor Author

We have lift off 🥳. The path alias was confusing the linter so I've moved the prisma client back into node_modules.

1000 users in 101 groups with 10k expenses each
Type Hash for faster lookup with =
@FriesischScott FriesischScott marked this pull request as ready for review June 18, 2025 12:29
@FriesischScott
Copy link
Contributor Author

FriesischScott commented Jun 18, 2025

I've updated the seed file to create 1000 users and 101 groups with 10k expenses each. The largest group has 30 members all the others have 10. It should give us a better understanding of the performance.

It does take quite a while to create all the expenses but it's a temporary change anyway. If we merge we can just revert the file completely.

@krokosik
Copy link
Collaborator

Fantastic work, thank you 🙌
I know I promised to take a swing at it this week, but unfortunately it's not likely to happen due to other obligations :/

@FriesischScott
Copy link
Contributor Author

Fantastic work, thank you 🙌 I know I promised to take a swing at it this week, but unfortunately it's not likely to happen due to other obligations :/

No problem at all. Take your time. Better not to rush things :)

Copy link
Collaborator

@krokosik krokosik left a comment

Choose a reason for hiding this comment

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

Very sorry for taking so long with the review. I had to take care of other stuff as well as more pressing current issues. I requested some changes that would minimize this PR further as I believe such risky changes should be as easy to review as possible. I ran some initial tests on my machine and it is looking good, so I would like to proceed with this, but only after they have been resolved and the branch gets rebased onto main. Furthermore, if you could give me write access to your fork, I will implement friend balances as well :)

Copy link
Collaborator

Choose a reason for hiding this comment

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

The original seed script is useful for local dev work, while this one takes a long time to execute. Please move it to a separate file

Copy link
Contributor Author

Choose a reason for hiding this comment

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

My plan was to only use this as and intermediate script and revert to the original before we merge. If you think it's worth keeping around I'll move it to a separate script.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Can you apply some SQL formatting to these queries?

Comment on lines +11 to +15
const sortByIds = (a: getAllBalancesForGroup.Result, b: getAllBalancesForGroup.Result) => {
if (a.paidBy === b.paidBy) {
return a.borrowedBy - b.borrowedBy;
}
return a.userId - b.userId;
return a.paidBy - b.paidBy;
Copy link
Collaborator

Choose a reason for hiding this comment

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

I would prefer to keep this PR as minimal as possible. As such, please keep the property names identical and create a type GroupBalance = getAllBalancesForGroup.Result alias

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Just to confirm, you want me to keep the existing firendId including the typo?

Comment on lines +60 to +81
const groupBalances = await ctx.db.$queryRawTyped(getGroupsWithBalances(ctx.session.user.id));

const _groups = groupBalances
.map((b) => {
return {
id: b.id,
name: b.name,
};
})
.filter((obj, index, self) => index === self.findIndex((t) => t.id === obj.id));

const groupsWithBalances = _groups.map((group) => {
const balancesForGroup: Record<string, bigint> = {};
groupBalances
.filter((b) => {
return b.id == group.id;
})
.forEach((b) => {
if (b.currency != null && b.balance != null) {
balancesForGroup[b.currency] = b.balance;
}
});
Copy link
Collaborator

Choose a reason for hiding this comment

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

While we remain on the dev branch, I propose the following sanity check:
utilize both the legacy and new method and compare their results. When a mismatch is detected, throw an Error. We will remove it before creating a release.

@krokosik
Copy link
Collaborator

krokosik commented Jul 7, 2025

Oh and one more thing, this change would break the current splitwise import functionality, as it does not import expenses as of now. #207 needs to be implemented first and as such I don't expect to go ahead with this PR for 1.5 release

@FriesischScott
Copy link
Contributor Author

Oh and one more thing, this change would break the current splitwise import functionality, as it does not import expenses as of now. #207 needs to be implemented first and as such I don't expect to go ahead with this PR for 1.5 release

No problem. More time to polish.

@alexanderwassbjer
Copy link
Contributor

alexanderwassbjer commented Aug 5, 2025

This is really good work! Calculating it based on the expenses rather then the balance table is amazing! 👏

@krokosik krokosik changed the title Compute balances in database WIP: Compute balances in database Sep 5, 2025
@krokosik krokosik marked this pull request as draft September 5, 2025 11:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants