Skip to content
  • eileencodes's avatar
    31461d8a
    Implement granular role and shard swapping · 31461d8a
    eileencodes authored
    
    
    This change allows for a connection to be swapped on role or shard for a
    class. Previously calling `connected_to` would swap all the connections
    to a particular role or shard. Granular connection swapping is useful
    for swapping one connection to reading while leaving all other
    connection classes on writing.
    
    The public methods on connection handler have been updated to behave the
    same as they did previously on the different handlers. The difference
    however is instead of calling
    `ActiveRecord::Base.connection_handlers[:reading].clear_all_connections!`
    you now call
    `ActiveRecord::Base.connection_handler.clear_all_connections!` which
    will clear based on current role set by a `connected_to` block. Outside
    the context of a `connected_to` block, `clear_all_connections!` can take
    an optional parameter to clear specific connections by role.
    
    The major changes in this PR are:
    
    * We introduced a `legacy_connection_handling` configuration option that
    is set to true by default. It will be set to `false` for all new
    applications.
    * In the new connection handling there will be one only connection
    handler. Previously there was a connection handler for each role. Now
    the role is stored in the `PoolManager`. In order to maintain backwards
    compatibility we introduced a `LegacyPoolManager` to avoid duplicate
    conditionals. See diagram in PR body for changes to connection
    management.
    * `connected_to` will now use a stacked concurrent map to keep track of
    the connection for each class. For each opened block the `class`,
    `role`, and `shard` will be added to the stack, when the block is exited
    the `class`, `role`, `shard` array will be removed from the stack.
    * With these changes `ActiveRecord::Base.connected_to` will remain
    global. If called all connections in the block will use the `role` and
    `shard` that was switched to. If called with a parent class like
    `AnimalsRecord.connected_to` only models under `AnimalsRecord` will be
    switched and everything else will remain the same.
    
    Examples:
    
    Given an application we have a `User` model that inherits from
    `ApplicationRecord` and a `Dog` model that inherits from
    `AnimalsRecord`. `AnimalsRecord` and `ApplicationRecord` have writing
    and reading connections as well as shard `default`, `one`, and `two`.
    
    ```ruby
    ActiveRecord::Base.connected_to(role: :reading) do
      User.first # reads from default replica
      Dog.first # reads from default replica
    
      AnimalsRecord.connected_to(role: :writing, shard: :one) do
        User.first # reads from default replica
        Dog.first # reads from shard one primary
      end
    
      User.first # reads from default replica
      Dog.first # reads from default replica
    
      ApplicationRecord.connected_to(role: :writing, shard: :two) do
        User.first # reads from shard two primary
        Dog.first # reads from default replica
      end
    end
    ```
    
    Things this PR does not solve:
    
    * Currently there is no API for swapping more than one but not all
    connections. Apps with many primaries may want to swap 3 but not all 10
    connections. We plan to build an API for that in a followup PR.
    * The middleware remains the same and is using the global switching
    methods. Therefore at this time to use this new feature applications
    must manually switch connections. We will also address this in a
    followup PR.
    * The `schema_cache` is currently on the `PoolConfig`. We plan on trying
    to move this up to the `PoolManager` or elsewhere later on so each
    `PoolConfig` doesn't need to hold a reference to the `schema_cache`.
    
    Co-authored-by: default avatarJohn Crepezzi <john.crepezzi@gmail.com>
    31461d8a
    Implement granular role and shard swapping
    eileencodes authored
    
    
    This change allows for a connection to be swapped on role or shard for a
    class. Previously calling `connected_to` would swap all the connections
    to a particular role or shard. Granular connection swapping is useful
    for swapping one connection to reading while leaving all other
    connection classes on writing.
    
    The public methods on connection handler have been updated to behave the
    same as they did previously on the different handlers. The difference
    however is instead of calling
    `ActiveRecord::Base.connection_handlers[:reading].clear_all_connections!`
    you now call
    `ActiveRecord::Base.connection_handler.clear_all_connections!` which
    will clear based on current role set by a `connected_to` block. Outside
    the context of a `connected_to` block, `clear_all_connections!` can take
    an optional parameter to clear specific connections by role.
    
    The major changes in this PR are:
    
    * We introduced a `legacy_connection_handling` configuration option that
    is set to true by default. It will be set to `false` for all new
    applications.
    * In the new connection handling there will be one only connection
    handler. Previously there was a connection handler for each role. Now
    the role is stored in the `PoolManager`. In order to maintain backwards
    compatibility we introduced a `LegacyPoolManager` to avoid duplicate
    conditionals. See diagram in PR body for changes to connection
    management.
    * `connected_to` will now use a stacked concurrent map to keep track of
    the connection for each class. For each opened block the `class`,
    `role`, and `shard` will be added to the stack, when the block is exited
    the `class`, `role`, `shard` array will be removed from the stack.
    * With these changes `ActiveRecord::Base.connected_to` will remain
    global. If called all connections in the block will use the `role` and
    `shard` that was switched to. If called with a parent class like
    `AnimalsRecord.connected_to` only models under `AnimalsRecord` will be
    switched and everything else will remain the same.
    
    Examples:
    
    Given an application we have a `User` model that inherits from
    `ApplicationRecord` and a `Dog` model that inherits from
    `AnimalsRecord`. `AnimalsRecord` and `ApplicationRecord` have writing
    and reading connections as well as shard `default`, `one`, and `two`.
    
    ```ruby
    ActiveRecord::Base.connected_to(role: :reading) do
      User.first # reads from default replica
      Dog.first # reads from default replica
    
      AnimalsRecord.connected_to(role: :writing, shard: :one) do
        User.first # reads from default replica
        Dog.first # reads from shard one primary
      end
    
      User.first # reads from default replica
      Dog.first # reads from default replica
    
      ApplicationRecord.connected_to(role: :writing, shard: :two) do
        User.first # reads from shard two primary
        Dog.first # reads from default replica
      end
    end
    ```
    
    Things this PR does not solve:
    
    * Currently there is no API for swapping more than one but not all
    connections. Apps with many primaries may want to swap 3 but not all 10
    connections. We plan to build an API for that in a followup PR.
    * The middleware remains the same and is using the global switching
    methods. Therefore at this time to use this new feature applications
    must manually switch connections. We will also address this in a
    followup PR.
    * The `schema_cache` is currently on the `PoolConfig`. We plan on trying
    to move this up to the `PoolManager` or elsewhere later on so each
    `PoolConfig` doesn't need to hold a reference to the `schema_cache`.
    
    Co-authored-by: default avatarJohn Crepezzi <john.crepezzi@gmail.com>
Loading