-
Notifications
You must be signed in to change notification settings - Fork 803
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
fatxpool
: do not use individual transaction listeners
#7316
base: master
Are you sure you want to change the base?
Conversation
substrate/client/transaction-pool/src/fork_aware_txpool/multi_view_listener.rs
Outdated
Show resolved
Hide resolved
substrate/client/transaction-pool/src/fork_aware_txpool/multi_view_listener.rs
Outdated
Show resolved
Hide resolved
substrate/client/transaction-pool/src/fork_aware_txpool/view_store.rs
Outdated
Show resolved
Hide resolved
aggregated_stream | ||
} | ||
|
||
/// Notify the listeners about the extrinsic broadcast. | ||
pub fn broadcasted(&mut self, hash: &H, peers: Vec<String>) { | ||
trace!(target: LOG_TARGET, "[{:?}] Broadcasted", hash); | ||
self.fire(hash, |watcher| watcher.broadcast(peers)); |
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.
So broadcasted event is a bit different and we handle it directly in the upper layers without involvement of the validated_pool?
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, it is directly call on the pool by the networking. There is dedicated method for this:
polkadot-sdk/substrate/client/transaction-pool/api/src/lib.rs
Lines 308 to 309 in 0d644ca
/// Notify the pool about transactions broadcast. | |
fn on_broadcasted(&self, propagations: HashMap<TxHash<Self>, Vec<String>>); |
For new pool, we are just triggering event:
polkadot-sdk/substrate/client/transaction-pool/src/fork_aware_txpool/fork_aware_txpool.rs
Lines 831 to 833 in 0d644ca
fn on_broadcasted(&self, propagations: HashMap<TxHash<Self>, Vec<String>>) { | |
self.view_store.listener.transactions_broadcasted(propagations); | |
} |
For the old pool, this went through validated_pool
's listener:
polkadot-sdk/substrate/client/transaction-pool/src/single_state_txpool/single_state_txpool.rs
Lines 345 to 347 in 0d644ca
fn on_broadcasted(&self, propagations: HashMap<TxHash<Self>, Vec<String>>) { | |
self.pool.validated_pool().on_broadcasted(propagations) | |
} |
polkadot-sdk/substrate/client/transaction-pool/src/graph/validated_pool.rs
Lines 682 to 687 in 0d644ca
pub fn on_broadcasted(&self, propagated: HashMap<ExtrinsicHash<B>, Vec<String>>) { | |
let mut listener = self.listener.write(); | |
for (hash, peers) in propagated.into_iter() { | |
listener.broadcasted(&hash, peers); | |
} | |
} |
>::default())); | ||
|
||
let (tx, rx) = mpsc::tracing_unbounded("txpool-multi-view-listener-task-controller", 32); | ||
let task = Self::task(external_controllers.clone(), rx); |
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 find that the flow is not so easy to follow. I thought about giving the task sole ownership of the external_controllers
. This would get rid of the mutex. But requires some additional messages to manage the external streams list. What do you think?
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 was also thinking about adding messages here. But at the end I decided that it will be more complex then having mutex.
After all the flow is not that complex. We need mutex to add external watcher controller (sink) into the map. This addition is made from the context of submit_and_watch
call.
I don't know. If you think messages will make code more readable, I can give it a try (my little concern is proper order of processing, but probably should be fine).
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 thinking adding message passing is more complex too (as in we'll need to add APIs for sending/receiving messages, and for handling them), and at the same time it would make the logic around external_controllers
easier to read/comprehend. However, we can also add a comment mentioning that the only place where we're writing is submit_and_watch
and I would find it sufficient.
I am thinking that there might be some benefits of message passing over taking and holding the mutex - which can create contention if submit_and_watch
is called (very) frequently - although, if rate-limitting is implemented at some upper level, this wouldn't be an issue.
All in all, I transform this to message passing solely from the performance consideration, if my reasoning is correct. I would also add the comment specifying where the external_controllers
is written from.
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 considering using: DashMap
which replacement for RwLock<HashMap>
. Need to take a deeper look on this.
substrate/client/transaction-pool/src/fork_aware_txpool/multi_view_listener.rs
Outdated
Show resolved
Hide resolved
self.listener.write().create_dropped_by_limits_stream() | ||
} | ||
|
||
/// Refer to [`Listener::create_aggregated_stream`] | ||
pub fn create_aggregated_stream( |
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 general these changes mean that the submit_and_watch
codepath is only used by the single state pool, so we can remove all of that once the fatxpool is the default.
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!
The only concern I have is that now we are also sending events for transactions that are not watched. So theoretically we are creating a unnecessary traffic in the aggregated stream on collators for gossiped transactions. (All notifications are simply dropped in the task, because there are no external controllers in the map).
I think it is not significant overhead (I would even say negligible), but maybe 🤔 we should block it. This will involve populating the hashmap with transactions which are allowed to be notified in the aggregated stream. But for now I would leave it as it is proposed.
…view_listener.rs Co-authored-by: Sebastian Kunert <[email protected]>
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 stylistic comments and some questions/opinions. LGTM but I will approve after clarifying the comments (if we don't need other changes than the ones I suggested).
Controller<ExternalWatcherCommand<ChainApi>>, | ||
>::default())); | ||
|
||
let (tx, rx) = mpsc::tracing_unbounded("txpool-multi-view-listener-task-controller", 512); |
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 please extract the 512
into a constant variable?
return None | ||
} | ||
|
||
trace!(target: LOG_TARGET, "[{:?}] create_external_watcher_for_tx", tx_hash); | ||
|
||
let (tx, rx) = mpsc::tracing_unbounded("txpool-multi-view-listener", 32); | ||
controllers.insert(tx_hash, tx); | ||
let (tx, rx) = mpsc::tracing_unbounded("txpool-multi-view-listener", 128); |
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.
Would be nice to have 128
extracted in a dedicated const
.
trace!(target: LOG_TARGET, "[{:?}] dropped_sink: send message failed: {:?}", tx, e); | ||
} | ||
} | ||
self.send_to_dropped_stream_sink(tx, TransactionStatus::Dropped); |
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.
why aren't we sending to the aggregated stream sink here as well?
trace!(target: LOG_TARGET, "[{:?}] dropped_sink: send message failed: {:?}", tx, e); | ||
} | ||
} | ||
self.send_to_dropped_stream_sink(tx, TransactionStatus::Usurped(by.clone())); |
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.
Why aren't we sending to the aggregated stream sink here as well?
/// (external watcher) are not sent. | ||
pub fn create_aggregated_stream(&mut self) -> AggregatedStream<H, BlockHash<C>> { | ||
let (sender, aggregated_stream) = | ||
tracing_unbounded("mpsc_txpool_aggregated_stream", 100_000); |
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.
Would be great to have a const
variable with 100_000
.
pub fn create_dropped_by_limits_stream(&mut self) -> DroppedByLimitsStream<H, BlockHash<C>> { | ||
/// The stream can be used to subscribe to events related to dropping of all extrinsics in the | ||
/// pool. | ||
pub fn create_dropped_by_limits_stream(&mut self) -> AggregatedStream<H, BlockHash<C>> { | ||
let (sender, single_stream) = tracing_unbounded("mpsc_txpool_watcher", 100_000); |
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.
Would be good to have 100_000
placed in a dedicated const
here too.
/// ready and future statuses are reported via this channel to allow consumer of the stream | ||
/// tracking actual drops. |
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 does it mean to allow consumer of the stream tracking actual drops
? How does ready
& future
events on the same stream improve the dropped
tracking?
>, | ||
mut command_receiver: CommandReceiver<ControllerCommand<ChainApi>>, | ||
) { | ||
let mut aggregated_streams_map: StreamMap<BlockHash<ChainApi>, ViewStatusStream<ChainApi>> = |
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.
dq: Should we be concerned in practice with the size of this StreamMap
? It increases when adding a new view, so in theory it can grow to arbitrary sizes, but in practice it might not get to extreme sizes due back pressure in other parts of the pool (for which I am having a hard time to reason about). The docs mention that it works best with a smallish
number of streams as all entries are scanned on insert, remove, and polling.
>::default())); | ||
|
||
let (tx, rx) = mpsc::tracing_unbounded("txpool-multi-view-listener-task-controller", 32); | ||
let task = Self::task(external_controllers.clone(), rx); |
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 thinking adding message passing is more complex too (as in we'll need to add APIs for sending/receiving messages, and for handling them), and at the same time it would make the logic around external_controllers
easier to read/comprehend. However, we can also add a comment mentioning that the only place where we're writing is submit_and_watch
and I would find it sufficient.
I am thinking that there might be some benefits of message passing over taking and holding the mutex - which can create contention if submit_and_watch
is called (very) frequently - although, if rate-limitting is implemented at some upper level, this wouldn't be an issue.
All in all, I transform this to message passing solely from the performance consideration, if my reasoning is correct. I would also add the comment specifying where the external_controllers
is written from.
let (tx, rx) = mpsc::tracing_unbounded("txpool-multi-view-listener", 32); | ||
controllers.insert(tx_hash, tx); | ||
let (tx, rx) = mpsc::tracing_unbounded("txpool-multi-view-listener", 128); | ||
external_controllers.insert(tx_hash, tx); |
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 thinking about dropping the lock right after this, to minimize how long the lock is held. WDYT?
All GitHub workflows were cancelled due to failure one of the required jobs. |
Description
During 2s block investigation it turned out that ForkAwareTxPool::register_listeners call takes significant amount of time.
This PR implements the idea outlined in #7071. Instead of having a separate listener for every transaction in each view, we now use a single stream of aggregated events per view, with each stream providing events for all transactions in that view. Each event is represented as a tuple: (transaction-hash, transaction-status). This significantly reduce the time required for
maintain
.Review Notes
(transaction-hash, transaction-status)
,MultiViewListener
now has a task. This task is responsible for:controller_receiver
which provides side-channel commands (likeAddView
orFinalizeTransaction
) sent from the transaction pool.Closes #7071