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

index: block filters sync, reduce disk read operations by caching last header #28955

Merged
merged 6 commits into from Mar 20, 2024

Conversation

furszy
Copy link
Member

@furszy furszy commented Nov 28, 2023

Work decoupled from #26966 per request.

The aim is to remove an unnecessary disk read operation that currently takes place with every new arriving block (or scanned block during background sync). Instead of reading the last filter header from disk merely to access its hash for constructing the next filter, this work caches it, occupying just 32 more bytes in memory.

Also, reduces cs_main lock contention during the index initial sync process. And, simplifies the indexes initialization and shutdown procedure.

Testing Note:
To compare the changes, added a pretty basic benchmark in the second commit. Alternatively, could also test the changes by timing the block filter sync from scratch on any network; start the node with -blockfilterindex and monitor the logs until the syncing process finish.

Local Benchmark Results:

*Master (c252a0f):

ns/op op/s err% total benchmark
132,042,516.60 7.57 0.3% 7.79 BlockFilterIndexSync

*PR (43a212c):

ns/op op/s err% total benchmark
126,915,841.60 7.88 0.6% 7.51 BlockFilterIndexSync

@DrahtBot
Copy link
Contributor

DrahtBot commented Nov 28, 2023

The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.

Code Coverage

For detailed information about the code coverage, see the test coverage report.

Reviews

See the guideline for information on the review process.

Type Reviewers
ACK Sjors, TheCharlatan, andrewtoth, achow101
Concept ACK brunoerg

If your review is incorrectly listed, please react with 👎 to this comment and the bot will ignore it on the next update.

Conflicts

Reviewers, this pull request conflicts with the following ones:

  • #29432 (Stratum v2 Template Provider (take 3) by Sjors)
  • #24539 (Add a "tx output spender" index by sstone)
  • #24230 (indexes: Stop using node internal types and locking cs_main, improve sync logic by ryanofsky)

If you consider this pull request important, please also help to review the conflicting pull requests. Ideally, start with the one that should be merged first.

@Sjors
Copy link
Member

Sjors commented Nov 28, 2023

Ran bench on MacBook Pro 2019 (2,3 GHz 8-Core Intel Core i9, SSD):

src/bench/bench_bitcoin -filter=BlockFilterIndexSync

Before (486c71b):

ns/op op/s err% total benchmark
336,034,835.00 2.98 0.6% 19.86 BlockFilterIndexSync

After:

ns/op op/s err% total benchmark
339,403,771.80 2.95 0.6% 19.98 BlockFilterIndexSync

Not seeing any improvement in the bench. Haven't tried running the full indexer though.

@furszy
Copy link
Member Author

furszy commented Nov 28, 2023

Ran bench on MacBook Pro 2019 (2,3 GHz 8-Core Intel Core i9, SSD):

src/bench/bench_bitcoin -filter=BlockFilterIndexSync

Before (486c71b):

ns/op op/s err% total benchmark
336,034,835.00 2.98 0.6% 19.86 BlockFilterIndexSync
After:

ns/op op/s err% total benchmark
339,403,771.80 2.95 0.6% 19.98 BlockFilterIndexSync
Not seeing any improvement in the bench. Haven't tried running the full indexer though.

Probably, it is because the benchmarks, same as the unit tests, create temp directories. Which may be faster to access than regular directories. See #26684 (review).
I should verify if the bench framework accepts custom paths so we can test this differently. Most likely, it doesn't and we should add the feature.

Also, you could increase the CHAIN_SIZE variable inside the benchmark. Go from 600, to 3000 or more. The time should grow up linearly. I didn't do it merely because the bench would take longer to setup but the difference should be evident with a longer chain.

Copy link
Contributor

@TheCharlatan TheCharlatan left a comment

Choose a reason for hiding this comment

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

ACK 63ef83d

src/bench/index_blockfilter.cpp Show resolved Hide resolved
src/bench/index_blockfilter.cpp Show resolved Hide resolved
src/index/base.cpp Outdated Show resolved Hide resolved
@furszy furszy force-pushed the 2023_index_blockfilter_cache_header branch from 63ef83d to b19585e Compare November 30, 2023 02:04
Copy link
Contributor

@TheCharlatan TheCharlatan left a comment

Choose a reason for hiding this comment

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

Re-ACK b19585e

@luke-jr
Copy link
Member

luke-jr commented Dec 5, 2023

Probably, it is because the benchmarks, same as the unit tests, create temp directories. Which may be faster to access than regular directories.

Is it, though? I would think the OS should have this cached regardless?

Copy link
Member Author

@furszy furszy left a comment

Choose a reason for hiding this comment

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

Probably, it is because the benchmarks, same as the unit tests, create temp directories. Which may be faster to access than regular directories.

Is it, though? I would think the OS should have this cached regardless?

We will know it soon. I'm working on a way to test it inside #26564 (review).

But, it seems to be an orthogonal topic. Regardless of the result, which could vary depending on the OS, the changes in this PR should be good to go as is.

Conceptually, the block filter index synchronization process makes a db call on every new block (forever) just to get the tip's header hash when it could just cache those 32 bytes.

Copy link
Member Author

@furszy furszy left a comment

Choose a reason for hiding this comment

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

Finished testing this on a custom directory. Results are favorable @Sjors and @luke-jr.
I have observed that access to the OS temp directory is faster than accessing regular directories locally. Benchmark outputs are provided at the end.

The testing methodology is as following:

Running ./bench_bitcoin -filter="BlockFilterIndexSync" -testdatadir=<custom_test_datadir_path> on any of the following branches:

Branch 1 -> Which includes: this PR changes + #26564 + a commit that introduces the custom datadir feature for the benchmark framework.

Branch 2 -> Which includes: the block filter index benchmark + #26564 + a commit that introduces the custom datadir feature for the benchmark framework.

The results were:

For Branch 1

ns/op op/s err% total benchmark
117,648,576.33 8.50 0.5% 6.96 BlockFilterIndexSync

For Branch 2

ns/op op/s err% total benchmark
121,370,493.17 8.24 0.5% 7.16 BlockFilterIndexSync

Copy link
Contributor

@andrewtoth andrewtoth left a comment

Choose a reason for hiding this comment

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

This patch seems to be doing more than what is in the description. From my understanding, it is also reducing cs_main lock contention in the renamed Sync method, which is also now made public. The former seems like a simple win, but it's unclear to me why we want the latter?
Also, a refactor to indexes in init.cpp that also seems fine but unrelated?
Maybe the PR description can be updated to describe the motivation behind these other changes?

Copy link
Member Author

@furszy furszy left a comment

Choose a reason for hiding this comment

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

Updated per feedback. Thanks @andrewtoth!

This patch seems to be doing more than what is in the description. From my understanding, it is also reducing cs_main lock contention in the renamed Sync method, which is also now made public. The former seems like a simple win, but it's unclear to me why we want the latter?

The benchmark, introduced in the second commit, makes use of it. If BaseIndex::Sync() is not public, the benchmark would need to call BaseIndex::StartBackgroundSync(), who wraps BaseIndex::Sync() on a separate thread, which goes against the objective of the benchmark (we want to bench the process, not the time it takes the OS to create, wait-for and destroy a thread).

Also, it facilitates the future decoupling of the index inner thread in #26966. Replacing it for a thread-pool class provided by the caller.

Also, a refactor to indexes in init.cpp that also seems fine but unrelated?
Maybe the PR description can be updated to describe the motivation behind these other changes?

Sure. This PR improves the index sources. And, while the changes simplify the code, to me, pushing a PR just to do such small code refactoring does not seem worth the efforts (from reviewers and from myself).
Will update the description. Thanks!

Copy link
Contributor

@andrewtoth andrewtoth left a comment

Choose a reason for hiding this comment

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

Concept ACK

I've ran the benchmark locally on this PR and the branches you mention in #28955 (review), and it is sometimes better on one branch then the other for both tests. The results you show are very small improvements which I think could just be attributed to noise.

Regardless of the result, which could vary depending on the OS, the changes in this PR should be good to go as is.

I agree, except for the first 2 commits. The benchmark as I mentioned doesn't seem particularly useful, and the Sync method being made public should then only be done in a patch that will take advantage of it's new accessibility.

@@ -215,10 +224,25 @@ size_t BlockFilterIndex::WriteFilterToDisk(FlatFilePos& pos, const BlockFilter&
return data_size;
}

std::optional<uint256> BlockFilterIndex::ReadHeader(int height, const uint256& expected_block_hash)
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: could be const function.

}

BlockFilter filter(m_filter_type, *Assert(block.data), block_undo);

const uint256& header = filter.ComputeHeader(last_header);
bool res = Write(filter, block.height, header);
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: const bool.

@Sjors
Copy link
Member

