-
Notifications
You must be signed in to change notification settings - Fork 2k
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
execute SQL updates as chunks in _set_spent function for tx_removals #11613
Conversation
wow that is a major speedup! This must be one of the main bottlenecks of syncing, since there are probably ~1000 coins per transaction block during the dust storm periods. |
I'll need to rewrite the script as the chosen This is how a sql log would look like with this PR 23:45:53.959293 SAVEPOINT s162960
23:45:53.959428
UPDATE OR FAIL coin_record INDEXED BY sqlite_autoindex_coin_record_1
SET spent_index=1556983
WHERE spent_index=0
AND coin_name IN (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
23:45:53.959953 RELEASE s162960 |
Earle answered my question about the sqlite concerns in the last AMA mainly with the high amount of I/O which is done right now. I see a potential to reduce that, by optimizing SQLs and some parameters around. For instance, I did full sync tests from height 0 to 1945145 on my i7-11700k recently and tested some of these changes.
I'd expect that on RPi the reduced runtime percentage would be even higher. As I fully understand the reasoning behind the runtime/write reduction from my test1, I am still not sure why test2 brings that much of an improvement. So I'm picking some of the kind-of-standalone changes and submitting them as PRs, like the last few I did. |
The tests are failing here, they need to pass. Those are some impressive improvements! what is test1 and what is test2? |
I have problems understanding how these tests get triggered and what to check before making a PR to mitigate that errors.
I think rowcount is only available for
test1 The runtime improvement and IO reduction was expected and that was already what I experienced in dbv1 in winter during my tests, but I wasn't sure how to convince you to implement that and didn't have the time to follow up on that later. I was thinking of a simple implementation like test2 |
You can get rowcount from the Cursor object which is returned from execute I think. We need to look at the improvements one by one to see which one has an impact. The tests get trigerred every time you push a new commit. You can also close and re-open the PR to trigger the tests. Thanks for helping out btw, this is a very important change |
You can also run the tests locally. If you You can then find the test invocation by looking into the workflow. - for example, here is the invocation for the core-full_node test:
|
(Don't mind me, I'm just helping @neurosis69 a bit with putting his ideas into code :) |
While `executemany` is convenient for executing the same SQL for many different sets of parameters, it still executes one SQL for each parameter set. For updating coin spends, this means potentially running hundreds of UPDATEs per transaction block. With this change, these hundreds of single coin UPDATEs are batched into larger chunks where a single UPDATE marks many coins spent.
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 this looks great! I've spent some time recently trying to pin down exactly where we're spending so much time. My first impression was that we were I/O bound, but I couldn't find any smoking guns when profiling the aiosqlite thread. I suspect we spend a lot of time ping-ponging with the aiosqlite thread.
So, I suspect this speed-up could also be gained by not waiting for one execute()
to complete before issuing the next. Until recently I assumed async
functions would return a future and you could just remove the await
and wait for them all at the end of the function. However, that doesn't quite work, you have to create_task()
to get a future back.
In summary, I think every single await
in a loop is potentially problematic, and we should probably generally try to void that, by creating short lived tasks (just to get futures for the async operaitons). Even with this change, ideally you wouldn't use await
inside the loop. Although, in practice you would most likely end up with two or fewer turns through the loop.
name_params = ",".join(["?"] * len(coin_names_chunk)) | ||
if self.db_wrapper.db_version == 2: | ||
ret: Cursor = await conn.execute( | ||
f"UPDATE OR FAIL coin_record INDEXED BY sqlite_autoindex_coin_record_1 " |
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.
specifying the index here, I assume, is to make sure it's not using the spent_index
index, right?
The condition spent_index=0
isn't really supposed to be required. All these coins are supposed to be unspent (iiuc). Perhaps we could remove that condition and also drop the INDEXED BY
, would that work or make sense?
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.
Yes, I ran this first without that index clause and it started to create a huge amount of i/o, then I figured the wrong index usage.
I'm sure if we remove spent_index
condition, we don't need INDEXED BY
any more
I'm interested in running benchmarks on this myself. |
running the Running the stress test with 500 full blocks, I see:
|
my first run was with db-sync
|
On which hardware are you running your benchmarks? My timings in first post for this patch were taken from RPi. |
I've started a few testsyncs (regular from peer) over the last few days and when I've sorted the data out I will share that data in my repo. How can I start your mentioned benchmark? |
I'm running it on an AMD threadripper with SSD, so quite powerful compared to the RPi. Unfortunately it's not so simple to run the benchmark. I'm using an artificial chain, created to max out all blocks. i.e. as a stress-test. You can create this chain by running:
And when running |
chia-sync-data is my repo where I'll upload the test data. These uploads include the debug.log and automatically generated sar reports for every uploaded test. The ansible repo is I use for these tests is chia-sync-test. |
I'm not sure what this test actually does? Is this a block validation test? I don't think that this is a valid test for my proposed change in _set_spent function, or is it? EDIT: Or I may have executed the wrong script? |
Thank you, I am hopeful fir the future and continued success! thank you for your hard work!
Get Outlook for iOS<https://aka.ms/o0ukef>
…________________________________
From: Mariano Sorgente ***@***.***>
Sent: Tuesday, May 24, 2022 10:55:12 AM
To: Chia-Network/chia-blockchain ***@***.***>
Cc: Subscribed ***@***.***>
Subject: Re: [Chia-Network/chia-blockchain] execute SQL updates as chunks in _set_spent function for tx_removals (PR #11613)
You can get rowcount from the Cursor object which is returned from execute I think.
We need to look at the improvements one by one to see which one has an impact.
The tests get trigerred every time you push a new commit. You can also close and re-open the PR to trigger the tests.
The main thing is that you need to replicate the exact behavior of the previous code. For example, we use hex in the previous code, and not bytes for the coin names. Also, we throw a value error if we weren't able to set everything to spent. These things must be in the new PR.
Thanks for helping out btw, this is a very important change
—
Reply to this email directly, view it on GitHub<https://nam12.safelinks.protection.outlook.com/?url=https%3A%2F%2Fgithub.com%2FChia-Network%2Fchia-blockchain%2Fpull%2F11613%23issuecomment-1136105774&data=05%7C01%7C%7Cde0ddd2655cb4867851f08da3d9dc9b0%7C84df9e7fe9f640afb435aaaaaaaaaaaa%7C1%7C0%7C637890045139914989%7CUnknown%7CTWFpbGZsb3d8eyJWIjoiMC4wLjAwMDAiLCJQIjoiV2luMzIiLCJBTiI6Ik1haWwiLCJXVCI6Mn0%3D%7C3000%7C%7C%7C&sdata=85leDgYSWKd6lwOXxmOK8%2FXkLUZiCLZEF6iwoU89Gbw%3D&reserved=0>, or unsubscribe<https://nam12.safelinks.protection.outlook.com/?url=https%3A%2F%2Fgithub.com%2Fnotifications%2Funsubscribe-auth%2FAW5GVO2PPWVNBBECQMVAZWTVLT3WBANCNFSM5WXFQICA&data=05%7C01%7C%7Cde0ddd2655cb4867851f08da3d9dc9b0%7C84df9e7fe9f640afb435aaaaaaaaaaaa%7C1%7C0%7C637890045139914989%7CUnknown%7CTWFpbGZsb3d8eyJWIjoiMC4wLjAwMDAiLCJQIjoiV2luMzIiLCJBTiI6Ik1haWwiLCJXVCI6Mn0%3D%7C3000%7C%7C%7C&sdata=hbJ9rKqRKEt91AGvPH7VJgfWh2RiHaewHLWtObJx8L0%3D&reserved=0>.
You are receiving this because you are subscribed to this thread.Message ID: ***@***.***>
|
That's the connection that reads the test blockchain database. It feeds those blocks into a full node instance, that's the actual test/benchmark. |
Finally I was able to run your benchmark on my i7-11700k with enabled sql logging.
sql.log output, taken from last updates issued on block height 479: before: 13:38:03.322184 SAVEPOINT s2808
13:38:03.322345 UPDATE OR FAIL coin_record SET spent_index=? WHERE coin_name=? AND spent_index=0
...
13:38:03.348058 UPDATE OR FAIL coin_record SET spent_index=? WHERE coin_name=? AND spent_index=0
13:38:03.348209 RELEASE s2808 13:38:03.348209 - 13:38:03.322184 = 0,026025s after 13:42:32.806548 SAVEPOINT s2808
13:42:32.807098 UPDATE OR FAIL coin_record INDEXED BY sqlite_autoindex_coin_record_1 SET spent_index=479 WHERE spent_index=0 AND coin_name IN (?,?,?,?,?,
?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?
...
,?,?,?,?,?,?,?,?,?,?)
13:42:32.814143 RELEASE s2808 13:42:32.814143 - 13:42:32.806548 = 0,007595s I compared the main branch with the change The IO related improvements would come from no-wal/synchronous and drop indexes during sync. |
I was expecting WAL would improve write performance, since updates are written sequentially to the WAL, and incorporated into the DB later, asynchronously. |
Compared to other journal modes I'd agree. But compared to no journaling mode I'd expect it to be slower. At least you need additional checkpointing operation which slows down transactions. Probably different file format in WAL as well, but I don't know SQLite that good. Here is hyperfine Benchmark result of 10 runs for above tests, slight improvement stays. before: Benchmark 1: python ./tools/test_full_sync.py run --test-constants /chia_temp1/test-chain/stress-test-blockchain-500-100-refs.sqlite
Time (mean ± σ): 146.391 s ± 0.508 s [User: 368.267 s, System: 147.680 s]
Range (min … max): 145.375 s … 147.050 s 10 runs after: Benchmark 1: python ./tools/test_full_sync.py run --test-constants /chia_temp1/test-chain/stress-test-blockchain-500-100-refs.sqlite
Time (mean ± σ): 143.878 s ± 0.536 s [User: 364.189 s, System: 149.680 s]
Range (min … max): 142.985 s … 144.855 s 10 runs |
I have another PR planned, based on an idea of @xchdata1, using batch Running a current benchmark for only this change brings down runtime to 138.88s. But for this |
Benchmark tests For now I'll simply update the readme in my repo. At the top, the table containes the runtimes from my i7-11700k, the avg timing over 5 executions on a 500 blocks test. I'll probably add timings for other tests and also plan to create a bigger chain, as 500 is a bit to small for my i7 I think. Synchronous It looks like it is always off. Is this part of your simulation? Regarding this PR I'd conclude
EDIT: Is there a profiling feature built in, to only see the time it spent for the _set_spent function call? Because that's what we actually want to measure. |
The original purpose of I've manually set it to "FULL" from time to time to be more authentic for benchmark purposes. I also have an outstanding PR to make this configurable on the command line: #11647 |
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.
aok
Currently for every coin removal per tx block, one SQL Update command is executed after the other.
This could lead to up to 900 update executions per tx block.
This PR changes the issued Update statement by
coin_names
intocoin_names_chunk
using given constantcoin_name IN ( ... )
instead ofcoin_name = x'...'
sql log from current implementation
tx time from
SAVEPOINT
toRELEASE
: 0.061659s for 103 removals on spent_index 283422sql log after this PRs implementation
tx time from
SAVEPOINT
toRELEASE
: 0.005919s for 103 removals on spent_index 283422