Skip to content

Conversation

andrewtoth
Copy link
Contributor

@andrewtoth andrewtoth commented Oct 13, 2022

Picking up from #21006.

After commit ccd8ef6 it is no longer required to hold cs_main when calling ReadBlockFromDisk. This can be verified in master at https://github.com/bitcoin/bitcoin/blob/master/src/node/blockstorage.cpp#L755. Same can be seen for UndoReadFromDisk https://github.com/bitcoin/bitcoin/blob/master/src/node/blockstorage.cpp#L485.

The first commit moves ReadBlockFromDisk outside the lock scope in rest_block, where we can see a huge performance improvement when fetching blocks with multiple threads.

My test setup, on an Intel i7 with 8 cores (16 threads):

  1. Start a fully synced bitcoind, with this bitcoin.conf:
    rest=1
    rpcthreads=16
    rpcworkqueue=64
    rpcuser=user
    rpcpassword=password
  1. Run ApacheBench: 10000 requests, 16 parallel threads, fetching block nr. 750000 in binary:
    ab -n 10000 -c 16 "http://127.0.0.1:8332/rest/block/0000000000000000000592a974b1b9f087cb77628bb4a097d5c2c11b3476a58e.bin"

Time per request (mean)
183 ms on master
30 ms this branch

So this can process 6.1 times as many requests, and saturates all the cores instead of keeping them partly idle waiting in the lock. With 8 threads the mean times were 90 ms on master and 19 ms on this branch, a speedup of 4.7x.

Big thanks to martinus for finding this and the original PR.

The second commit is from a suggestion on the original PR by jonatack to remove the unnecessary LOCK(cs_main) in the zmq notifier's NotifyBlock.

I also found that this approach could be applied to rpcs getblock (including verbosity=3), getblockstats, and gettxoutproof with similar very good results. The above benchmarks steps need to be modified slightly for RPC. Run the following ApacheBench command with different request data in a file named data.json:

ab -p data.json -n 10000 -c 16 -A user:password "http://127.0.0.1:8332/"

For getblock, use the following in data.json:

{"jsonrpc": "1.0", "id": "curltest", "method": "getblock", "params": ["0000000000000000000592a974b1b9f087cb77628bb4a097d5c2c11b3476a58e"]}

master - 184 ms mean request time
branch - 28 ms mean request time

For getblock with verbosity level 3, use the following in data.json:

{"jsonrpc": "1.0", "id": "curltest", "method": "getblock", "params": ["0000000000000000000592a974b1b9f087cb77628bb4a097d5c2c11b3476a58e", 3]}

This verbosity level fetches an undo file from disk, so it benefits from this approach as well. However, a lot of time is spent serializing to JSON so the performance gain is not as severe.
master - 818 ms mean request time
branch - 505 ms mean request time

For getblockstats, use the following in data.json:

{"jsonrpc": "1.0", "id": "curltest", "method": "getblockstats", "params": ["0000000000000000000592a974b1b9f087cb77628bb4a097d5c2c11b3476a58e", ["minfeerate","avgfeerate"]]}

This request used a lock on reading both a block and undo file, so the results are very good.
master - 244 ms mean request time
branch - 28 ms mean request time

Copy link
Contributor

@aureleoules aureleoules left a comment

Choose a reason for hiding this comment

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

ACK 77de778

Master results
hey -n 1000 -c 16 "http://127.0.0.1:8332/rest/block/0000000000000000000592a974b1b9f087cb77628bb4a097d5c2c11b3476a58e.bin"

Summary:
  Total:	16.1675 secs
  Slowest:	0.5310 secs
  Fastest:	0.0372 secs
  Average:	0.2569 secs
  Requests/sec:	61.3578
  
  Total data:	1285175680 bytes
  Size/request:	1295540 bytes

Response time histogram:
  0.037 [1]	|
  0.087 [6]	|
  0.136 [6]	|
  0.185 [7]	|
  0.235 [168]	|■■■■■■■■■■
  0.284 [685]	|■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
  0.333 [92]	|■■■■■
  0.383 [3]	|
  0.432 [10]	|■
  0.482 [13]	|■
  0.531 [1]	|


Latency distribution:
  10% in 0.2290 secs
  25% in 0.2390 secs
  50% in 0.2520 secs
  75% in 0.2671 secs
  90% in 0.2880 secs
  95% in 0.3055 secs
  99% in 0.4610 secs

Details (average, fastest, slowest):
  DNS+dialup:	0.0000 secs, 0.0372 secs, 0.5310 secs
  DNS-lookup:	0.0000 secs, 0.0000 secs, 0.0000 secs
  req write:	0.0000 secs, 0.0000 secs, 0.0003 secs
  resp wait:	0.2172 secs, 0.0197 secs, 0.4897 secs
  resp read:	0.0396 secs, 0.0005 secs, 0.0429 secs

Status code distribution:
  [200]	992 responses
PR results
hey -n 1000 -c 16 "http://127.0.0.1:8332/rest/block/0000000000000000000592a974b1b9f087cb77628bb4a097d5c2c11b3476a58e.bin"

Summary:
  Total:	5.5089 secs
  Slowest:	0.3319 secs
  Fastest:	0.0221 secs
  Average:	0.0856 secs
  Requests/sec:	180.0725
  
  Total data:	1285175680 bytes
  Size/request:	1295540 bytes

Response time histogram:
  0.022 [1]	|
  0.053 [109]	|■■■■■■■■■■
  0.084 [397]	|■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
  0.115 [420]	|■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
  0.146 [41]	|■■■■
  0.177 [3]	|
  0.208 [4]	|
  0.239 [1]	|
  0.270 [7]	|■
  0.301 [3]	|
  0.332 [6]	|■


Latency distribution:
  10% in 0.0508 secs
  25% in 0.0690 secs
  50% in 0.0831 secs
  75% in 0.0979 secs
  90% in 0.1090 secs
  95% in 0.1220 secs
  99% in 0.2890 secs

Details (average, fastest, slowest):
  DNS+dialup:	0.0000 secs, 0.0221 secs, 0.3319 secs
  DNS-lookup:	0.0000 secs, 0.0000 secs, 0.0000 secs
  req write:	0.0000 secs, 0.0000 secs, 0.0004 secs
  resp wait:	0.0486 secs, 0.0126 secs, 0.2870 secs
  resp read:	0.0369 secs, 0.0004 secs, 0.0731 secs

Status code distribution:
  [200]	992 responses

The results are consistent. The speed improvement seems to be seen only with large blocks, which is great for mainnet. I didn't notice any gains on testnet since blocks are almost empty.

@andrewtoth
Copy link
Contributor Author

andrewtoth commented Oct 14, 2022

After reading this comment #17161 (comment) it appears it is unsafe to call ReadBlockFromDisk without holding cs_main. The block could be removed while the file is open which can cause issues on Windows.

I proposed a fix in #26316. That or another fix should be merged first before this would be safe for use with pruning I believe.

@andrewtoth andrewtoth marked this pull request as draft October 15, 2022 00:39
@andrewtoth andrewtoth changed the title rest: reduce LOCK(cs_main) scope in rest_block: ~6 times as many requests per second rpc/rest/zmq: reduce LOCK(cs_main) scope: ~6 times as many requests per second Oct 17, 2022
@andrewtoth andrewtoth force-pushed the no-lock-for-read-block branch 3 times, most recently from 0f882ec to bfa9663 Compare October 17, 2022 01:27
@DrahtBot
Copy link
Contributor

DrahtBot commented Oct 17, 2022

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

Reviews

See the guideline for information on the review process.

Type Reviewers
ACK MarcoFalke, hebasto
Stale ACK aureleoules, ryanofsky

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:

  • #26415 (rpc,rest: faster getblock and rest_block by reading raw block by andrewtoth)
  • #25232 (rpc: Faster getblock API by AaronDewes)
  • #23507 (Refactor: Improve API design of ScriptToUniv, TxToUniv etc to return the UniValue instead of mutating a parameter/reference by mjdietzx)
  • #21319 (RPC/Blockchain: Optimise getblock for simple disk->hex case by luke-jr)
  • #19888 (rpc, test: Improve getblockstats for unspendables by fjahr)

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.

Copy link
Contributor

@ryanofsky ryanofsky left a comment