Sjors commented Feb 23, 2024

Concept ACK

I synced the block filter from scratch at c3e2915 (rebased on master) and then on the last commit. AMD Ryzen 7950x with SSD drive running Ubuntu 23.10:

  • before: 5 hours 35 minutes
  • after: 5 hours 46 minutes

So not much difference, which seems fine with the goal of #26966 in mind - which does make a dramatic difference.

I haven't measured the performance impact on syncing the index during IBD; in light of 5d5e22b that might be more significant because there's more going on in cs_main.

Can you add some rationale to the b19585e commit message as to what was preventing this simplification before?

@andrewtoth wrote:

The benchmark as I mentioned doesn't seem particularly useful,

Benchmarks are also useful to prevent future regressions.

src/index/blockfilterindex.h Outdated Show resolved Hide resolved
src/index/blockfilterindex.cpp Outdated Show resolved Hide resolved
src/index/blockfilterindex.h Outdated Show resolved Hide resolved
@furszy furszy force-pushed the 2023_index_blockfilter_cache_header branch 2 times, most recently from e3d3763 to 08d8608 Compare February 23, 2024 19:47
@furszy
Copy link
Member Author

furszy commented Feb 23, 2024

Updated per feedback. Thanks both.

Can you add some rationale to the b19585e commit message as to what was preventing this simplification before?

Nothing was preventing it.

The benchmark as I mentioned doesn't seem particularly useful

Have you tried it on a spinning disk? The diff should be noticeable there. We need to avoid disk writes/reads where is possible. See #28037 discussion as a good example of it.

the Sync method being made public should then only be done in a patch that will take advantage of its new accessibility.

Have merged the first two commits.

@Sjors
Copy link
Member

Sjors commented Feb 24, 2024

tACK 08d8608

@furszy furszy force-pushed the 2023_index_blockfilter_cache_header branch from 08d8608 to 1cf73a3 Compare March 12, 2024 12:36
@furszy
Copy link
Member Author

furszy commented Mar 12, 2024

Rebased due a one-line conflict with #29236.

@Sjors
Copy link
Member

Sjors commented Mar 12, 2024

Tidy complains about

index/blockfilterindex.cpp:135:22: error: Unterminated format string used with LogPrintf [bitcoin-unterminated-logprintf,-warnings-as-errors]
  135 |             LogError("Cannot read last block filter header; index may be corrupted");
      |                      ^                                                            
      |                                                                                   \n

Avoid disk read operations on every new processed block.
Only NextSyncBlock requires cs_main lock. The
other function calls like Commit or Rewind will
lock or not cs_main internally when they need it.

Avoiding keeping cs_main locked when Commit() or
Rewind() write data to disk.
@furszy furszy force-pushed the 2023_index_blockfilter_cache_header branch from 1cf73a3 to 99afb9d Compare March 12, 2024 14:55
@Sjors
Copy link
Member

Sjors commented Mar 12, 2024

re-ACK 99afb9d

Copy link
Contributor

@TheCharlatan TheCharlatan left a comment

Choose a reason for hiding this comment

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

Re-ACK 99afb9d

Copy link
Contributor

@andrewtoth andrewtoth left a comment

Choose a reason for hiding this comment

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

ACK 99afb9d

@brunoerg
Copy link
Contributor

Concept ACK

@achow101
Copy link
Member

ACK 99afb9d

@DrahtBot DrahtBot requested a review from brunoerg March 20, 2024 16:19
@achow101 achow101 self-assigned this Mar 20, 2024
@achow101 achow101 merged commit 0b96a19 into bitcoin:master Mar 20, 2024
16 checks passed
@furszy furszy deleted the 2023_index_blockfilter_cache_header branch March 20, 2024 19:38
@maflcko
Copy link
Member

maflcko commented Apr 15, 2024

Follow-up in #29867 (comment)

@@ -159,23 +159,20 @@ void BaseIndex::Sync()
return;
}

{
LOCK(cs_main);
Copy link
Contributor

Choose a reason for hiding this comment

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

In commit "index: decrease ThreadSync cs_main contention" (0faafb5)

Note: This commit introduces a race condition, because it is no longer locking cs_main while calling NextSyncBlock and setting m_synced = true. As a result, a new block could be connected by another thread after NextSyncBlock returns null in this thread, but before m_synced is set to true, so the block will never be indexed because BlockConnected notifications are ignored while m_synced is false. This should be fixed in #29867

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

10 participants