-
Notifications
You must be signed in to change notification settings - Fork 3.7k
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
Execution speed backpressure to handle consistent gas miscalibration #13829
Conversation
5bc70e3
to
55e3c5c
Compare
55e3c5c
to
680a819
Compare
680a819
to
01404b6
Compare
} | ||
}) | ||
.sorted() | ||
// .sorted_by_key(|key| key.unwrap_or(u64::MAX)) |
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.
nit: remove
execution/executor-types/src/lib.rs
Outdated
self.compute_status_for_input_txns() | ||
.iter() | ||
.filter(|status| if let TransactionStatus::Keep(_) = status { true } else { false }) | ||
.count() |
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.
hmm.. why has this changed?
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.
it was wrong, and it is used in tests.
it was passing , because those tests had no block limit.
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.
hmmm.. wouldn't this make execution pipeline fail any time the block limit is hit??
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.
this function (transactions_to_commit_len) is only used in tests.
transactions_to_commit is used throughout, and that one is correct.
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.
Left some comments. Nothing serious or blocking as far as I'm concerned.
The main issue with the slowdown is that it's a local decision which raises the free-riding concern. However, I do not think it is a severe risk -- just one to be aware of.
}, | ||
PipelineBackpressureValues { | ||
back_pressure_pipeline_latency_limit_ms: 3500, | ||
back_pressure_pipeline_latency_limit_ms: 6000, | ||
max_sending_block_txns_override: 1000, | ||
max_sending_block_bytes_override: MIN_BLOCK_BYTES_OVERRIDE, | ||
backpressure_proposal_delay_ms: 500, |
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.
In this block of PipelineBackpressureValues
I don't understand why the gas is not limited. Wouldn't that affect the execution time better than # of txs?
Also, what is the logic behind the chosen numbers?
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.
Also, what is the logic behind the chosen numbers?
nothing more than gradual reduction as values change. we could've also just had formula instead of multiple discrete values, not a big deal either way
In this block of PipelineBackpressureValues I don't understand why the gas is not limited. Wouldn't that affect the execution time better than # of txs?
number of txns is known upfront (i.e. when block is being created), and how block gas limit affects the block is only know after execution. So if proposer wants to create smaller block for smaller messages, # of txns need to be adjusted.
Once we have execution pool, and latency overhead of transaction being included in the block, but hitting the limit, being drastically reduced, plan is to change most backpressures to reduce block gas limit, instead of transactions. (i.e. max_sending_block_txns_override will be in txns, and max_txns_from_block_to_execute will be changed to be max_block_gas_limit_override)
@@ -104,9 +106,28 @@ impl PipelinedBlock { | |||
mut self, | |||
input_transactions: Vec<SignedTransaction>, | |||
result: StateComputeResult, | |||
execution_time: Duration, |
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 suppose a validator can write any value she wishes here. Can this value be verified to be true?
The validator might have an incentive to lie due to a "free-riding" argument. That is, let other validators slow down (and receive less rewards), their slow down will also "solve the problem for me" while not slowing down my rewards.
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.
this is a local value, from it's own computation. there is no need for it to lie here, validator can just adjust block creation computation in any way they want.
that's why longterm - getting incentives/protocol right is needed here for best results
parent_block_id, | ||
block_executor_onchain_config, | ||
) | ||
.map(|output| (output, start.elapsed())) |
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.
Is this start.elapsed() value being propagated to other validators (is it in the block data)? And if so, does writing a different (incorrect) value affects the block's validity or txs validity?
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.
no, this is local value, only used by a validator to decide how big of a block to create.
@@ -17,18 +17,18 @@ use crate::{ | |||
util::time_service::TimeService, | |||
}; | |||
use anyhow::{bail, ensure, format_err, Context}; | |||
use aptos_config::config::{ChainHealthBackoffValues, PipelineBackpressureValues}; | |||
use aptos_config::config::{ | |||
ChainHealthBackoffValues, ExecutionBackpressureConfig, PipelineBackpressureValues, |
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.
What is the reason for the different name convention? (Backoff != Backpressure and Values != Config)
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.
Backoff vs Backpressure is old artifact
Config
here generally means a set of configs (i.e. struct with fields), and Values
here means a vector of sorts - either of raw values or of config structs
} else { | ||
0.0 | ||
}, | ||
); |
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.
This seems to be a main place where the logic is applied. I would have benefited from some commenting here. But maybe it's just me and my (lack of) coding skill ;-)
} | ||
} | ||
} | ||
EXECUTION_BACKPRESSURE_ON_PROPOSAL_TRIGGERED.observe( |
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.
do we need both this and pipeline backpressure based on timestamp? aren't they achieving similar goal?
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.
this drastically reduces when pipeline backpressure kicks in. but:
- this only considers execution. if commit or another stage becomes a bottleneck, we need to fall back to pipeline backpressure
- this is only local. pipeline backpressure sees if building commit cert is delayed, and kicks in - even if current node is processing thing very fast
pub back_pressure_pipeline_latency_limit_ms: u64, | ||
pub num_blocks_to_look_at: usize, | ||
pub min_blocks_to_activate: usize, | ||
pub percentile: f64, |
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.
nit: many of the configs could use some comments explaining the behavior.
num_blocks_to_look_at: 12, | ||
min_blocks_to_activate: 4, | ||
percentile: 0.5, | ||
target_block_time_ms: 300, |
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.
Should we reduce the target block time to 250 ms given we can do ~3.5-4 blocks per second under load.
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.
Can you address this? Apart from it, the PR looks good to me.
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.
oops thought I did
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.
Does this still have to be changed?
block_executor_onchain_config, | ||
) | ||
let start = Instant::now(); | ||
executor |
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.
Hmm wondering if its better to measure both the execution and state checkpoint latency here or just the execution latency ?
block_execution_times | ||
); | ||
|
||
self.execution.as_ref().and_then(|config| { |
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.
This function could use some comment explaining the backoff behavior.
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.
added comments
.iter() | ||
.flat_map(|summary| { | ||
let execution_time_ms = summary.execution_time.as_millis(); | ||
if execution_time_ms > config.min_block_time_ms_to_activate as u128 { |
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 am wondering if this is the optimal way to calculate the block size. Instead of this, how about calculating some sort of normalize time to execute per transaction in the entire window and use that to compute the block size?
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.
updated comment why I am doing it this way
01404b6
to
651c559
Compare
const MAX_SENDING_BLOCK_UNIQUE_TXNS: u64 = 1900; | ||
const MAX_SENDING_BLOCK_TXNS: u64 = 4500; |
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.
updating block size here
651c559
to
b6e6120
Compare
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
b6e6120
to
14d7a7b
Compare
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
✅ Forge suite
|
✅ Forge suite
|
✅ Forge suite
|
Description
We can have gas miscalibration for various reasons, leading to a block taking longer than intended 300ms even after block gas limit has been applied.
And so here we make sure on consensus side to gracefully handle it.
We track execution speed of historical blocks, and their sizes - and compute what block size should've been such that it lasted 300ms. We then take p50 of previous 10 blocks - and use that as the block limit for the next block.
Once we have improved re-ordering and execution pool, we can additionally use reordering_ovarpacking_factor to create larger blocks - but only execute wanted number of transactions.
Currently execution stage is the bottleneck almost always, and so this PR only touches that. But practically - we can do the same for each stage, to make sure to reduce the block if for example commit stage is slow.
Type of Change
Which Components or Systems Does This Change Impact?
How Has This Been Tested?
forge test from #13789
Key Areas to Review
Checklist