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

khepri_tree: Add an option to accumulate keep-while expirations #301

Closed
wants to merge 1 commit into from

Conversation

the-mikedavis
Copy link
Member

This change adds a type khepri:delete_options() similar to khepri:put_options() which includes a new option return_keep_while_expirations. Setting this option in a call to khepri_adv:delete_many/3 or khepri_tx_adv:delete_many/2 will include any tree nodes deleted because of expired keep-while conditions in the khepri_adv:many_results() return value.

In order to accumulate these deletions we need to modify khepri_machine:remove_expired_nodes/2 slightly to pass the fold function and accumulator for the current #walk{..}.

This option is useful for callers if they need to do something with any tree nodes which were deleted by keep-while conditions. For example in RabbitMQ we currently delete all bindings which are associated with a queue in a transaction. Bindings should always be removed when their associated queue is removed so they seem like an ideal case to use a keep-while condition. We send notifications and invoke callbacks with the set of bindings which were deleted though, so we need to return these deleted records. With this change we can avoid performing binding deletion in a transaction and instead only delete the queue record while providing the new option. This is significantly more efficient because of the way the Khepri store is organized in RabbitMQ.

This change adds a type `khepri:delete_options()` similar to
`khepri:put_options()` which includes a new option
`return_keep_while_expirations`. Setting this option in a call to
`khepri_adv:delete_many/3` or `khepri_tx_adv:delete_many/2` will include
any tree nodes deleted because of expired keep-while conditions in the
`khepri_adv:many_results()` return value.

In order to accumulate these deletions we need to modify
`khepri_machine:remove_expired_nodes/2` slightly to pass the fold
function and accumulator for the current `#walk{..}`.

This option is useful for callers if they need to do something with any
tree nodes which were deleted by keep-while conditions. For example in
RabbitMQ we currently delete all bindings which are associated with a
queue in a transaction. Bindings should always be removed when their
associated queue is removed so they seem like an ideal case to use a
keep-while condition. We send notifications and invoke callbacks with
the set of bindings which were deleted though, so we need to return
these deleted records. With this change we can avoid performing binding
deletion in a transaction and instead only delete the queue record while
providing the new option. This is significantly more efficient because
of the way the Khepri store is organized in RabbitMQ.
@the-mikedavis the-mikedavis added the enhancement New feature or request label Oct 7, 2024
@the-mikedavis the-mikedavis added this to the v0.17.0 milestone Oct 7, 2024
@the-mikedavis the-mikedavis self-assigned this Oct 7, 2024
Copy link

codecov bot commented Oct 7, 2024

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 89.71%. Comparing base (a69d642) to head (0d3b3f5).
Report is 10 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #301      +/-   ##
==========================================
+ Coverage   89.60%   89.71%   +0.10%     
==========================================
  Files          22       22              
  Lines        3251     3256       +5     
==========================================
+ Hits         2913     2921       +8     
+ Misses        338      335       -3     
Flag Coverage Δ
erlang-25 88.97% <100.00%> (+0.44%) ⬆️
erlang-26 89.31% <100.00%> (+0.23%) ⬆️
erlang-27 89.64% <100.00%> (+0.10%) ⬆️
os-ubuntu-latest 89.71% <100.00%> (+0.32%) ⬆️
os-windows-latest 89.43% <100.00%> (-0.14%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@the-mikedavis the-mikedavis marked this pull request as ready for review October 7, 2024 13:05
@the-mikedavis
Copy link
Member Author

Expanding a bit on the efficiency aspect for the use-case in RabbitMQ...

When deleting a queue we call rabbit_db_binding:delete_for_destination_in_khepri/2 to remove any bindings that lead to the queue. This uses khepri_tx_adv:delete_many/2 with a path pattern like so:

[rabbitmq,
 vhosts, VHostName,
 exchanges, _SrcName = ?KHEPRI_WILDCARD_STAR,
 bindings, queue, QName, _RoutingKey = ?KHEPRI_WILDCARD_STAR]

So in order to find all bindings that lead to a given queue we need to search each exchange's subtree even if the exchange has no bindings. It is fast to walk down each subtree individually but the cost becomes noticeable when the metadata store has many exchanges and you attempt to delete many queues. For example deleting a vhost that has 100k exchanges is fast, deleting a vhost with 100k queues is pretty fast, but deleting a vhost with both 100k exchanges and 100k queues is very slow. (Note that vhost deletion currently deletes queues and then exchanges.)

Using keep-while conditions to clean up these bindings means that we use the prefix tree in #298 instead which is a much more efficient lookup because the prefix tree associates the queue directly to all bindings that need to be deleted. Complexity-wise the time taken to delete a queue is currently proportional to the number of exchanges in the queue's vhost but with this change we can make the time taken proportional only to the number of bindings which point to the queue.

@dumbbell
Copy link
Member

dumbbell commented Oct 7, 2024

Thank you, I like that we get a way to get all nodes deleted by keep_while conditions.

However, I’m undecided on the use of an option.

First, I think I prefer if we have a single behavior because it’s easier for users to reason about and reduce the complexity of the library code.

Second, a put could trigger the delete of tree nodes too if a keep_while conditions becomes false, so if we have an option, it should be accepted by "put" operations as well. In this case it’s less obvious how it should be communicated to the caller.

I need to think about this.

@the-mikedavis
Copy link
Member Author

Superseded by #305

@the-mikedavis the-mikedavis deleted the md/return-keep-while-expirations branch October 15, 2024 21:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants