-
Notifications
You must be signed in to change notification settings - Fork 783
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
Fix accounts_receivable RPC command with sorting and count #3845
Fix accounts_receivable RPC command with sorting and count #3845
Conversation
I'd like to improve the algorithm we use here since we're in the area. The first issue I see is all the pending entries are added followed at the end by clamping to the requested size. This consumes memory proportional to the total number of pending entries but we can solve this a more efficient way. Every time we add an item to the map if we check to see if the map has exceeded the requested size we can erase the minimum item in the map. The second issue is using conditional logic for checking if sorting was requested. This can be replaced with a strategy pattern that only needs to be constructed once and then the rest of the algorithm proceeds simply by iterating all pending entries and pasing them through the strategy object. It would be something like: ledger pending entry -> strategy -> result map -> boost ptree I see that across the RPCs we use 3 different ways to sort things, maps, sorting a vector, and sorting directly in the boost ptree. We should use maps unless there's a quantifyable reason not to since they'll generally be the most efficient. Stable sort is guaranteed by std::multimap "The order of the key-value pairs whose keys compare equivalent is the order of insertion and does not change. (since C++11)" https://en.cppreference.com/w/cpp/container/multimap We would have a class "result_strategy" which has a pure virtual function result_strategy::operator () (std::pair<nano::pending_key, nano::pending_info>). The main rpc handler function would iterate all pending entries and pass in each item. The non-sorted version would simply insert each item in to the ptree. The sorted version would have an std::multimap<nano::pending_key, nano::pending_info> object state and operator () would insert/trim on each call. The sorted destructor would insert all items from the std::multimap in to the boost ptree. Future work would be making this a template that could be used in the other places we do this same pattern to eliminate duplicate code. |
Thank you, good points, I'll try to work on that. |
@clemahieu do we want to introduce sorting field in wallet_receivable? |
e8a3bde
to
fee4b88
Compare
PR updated, following your suggestion I used strategy pattern on two layers - for sorting/non-sorting and for response format (which fields to use in response). |
Hi @clemahieu, I know it's been a long time and it's not a priority now, however would you be able to take a look at changes here and comment if this is what you expected? |
This is a great time to revisit this, just as a new cycle is about to begin. |
Sure, so the issue is related to 3 methods - receivable, accounts_receivable and wallet_receivable, in which we apply pretty similar algorithm. In current solution we iterate through pending blocks and build result tree applying conditional logic based on input parameters. Then we sort the tree (if necessary - according to request parameter). This causes a few problems:
The solution that Colin suggested is based on strategy pattern, so instead of applying conditions on each iteration we define the behaviour in a separate class. In fact I created two levels of strategy - the behavioral one in which we define how we handle the blocks (do we sort them, do we apply count parameter, etc.) - this is So the RPC method implentations are pretty simple now:
This solution solves problems described above. Maybe the disadvantage is the number of additional classes to maintain? Maybe we should move them to separate namespace? Anyway I don't consider this as final PR, I would like to add more test cases but first I wanted to hear your opinion if this is a good direction at all. Hope it's understandable, let me know if you need further clarifications. |
With the upcoming upgrade to C++20 this feature could be implemented using the new |
@JerzyStanislawski would you like to redo this using the C++20 ranges library as suggested by Piotr? If yes, before, before you start coding, let's have a meeting to decide the architecture of the solution, so we are all on the same page. |
Yeah, happy to rework that. My initial understanding is that we should first filter |
Hi @JerzyStanislawski, the develop branch in now using C++20 and we can start using the ranges library of C++20 to implement this. |
The plan is to add 2 wrapper classes around pending_store to enable interaction with ranges. They must satisfy concepts required by ranges, something like:
So viewable_pending_store.begin() will internally call pending_store.begin() and construct pending_store_iterator_wrapper from nano::store_iterator<nano::pending_key, nano::pending_info> returned by pending_store. Once we have that we should be ready to use viewable_pending_store with ranges. We'll probably pipe operations according to input parameters:
I don't have strict plan on how to use ranges in receivable methods yet, first I'd like to code PoC with wrapper classes to ensure it works. |
I wouldn't worry about making a dedicated wrapper for each store, we need to have the results in memory anyway for sorting and serialization. Simply creating a |
And if you want to adapt an existing store to work with ranges, maybe using the standard subrange class will do, like this |
There is a lot of overhead with adapting so I'd be happy to avoid that, but can we afford that in terms of performance? |
Using the The rest of the code (filtering/sorting/transforming) should be the same regardless of the choice here, so it should be possible to easily profile those two approaches and see if there is any meaningful difference. |
ecb1ec6
to
b37c3c7
Compare
Until now accounts_receivable RPC command retrieved only first
count
blocks, then sorted them by amount. The correct flow is to get all the receivable blocks, sort them by amount and apply count at the end, which this PR is about.Related issue: #3801