Choose a reason for hiding this comment

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

Code review ACK d7f358b, and I think I disagree with (or don't understand) the comment that this change should require #26316. In all of the cases here, the ReadBlock or UndoRead return value is checked so if reading data fails on windows, the RPC and rest requests should still do the right thing.

Or is the concern not that this change would cause RPC and rest calls to do the wrong thing, but that this change would cause unlink calls to fail on windows and result in a wasted disk space? If that is the bug, and I think a more minimal and elegant fix might just be to make the pruning code resilient to files temporarily being unlinkable because they are in-use, and to just retry and delete them later on the next round of pruning.

@andrewtoth
Copy link
Contributor Author

Or is the concern not that this change would cause RPC and rest calls to do the wrong thing, but that this change would cause unlink calls to fail on windows and result in a wasted disk space? If that is the bug, and I think a more minimal and elegant fix might just be to make the pruning code resilient to files temporarily being unlinkable because they are in-use, and to just retry and delete them later on the next round of pruning.

@ryanofsky yes, thank you for the suggestion. I've implemented that in #26533. I think that's a better/less invasive solution than what I have proposed in #26316.

@andrewtoth andrewtoth marked this pull request as ready for review November 18, 2022 16:06
@andrewtoth andrewtoth force-pushed the no-lock-for-read-block branch from d7f358b to 2cd94da Compare December 6, 2022 14:27
@andrewtoth
Copy link
Contributor Author

Rebased

@maflcko
Copy link
Member

maflcko commented Dec 6, 2022

Happy to re-ACK for the nit

review ACK 2cd94da 🔗

Show signature

Signature:

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA512

review ACK 2cd94daf18aa4ce124142d7f41e6a6b627580acb 🔗
-----BEGIN PGP SIGNATURE-----

iQGzBAEBCgAdFiEE+rVPoUahrI9sLGYTzit1aX5ppUgFAlwqrYAACgkQzit1aX5p
pUjc5Av/RSFb/nqSUF7w9OmGQfGhal9C56bH/LMj9shwqYn3Ic9hW/CruW5tWAlJ
Gq97INRqPxt+t9wnsgUYXEahBWkpmfUvNo/0sXJQnwE86fbBm+yWDEZnmeAiYQ/f
wPkXkLSXqcnyrGCaEN5t4lwU7qTAMw0MCqOKCXZJwdMoKEUtvxt+BEGmy9ZlXNtg
Vyu0+aUvjysxdpGjScdDC7GRbjlIAWG10kXs3en3zKEHK4Q4bG/Cv0pmxYxrBhXa
7HROxcpJcl94HcwePF7xr1NstEBkw4Hc+GhjSasw1aJHs84clRyQj7Jg/1Q+Y2Qq
klCiYa6rK3WytxyWzBc3eETCuXLOhugGIFFu6mjOHx5zcmcwGRiRQIunOwOtHb4G
H2izSoJigG1WESg4fyC/4YKemx/lUDgui+4mcqzVqvedgWB1cCFC8xZ+mdJB1qld
7AUPRhrAQPTOAi1+5gg/KQbcX9YIuJADB8onzaU/wP0/guEusTJGvOKHoynOKm8a
QbGZaOzx
=yYXw
-----END PGP SIGNATURE-----

@andrewtoth andrewtoth force-pushed the no-lock-for-read-block branch from 2cd94da to d7f61e7 Compare December 6, 2022 20:07
@andrewtoth
Copy link
Contributor Author

@MarcoFalke addressed the nit.

@maflcko
Copy link
Member

maflcko commented Dec 7, 2022

re-ACK d7f61e7 💫

Show signature

Signature:

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA512

re-ACK d7f61e7d5909da7227c9e34be06ce9eb872ba074 💫
-----BEGIN PGP SIGNATURE-----

iQGzBAEBCgAdFiEE+rVPoUahrI9sLGYTzit1aX5ppUgFAlwqrYAACgkQzit1aX5p
pUh4tQwAvbdeO4ZW0zETPMGCO0+JGfkDLQdWqhYC1Tm+LpGUS5otVF64KqbI5xid
wuXSbuHEQhc4nXHrfLdnuuJ9WQFcfIbZu5XE2pybBAMj1yoCtUH9j1mr4WlVvbxi
f7wn2bSwO5gcJX0XrMU/TvwQT8AMbbell6gXqs8Llt08yddoeQq2GH8WYZhUPF8W
q8NloGBUB4EENXH8PeFwq1qXpmBMaGA9b0QCgzCK4X7Vfd6eVlbreTyWNO3hgRDr
kl3LbJDaiFzP8LR2x3xg9qjf0GyRFg6VAZGm0RA+Cf2FySUj4GH4RvaqyGbBKBCq
pKqKpqKxywN245uzAVBRW8SYyDrP6ibB/KyEB2G4CUkzGypeRGFsULaprG9n0brA
YM0z4Sgl30W+A64iJOm4cnHVUoyfFpH+ez6NQpxx9WxZ7xhXNDUU6oVQ91FcIr0Q
Hct5tsAyR5GTfsfzbqbgokuUn210xMvPkoorZuW5SCFZ33rYw3M1CNNumBrX7vXx
sWjO8zXh
=lJz/
-----END PGP SIGNATURE-----

@hebasto
Copy link
Member

hebasto commented Dec 7, 2022

Concept ACK.

}

if (!ReadBlockFromDisk(block, pblockindex, Params().GetConsensus())) {
// Block not found on disk. This could be because we have the block
// header in our index but not yet have the block or did not accept the
// block.
// block. Or if the block was pruned right after we released the lock above.
Copy link
Member

Choose a reason for hiding this comment

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

Do we have a test for such a case?

Copy link
Member

Choose a reason for hiding this comment

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

I don't think it it possible to write a test for this deletion race, unless you want to modify the source code as well?

It might be easier to write a functional test for lack of deletion (on Windows) in #26533 by holding a open file handle to the block file

Copy link
Contributor Author

@andrewtoth andrewtoth Dec 7, 2022

Choose a reason for hiding this comment

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

The way I could see a test for this case would be to have a long sleep above the if statement, then call PruneBlockFilesManual in the test and it should fail here, but that would require modifying the source as pointed out by MarcoFalke. Is there another way to introduce an arbitrary delay or block the thread in tests?

Copy link
Member

@hebasto hebasto left a comment

Choose a reason for hiding this comment

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

ACK d7f61e7, I have reviewed the code and it looks OK. Did not make benchmarking though.

Comment on lines +184 to +185
const bool is_not_pruned{WITH_LOCK(::cs_main, return !blockman.IsBlockPruned(blockindex))};
const bool have_undo{is_not_pruned && UndoReadFromDisk(blockUndo, blockindex)};
Copy link
Member

Choose a reason for hiding this comment

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

efd82ae

nit: Less negations:

Suggested change
const bool is_not_pruned{WITH_LOCK(::cs_main, return !blockman.IsBlockPruned(blockindex))};
const bool have_undo{is_not_pruned && UndoReadFromDisk(blockUndo, blockindex)};
const bool is_pruned{WITH_LOCK(::cs_main, return blockman.IsBlockPruned(blockindex))};
const bool have_undo{!is_pruned && UndoReadFromDisk(blockUndo, blockindex)};

?

Copy link
Member

Choose a reason for hiding this comment

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

How is this less negations? You're swapping a negation on one line, for a negation on a different line.

Copy link
Member

Choose a reason for hiding this comment

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

How is this less negations?

line 184: is_not_pruned
line 184: !blockman.IsBlockPruned(blockindex)
line 185: is_not_pruned
total = 3 cases

--

in suggestion
line 185: !is_pruned
total =1 case

Copy link
Member

Choose a reason for hiding this comment

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

I wouldn't consider variable names describing a state "negations", and I'm not sure why there is a need to avoid them.

Copy link
Member

Choose a reason for hiding this comment

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

Can also keep everything in one line as-is, and reduce the diff to a one-char diff.

const bool have_undo{WITH_LOCK(::cs_main, return !blockman.IsBlockPruned(blockindex)) && UndoReadFromDisk(blockUndo, blockindex)};

Copy link
Member

@hebasto hebasto Dec 7, 2022

Choose a reason for hiding this comment

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

I wouldn't consider variable names describing a state "negations", and I'm not sure why there is a need to avoid them.

nm, I agree with the current code.

@maflcko maflcko merged commit 1801d8c into bitcoin:master Dec 8, 2022
sidhujag pushed a commit to syscoin/syscoin that referenced this pull request Dec 8, 2022
@andrewtoth andrewtoth deleted the no-lock-for-read-block branch December 8, 2022 22:55
Fabcien pushed a commit to Bitcoin-ABC/bitcoin-abc that referenced this pull request Jan 25, 2023
Summary:
> After commit ccd8ef6 (D2979) it is no longer required to hold cs_main when calling ReadBlockFromDisk.
>
> The first commit moves ReadBlockFromDisk outside the lock scope in rest_block, where we can see a huge performance improvement when fetching blocks with multiple threads.

This is a partial backport of [[bitcoin/bitcoin#26308 | core#26308]]
bitcoin/bitcoin@c75e3d2

Test Plan:
With Clang and DEBUG:
`ninja all check-all`

Reviewers: #bitcoin_abc, sdulfari

Reviewed By: #bitcoin_abc, sdulfari

Subscribers: sdulfari

Differential Revision: https://reviews.bitcoinabc.org/D13052
Fabcien pushed a commit to Bitcoin-ABC/bitcoin-abc that referenced this pull request Jan 25, 2023
Summary:
> The second commit is from a suggestion on the original PR by jonatack to remove the unnecessary LOCK(cs_main) in the zmq notifier's NotifyBlock.

This is a partial backport of [[bitcoin/bitcoin#26308 | core#26308]]
bitcoin/bitcoin@7d253c9

Depends on D13052

Test Plan:
With Clang and DEBUG:
`ninja all check-all`

Reviewers: #bitcoin_abc, sdulfari

Reviewed By: #bitcoin_abc, sdulfari

Differential Revision: https://reviews.bitcoinabc.org/D13053
Fabcien pushed a commit to Bitcoin-ABC/bitcoin-abc that referenced this pull request Jan 25, 2023
Summary:
This is a partial backport of [[bitcoin/bitcoin#26308 | core#26308]]
bitcoin/bitcoin@f00808e

Depends on D13053

Test Plan:
With Clang and DEBUG:
`ninja all check-all`

Reviewers: #bitcoin_abc, sdulfari

Reviewed By: #bitcoin_abc, sdulfari

Differential Revision: https://reviews.bitcoinabc.org/D13054
Fabcien pushed a commit to Bitcoin-ABC/bitcoin-abc that referenced this pull request Jan 25, 2023
Summary:
This is a partial backport of [[bitcoin/bitcoin#26308 | core#26308]]
bitcoin/bitcoin@efd82ae

Depends on D13054

Test Plan:
With Clang and DEBUG:
`ninja all check-all`

Reviewers: #bitcoin_abc, sdulfari

Reviewed By: #bitcoin_abc, sdulfari

Differential Revision: https://reviews.bitcoinabc.org/D13055
Fabcien pushed a commit to Bitcoin-ABC/bitcoin-abc that referenced this pull request Jan 25, 2023
Summary:
This is a partial backport of [[bitcoin/bitcoin#26308 | core#26308]]
bitcoin/bitcoin@4d92b5a

Depends on D13055

Test Plan:
With Clang and DEBUG:
`ninja all check-all`

Reviewers: #bitcoin_abc, sdulfari

Reviewed By: #bitcoin_abc, sdulfari

Differential Revision: https://reviews.bitcoinabc.org/D13056
Fabcien pushed a commit to Bitcoin-ABC/bitcoin-abc that referenced this pull request Jan 25, 2023
Summary:
This concludes backport of [[bitcoin/bitcoin#26308 | core#26308]]
bitcoin/bitcoin@d7f61e7

Depends on D13055

Test Plan:
With Clang and DEBUG:
`ninja all check-all`

Reviewers: #bitcoin_abc, sdulfari

Reviewed By: #bitcoin_abc, sdulfari

Differential Revision: https://reviews.bitcoinabc.org/D13057
@bitcoin bitcoin locked and limited conversation to collaborators Dec 8, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants