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

multi: introduce a batch filter writer #274

Merged
merged 6 commits into from
Jul 25, 2023

Conversation

ellemouton
Copy link
Member

@ellemouton ellemouton commented May 5, 2023

This PR adds a generic BatchWriter which can be used to batch write items to a db.
It persists the batch once the size reaches a certain threshold or if a ticker goes off
if at least one items is in the batch.

This BatchWriter is then used by the GetCFilter method to persist filters that it
gets during a query. This will improve the speed of the query since the BatchWriter is
non-blocking.

Performance Improvement

On current master branch, syncing 3200 filters on my local regtest network takes 4m25s
With this PR, syncing the same number of filters takes 4.2s

LND CI

LND CI with this PR is being tested here: lightningnetwork/lnd#7788

filterdb/db.go Outdated Show resolved Hide resolved
chanutils/batch_writer.go Show resolved Hide resolved
chanutils/batch_writer.go Show resolved Hide resolved
query.go Show resolved Hide resolved
Copy link
Contributor

@yyforyongyu yyforyongyu left a comment

Choose a reason for hiding this comment

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

Very cool optimization⚡️ Left some comments on the usage of Batch and questions about the method manageNewItems.

filterdb/db.go Outdated Show resolved Hide resolved
chanutils/queue.go Show resolved Hide resolved
chanutils/batch_writer.go Outdated Show resolved Hide resolved
chanutils/batch_writer.go Outdated Show resolved Hide resolved
chanutils/batch_writer.go Show resolved Hide resolved
chanutils/batch_writer.go Show resolved Hide resolved
chanutils/batch_writer.go Outdated Show resolved Hide resolved
chanutils/batch_writer.go Show resolved Hide resolved
chanutils/batch_writer.go Outdated Show resolved Hide resolved
Copy link

@positiveblue positiveblue left a comment

Choose a reason for hiding this comment

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

from 4m25s to 4.2s 🔥

Those are amazing improvements Elle 🎉

It looks like we can be optimistic when we fetch the filters and assume "eventually they will be persisted". Worst case scenario we will ask for them again, but if we use sensitive default for flushing to disk it should not be a problem.

My main concern is about the concurrent queue. Isn't a simple struct with a slice + a lock enough for this?

filterdb/db.go Outdated
var targetBucket walletdb.ReadWriteBucket
switch filterData.Type {
case RegularFilter:
targetBucket = filters.NestedReadWriteBucket(

Choose a reason for hiding this comment

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

nit: instead of calling NestedReadWriteBucket in every iteration we can call it once before the loop and change targetBucket in the switch statement. Now we have only two optio: regBucket or error but this would work even if we add more in the future

Copy link
Member Author

Choose a reason for hiding this comment

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

ah yes, great idea - updated 👍

@ellemouton ellemouton force-pushed the batchFilterWriter branch 2 times, most recently from 972afbd to 9a991c7 Compare May 31, 2023 14:31
Copy link
Member Author

@ellemouton ellemouton left a comment

Choose a reason for hiding this comment

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

Thanks for the review everyone! 🙏

You guys were completely right about just using a slice for the batch writer :) I was too trigger happy with the channel 😂

Updated accordingly!

It looks like we can be optimistic when we fetch the filters and assume "eventually they will be persisted". Worst case scenario we will ask for them again

Yes exactly :)

filterdb/db.go Outdated Show resolved Hide resolved
filterdb/db.go Outdated
var targetBucket walletdb.ReadWriteBucket
switch filterData.Type {
case RegularFilter:
targetBucket = filters.NestedReadWriteBucket(
Copy link
Member Author

Choose a reason for hiding this comment

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

ah yes, great idea - updated 👍

chanutils/queue.go Show resolved Hide resolved
chanutils/batch_writer.go Show resolved Hide resolved
chanutils/batch_writer.go Outdated Show resolved Hide resolved
chanutils/batch_writer.go Show resolved Hide resolved
chanutils/batch_writer.go Outdated Show resolved Hide resolved
chanutils/batch_writer.go Show resolved Hide resolved
@Chinwendu20
Copy link
Contributor

Hello @ellemouton may I please ask how you timed this?

@ellemouton
Copy link
Member Author

@Chinwendu20 - I set up a regtest node & mined a bunch of blocks. Then I connected to it with that script i sent you in slack the other day and would time how long the rescan of those blocks would take. Note that for header syncing (which is what I believe you are working on), you would just want to time that part & not the rescan.

Lint the files and update the tests to use the `require` package.
Add a new FilterData type that encapsulates the filter, blockhash and
type associated with a filter. This is done in order to prepare for a
commit that will enable batch writes of these filters.
@ellemouton
Copy link
Member Author

@yyforyongyu & @positiveblue - this is ready! The dependant PR has been merged. PTAL 🙏

@lightninglabs-deploy
Copy link

@Roasbeef: review reminder
@positiveblue: review reminder
@yyforyongyu: review reminder

Copy link
Member

@Roasbeef Roasbeef left a comment

Choose a reason for hiding this comment

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

LGTM 🏐

One small suggestion to use list.List[T]

filterdb/db.go Show resolved Hide resolved

chanIn chan T
chanOut chan T
overflow *list.List
Copy link
Member

Choose a reason for hiding this comment

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

Can use this generic list to inherit type safety (or sort of push down the concerns to another layer):

// NewList returns an initialized list.
func NewList[V any]() *List[V] { return new(List[V]).Init() }

Copy link
Member Author

Choose a reason for hiding this comment

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

oh sweet - that is perfect. Updated!

chanutils/batch_writer.go Show resolved Hide resolved
Copy link
Member Author

@ellemouton ellemouton left a comment

Choose a reason for hiding this comment

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

Thanks @Roasbeef! updated. Also updated the LND PR pointing to this 👍


chanIn chan T
chanOut chan T
overflow *list.List
Copy link
Member Author

Choose a reason for hiding this comment

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

oh sweet - that is perfect. Updated!

Copy link
Contributor

@yyforyongyu yyforyongyu left a comment

Choose a reason for hiding this comment

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

LGTM🥳 and def impressive optimization😻

filterdb/db.go Show resolved Hide resolved
chanutils/queue.go Show resolved Hide resolved
chanutils/batch_writer.go Outdated Show resolved Hide resolved

// Stop the ticker since we don't want it to tick unless there is at
// least one item in the queue.
ticker.Stop()
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: there's a near-impossible case, that if the DBWritesTickerDuration is extremely small, we may end up having a tick already inside ticker, causing an immediate write - no big deal here. But in a hindsight it looks like the lnd/ticker package is more suitable for this case.

Copy link
Member Author

Choose a reason for hiding this comment

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

cool yeah im happy to update to the lnd ticker.

The only reason not to would be for performance reasons: this ticker stops and starts the same one the whole time while the LND one keeps creating new ones & discarding old ones.
So maybe makes sense to keep as is? Agreed re the possible immediate write but also agree that it is not a big deal 👍

lemme know :)

Copy link
Member Author

Choose a reason for hiding this comment

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

also doesnt really make sense to make the DBWritesTickerDuration too small cause then the batching benefits are lost

Copy link
Contributor

Choose a reason for hiding this comment

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

yeah i think it's fine to leave it as-is, in the future we can add some config sanity check to make sure it won't happen - or let it happen, worse case we get an immediate call to the batch write.

chanutils/batch_writer_test.go Show resolved Hide resolved
Copy link
Member Author

@ellemouton ellemouton left a comment

Choose a reason for hiding this comment

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

Thanks @yyforyongyu !

chanutils/queue.go Show resolved Hide resolved
chanutils/batch_writer.go Outdated Show resolved Hide resolved

// Stop the ticker since we don't want it to tick unless there is at
// least one item in the queue.
ticker.Stop()
Copy link
Member Author

Choose a reason for hiding this comment

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

cool yeah im happy to update to the lnd ticker.

The only reason not to would be for performance reasons: this ticker stops and starts the same one the whole time while the LND one keeps creating new ones & discarding old ones.
So maybe makes sense to keep as is? Agreed re the possible immediate write but also agree that it is not a big deal 👍

lemme know :)


// Stop the ticker since we don't want it to tick unless there is at
// least one item in the queue.
ticker.Stop()
Copy link
Member Author

Choose a reason for hiding this comment

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

also doesnt really make sense to make the DBWritesTickerDuration too small cause then the batching benefits are lost

Copy link
Contributor

@yyforyongyu yyforyongyu left a comment

Choose a reason for hiding this comment

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

LGTM⛵️

select {
case item, ok := <-cq.chanIn:
if !ok {
log.Warnf("ConcurrentQueue " +
Copy link
Contributor

Choose a reason for hiding this comment

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

🙏

@Roasbeef Roasbeef merged commit f8b6f23 into lightninglabs:master Jul 25, 2023
4 checks passed
@ellemouton ellemouton deleted the batchFilterWriter branch July 25, 2023 18:13
@Chinwendu20
Copy link
Contributor

Chinwendu20 commented Jul 31, 2023

It seems like this change has not been reflected in btcwallet. I just rebased my branch and I could not run lnd with this new change because of this line of code in btcwallet @ellemouton
https://github.com/btcsuite/btcwallet/blob/23eda3cadf717dbf79d191873fc524178492aef9/chain/pruned_block_dispatcher.go#L194

I want to know if there is any way I can bypass this or if I am doing anything wrong. Thank you.

@ellemouton
Copy link
Member Author

@Chinwendu20 see btcsuite/btcwallet#884 and lightningnetwork/lnd#7788

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.

6 participants