Closed akashin closed 3 weeks ago
All modified and coverable lines are covered by tests :white_check_mark:
Project coverage is 71.66%. Comparing base (
48201f8
) to head (91bf9ad
).
:umbrella: View full report in Codecov by Sentry.
:loudspeaker: Have feedback on the report? Share it here.
What do we need this for? In particular, is the pre-generation of state from #11560 not enough, considering we said we don’t want to generate users upon each benchmark run?
We need to get the created users involved in each benchmark run, otherwise it's just an idle state that is never touched and is not occupying space in caches that we are trying to test by increasing the state size.
Is there any recipe on how to reuse the state created by https://github.com/near/nearcore/pull/11560 in a FT benchmark run?
otherwise it's just an idle state that is never touched and is not occupying space in caches that we are trying to test by increasing the state size
Hmm so it’s still making the trie bigger even if it’s never touched, and by making the trie bigger it increases the touching-trie-node costs. This being said, you’re right that it could be made worse, by actually using all the passive users of the state, instead of just the few passive users from the benchmark.
In order to properly reuse all the pre-created state, we should run this line for each account that has been generated in the state, before starting the benchmark: https://github.com/near/nearcore/blob/49eff7083e9f02b2fb63e063ed332b8188bf7d89/pytest/tests/loadtest/locust/common/ft.py#L58
This comes from register_passive_users
, and AFAIU should be enough for the benchmark to try using all the pre-generated passive users immediately, instead of trying to recreate them on each run.
Does that make sense?
otherwise it's just an idle state that is never touched and is not occupying space in caches that we are trying to test by increasing the state size
Hmm so it’s still making the trie bigger even if it’s never touched, and by making the trie bigger it increases the touching-trie-node costs. This being said, you’re right that it could be made worse, by actually using all the passive users of the state, instead of just the few passive users from the benchmark.
In order to properly reuse all the pre-created state, we should run this line for each account that has been generated in the state, before starting the benchmark:
This comes from
register_passive_users
, and AFAIU should be enough for the benchmark to try using all the pre-generated passive users immediately, instead of trying to recreate them on each run.Does that make sense?
It would be interesting to see the difference between running with idle large state and the hot large state. But ultimately, the benchmark needs to use hot large state to reflect disk access costs.
High-level idea makes sense, but I'm not sure how to implement it for https://github.com/near/nearcore/pull/11560, hence I created this PR.
It would be interesting to see the difference between running with idle large state and the hot large state.
This should be easy: the way I see it, we would add a --pregenerated-users=N
that’d assume the users numbered 0..N are pre-created (with the account name being hash(i)). We could then run with lots of pregenerated users anyway, but run the benchmark with either --pregenerated-users=500
or --pregenerated-users=10000000
:)
High-level idea makes sense, but I'm not sure how to implement it for https://github.com/near/nearcore/pull/11560, hence I created this PR.
Do you want to have a 1:1 so I can explain the details of the idea? :) That said if within the next 1-2 weeks you don’t think you’ll have time to look into it, then I’ll just do it before re-generating the state. The rough steps are:
hash(i)
with i in 0..N
--pregenerated-users=M
option to the actual benchmark, that just appends hash(i) for i in 0..M
into registered_users
X..Y
. This will make us able to be sure that all the pre-generated users are actually pre-generated, as right now it generates as much state as possible but without checking that all FT contracts are actually balanced@Ekleog-NEAR , I tweaked parameters a bit more for this to work well on GCP and it should be ready for submission.
The changes to
create_passive_users
sound reasonable to me.However, there’s one thing bothering me: right now you add a
--num-passive-users
option that generates passive users at the beginning. In order to tie in properly with #11560, WDYT about this?* Renaming this to `--pre-generate-passive-users=N` * In follow-up work, adding a `--pre-existing-passive-users=N` option that’d just do the second point from [this comment](https://github.com/near/nearcore/pull/11593#issuecomment-2180692999)
Overall I’m not even sure
--pre-generate-passive-users
would be needed, considering #11560 and the fact that pre-generating is too slow anyway; but at least with that name it wouldn’t conflict with the future--pre-existing-passive-users
:)
At the moment you can re-use users just by passing num-passive-users
- it's not ideal as it is doing some redundant work, but it is still much faster to run on a database where users are already created (it mostly queries accounts for existence and doesn't try to create them). I plan to optimize this a bit further in the future to leverage the fact that accounts are created sequentially and we can do a binary search to find how much more accounts we need to create.
it mostly queries accounts for existence and doesn't try to create them
If this is actually fast enough, then it would be great! But I’m not sure how you reached that conclusion? From my reading of the code, what should happen is for each num-passive-user
requested for, regardless of how many users already exist, on locust init:
prepare_accounts
, which unconditionally calls sign_create_account_with_full_access_key_and_balance_transaction
and submits the resulting transactionregister_passive_user
, which unconditionally submits a function call transaction to deposit some storageAnd both of these operations should take both wall clock time and gas, thus making creating them all very slow.
OTOH, if the users already exist, it should be enough to just run self.registered_users.append(account.key.account_id)
as per my above comment, which is basically instantaneous. And we can offload figuring that out to the user, because in practice we’ll know how many users are pregenerated in our databases, hence my suggestion for the parameter renaming right now, and filing an issue to add the --pre-existing-passive-users
option.
So I’m not sure, where did I miss the check for pre-existence of accounts to not re-create them?
it mostly queries accounts for existence and doesn't try to create them
If this is actually fast enough, then it would be great! But I’m not sure how you reached that conclusion? From my reading of the code, what should happen is for each
num-passive-user
requested for, regardless of how many users already exist, on locust init:1. it calls `prepare_accounts`, which unconditionally calls `sign_create_account_with_full_access_key_and_balance_transaction` and submits the resulting transaction 2. it calls `register_passive_user`, which unconditionally submits a function call transaction to deposit some storage
And both of these operations should take both wall clock time and gas, thus making creating them all very slow.
OTOH, if the users already exist, it should be enough to just run
self.registered_users.append(account.key.account_id)
as per my above comment, which is basically instantaneous. And we can offload figuring that out to the user, because in practice we’ll know how many users are pregenerated in our databases, hence my suggestion for the parameter renaming right now, and filing an issue to add the--pre-existing-passive-users
option.So I’m not sure, where did I miss the check for pre-existence of accounts to not re-create them?
sign_create_account_with_full_access_key_and_balance_transaction
is no unconditional, it is only called for accounts that need to be created https://github.com/near/nearcore/blob/ea9765f9f91ad4d38cdd4cdfa224b2037c7a19c5/pytest/tests/loadtest/locust/common/base.py#L504 (see iteration over to_create
list). Existing accounts will not be there and instead will be in to_refresh
list https://github.com/near/nearcore/blob/ea9765f9f91ad4d38cdd4cdfa224b2037c7a19c5/pytest/tests/loadtest/locust/common/base.py#L491 for which we only refresh nonces which does not submit any new transactions https://github.com/near/nearcore/blob/ea9765f9f91ad4d38cdd4cdfa224b2037c7a19c5/pytest/tests/loadtest/locust/common/base.py#L531
I agree that point 2 is not perfect and we should be able to avoid it, but these transactions should be no-op as the account is already created https://github.com/near/near-sdk-rs/blob/c843e9aec747b9acc7e240a6dea80a14448a7bfd/near-contract-standards/src/fungible_token/storage_impl.rs#L56
We can offload this to the user, but it will be more error-prone as it creates a possibility of using an incorrect value and getting hard-to-decipher failures.
Got it, thank you for the precision on step 1! I had indeed missed the split into to_create
and to_refresh
, so that only leaves one function call transaction per already-created user.
Dichotomy would likely be the best option, but this may be good enough for now, you know better than I as you actually tried running it. If it is, maybe we should delete the outcome of #11560 altogether? There’s no point in keeping the two of these PRs.
Got it, thank you for the precision on step 1! I had indeed missed the split into
to_create
andto_refresh
, so that only leaves one function call transaction per already-created user.Dichotomy would likely be the best option, but this may be good enough for now, you know better than I as you actually tried running it. If it is, maybe we should delete the outcome of #11560 altogether? There’s no point in keeping the two of these PRs.
We shouldn't need ft-state-builder.py any more, I agree.
This PR allow to generate multiple large FT contracts that can be reused in the future benchmark runs. I ran this with 10M users per contract and was able to generate 4 contracts (one per worker), each with 10M users and 1.25GB of state in 24 hours (with gas_limit set to 10 PGas):