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

Add ReadySet.exclude method to exclude queries from being routed #37

Open
ethan-readyset opened this issue Nov 30, 2023 · 1 comment
Open
Assignees

Comments

@ethan-readyset
Copy link
Contributor

ethan-readyset commented Nov 30, 2023

Brief Description of Feature:
We should add a Readyset.exclude method that allows users to annotate blocks of code that exist within a Readyset.route block from being routed to ReadySet.

Background:
It is convenient to be able to route all the queries in given controller actions or blocks of code to ReadySet; however, there may be one or two queries that should not be routed to ReadySet (e.g. queries that are unsupported by ReadySet). In such cases, a Readyset.exclude method would provide a way to ensure that the queries are routed to the database instead.

From the user's perspective:
In a given Readyset.route block, a user would annotate the unsupported query with Readyset.exclude:

Readyset.route do
  users = User.where(active: true)

  # ReadySet does not support HAVING
  orders = Readyset.exclude do
    Order.select("created_at, sum(total) as total_price").group("created_at").
      having("sum(total) > ?", 200)
  end
end

Optional information to add

Proposed Solution:
This gets a bit tricky. Internally, Readyset.route will be using ActiveRecord::Base.connected_to to route queries to ReadySet. In our definition of Readyset.route, we'll need to store the prior role, shard, and preventing_writes associated with the connection that was being used before we connected to ReadySet. I believe we can do this by invoking:

  • ActiveRecord::Base.current_role
  • ActiveRecord::Base.current_shard
  • ActiveRecord::Base.current_preventing_writes

We will need to store this information thread-locally. Luckily, ActiveSupport::CurrentAttributes provides such an interface for us to exploit. We can subclass this class and expose an attribute for the above information. When the exclude block exits, we'll reset the information to nil. We may want to use an ensure to be sure that the state is reset upon an error.

It would be a runtime error to call Readyset.exclude outside of the context of a connection to ReadySet.

@ethan-readyset ethan-readyset added this to the Milestone 1 milestone Nov 30, 2023
@ethan-readyset ethan-readyset self-assigned this Dec 6, 2023
@ethan-readyset
Copy link
Contributor Author

ethan-readyset commented Dec 11, 2023

I think I'm going to hold off on doing this for now. It's not as simple as invoking ActiveRecord::Base.current_role, etc. because different subclasses of ActiveRecord::Base can be connected to different shards/roles. We'd need to store connection state for every abstract subclass of ActiveRecord::Base whenever we invoke Readyset.route in order to reproduce the state of the world when call Readyset.exclude in order to ensure that we correctly "undo" the outer call to Readyset.route.

This is technically possible, but I think it has the potential to get pretty hairy and error-prone. Given that users have many workarounds (using Readyset.route more granularly, manually wrapping queries in connected_to blocks, etc.), I think we should deprioritize this until we get an explicit ask for it.

@ethan-readyset ethan-readyset removed this from the Milestone 1 milestone Dec 11, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant