Skip to content
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

Optimize batched RPC lookups in P2P branch #464

Closed
Pantamis opened this issue Aug 25, 2021 · 7 comments · Fixed by #600
Closed

Optimize batched RPC lookups in P2P branch #464

Pantamis opened this issue Aug 25, 2021 · 7 comments · Fixed by #600
Labels
enhancement New feature or request performance

Comments

@Pantamis
Copy link
Contributor

Batched RPC can be resolved faster in P2P branch.

Indeed, each RPC will require loading one block: the one at the height given by an index lookup. But several requests may require looking at the same block ! Currently, each RPC will load the block needed once no matter what.

An interesting improvement could be to first get all needed block heights for each RPC, sort the requests by block height and load a block only once even if it is required for several RPCs. Once loaded we search all the transactions we are looking for in it and send the responses in the order at which we find them.

This is very similar to what is done when loading blocks with scripthash status sync where several blocks are loaded but one block may contain several relevant transactions.

However I think this may require significant refactoring of the code....

@Pantamis Pantamis added the enhancement New feature or request label Aug 25, 2021
@romanz
Copy link
Owner

romanz commented Aug 26, 2021

Good suggestion, thanks!

However I think this may require significant refactoring of the code....

Indeed, current code handles each request in the RPC batch independently:

electrs/src/electrum.rs

Lines 370 to 376 in ffab4f4

Ok(requests) => match requests {
Requests::Single(request) => self.call(client, request),
Requests::Batch(requests) => json!(requests
.into_iter()
.map(|request| self.call(client, request))
.collect::<Vec<Value>>()),
},

@Pantamis
Copy link
Contributor Author

Pantamis commented Aug 26, 2021

The most insteresting use case I know would be this one: janoside/btc-rpc-explorer#356

This change could be also relevant if the stored index is changed to contain the position of the transaction in its block since this still requires parsing a full block for each request. We discuss this possibility with @Kixunil here (not sure if this change is worth it but it is interesting)

@Kixunil
Copy link
Contributor

Kixunil commented Aug 26, 2021

I had a similar idea but was thinking about it too much without writing. :D I think it may also be interesting in case many outputs of same transaction are requested - this transaction has to be loaded multiple times.

I was thinking that with async we could do this: instead of fetching data from db or bitcoind directly we create a future which registers request (as method+params; if there's already such request just add itself as another requester) and returns WouldBlock. At the join point in batch request - or even somehow right before call to select (to work across multiple requests issued at about the same time; not sure how/if we need to modify executor) we do the actual request of tasks and only return Ready after they are done. Perhaps something can be done to avoid waking those tasks during processing of current batch.

If done right, I think this could also decrease synchronization overhead and chance of deadlocks.

But I think we should profile it before attempting anything. :)

@Kixunil
Copy link
Contributor

Kixunil commented Aug 26, 2021

Couldn't stop thinking about it a bit more, no modifications of executor would be required. Instead first create resource handler wrapper (one for bitcoind and one for DB), which gets passed as &mut to all request handlers then call some method on it which will register it globally (to coordinate with other threads) and returns a future or nothing if a task handling the requests already exists. This future, if present, goes together with the remaining ones into FuturesUnordered.

romanz added a commit that referenced this issue Oct 26, 2021
@romanz romanz linked a pull request Oct 26, 2021 that will close this issue
@romanz romanz removed a link to a pull request Oct 26, 2021
romanz added a commit that referenced this issue Oct 27, 2021
romanz added a commit that referenced this issue Oct 27, 2021
@romanz romanz linked a pull request Oct 29, 2021 that will close this issue
@romanz
Copy link
Owner

romanz commented Oct 29, 2021

@Pantamis Could you please try #600?

@Pantamis
Copy link
Contributor Author

Pantamis commented Nov 4, 2021

I tested 70025e4 with my watch only JoinMarket wallet (thousands of subscriptions) in sparrow, the difference is very significant ! The UX is so much better ! (even better than ElectrumX !)

I don't think the impact is caused by smarter blocks lookups but rather parallel subscriptions because the scripthashes rarely appear in the same block in my wallet.

I guess this is not a priority currently but it would be really nice to have parallel subscriptions with smart blocks lookups for outpoints subscriptions for personal block explorers, something to add in #454. Smart block lookup would be very powerful to load all spending transactions of outpoints of a big transaction in janoside/btc-rpc-explorer#356 because many such outpoints are often spend at the same block (in the 10 next blocks). Not having to re-parse the same block would have a great impact on the loading time.

@Kixunil
Copy link
Contributor

Kixunil commented Nov 4, 2021

@Pantamis that needs a spec change but I don't see why it wouldn't be made - it is very reasonable and simple.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request performance
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants