{"id":20481173,"url":"https://github.com/teespring-labs/active_record_replica","last_synced_at":"2026-04-06T00:03:43.792Z","repository":{"id":2715645,"uuid":"3710125","full_name":"teespring-labs/active_record_replica","owner":"teespring-labs","description":"Redirect ActiveRecord (Rails) reads to replica databases while ensuring all writes go to the primary database.","archived":false,"fork":false,"pushed_at":"2024-04-24T19:56:37.000Z","size":77,"stargazers_count":164,"open_issues_count":2,"forks_count":26,"subscribers_count":33,"default_branch":"master","last_synced_at":"2025-05-03T19:48:54.650Z","etag":null,"topics":["activerecord","mysql-replication","rails","replica","ruby"],"latest_commit_sha":null,"homepage":"","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/teespring-labs.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2012-03-13T18:59:58.000Z","updated_at":"2025-02-22T05:27:32.000Z","dependencies_parsed_at":"2024-06-21T12:57:43.142Z","dependency_job_id":"5bbd333f-1348-4e7b-933a-2b1ac9cedec8","html_url":"https://github.com/teespring-labs/active_record_replica","commit_stats":null,"previous_names":["rocketjob/active_record_slave","teespring/active_record_replica"],"tags_count":8,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/teespring-labs%2Factive_record_replica","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/teespring-labs%2Factive_record_replica/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/teespring-labs%2Factive_record_replica/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/teespring-labs%2Factive_record_replica/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/teespring-labs","download_url":"https://codeload.github.com/teespring-labs/active_record_replica/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":252961258,"owners_count":21832182,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["activerecord","mysql-replication","rails","replica","ruby"],"created_at":"2024-11-15T16:07:13.527Z","updated_at":"2026-04-06T00:03:43.758Z","avatar_url":"https://github.com/teespring-labs.png","language":"Ruby","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Active Record Replica\n[![Gem Version](https://img.shields.io/gem/v/active_record_replica.svg)](https://rubygems.org/gems/active_record_replica)\n[![Build Status](https://travis-ci.org/teespring/active_record_replica.svg?branch=master)](https://travis-ci.org/teespring/active_record_replica)\n[![License](https://img.shields.io/badge/license-Apache%202.0-brightgreen.svg)](http://opensource.org/licenses/Apache-2.0)\n![](https://img.shields.io/badge/status-Production%20Ready-blue.svg)\n\nRedirect ActiveRecord (Rails) reads to replica databases while ensuring all writes go to the primary database.\n\n## Status\nThis is a slight modification of Rocket Job's original library, simply renaming it from `active_record_slave` to `active_record_replica`.\n\nIn order to more clearly distinguish the library from `active_record_slave`, we also incremented the major version – it is, however, functionally equivalent.\n\n## Introduction\n\n`active_record_replica` redirects all database reads to replica instances while ensuring\nthat all writes go to the primary database. `active_record_replica` ensures that\nany reads that are performed within a database transaction are by default directed to the primary\ndatabase to ensure data consistency.\n\n## Status\n\nProduction Ready. Actively used in large production environments.\n\n## Features\n\n* Redirecting reads to a single replica database.\n* Works with any database driver that works with ActiveRecord.\n* Supports all Rails 3, 4, or 5 read apis.\n    * Including dynamic finders, AREL, and ActiveRecord::Base.select.\n    * **NOTE**: In Rails 3 and 4, QueryCache is only enabled for BaseConnection by default. In Rails 5, it's enabled for all connections. [(PR)](https://github.com/rails/rails/pull/28869)\n* Transaction aware\n    * Detects when a query is inside of a transaction and sends those reads to the primary by default.\n    * Can be configured to send reads in a transaction to replica databases.\n* Lightweight footprint.\n* No overhead whatsoever when a replica is _not_ configured.\n* Negligible overhead when redirecting reads to the replica.\n* Connection Pools to both databases are retained and maintained independently by ActiveRecord.\n* The primary and replica databases do not have to be of the same type.\n    * For example Oracle could be the primary with MySQL as the replica database.\n* Debug logs include a prefix of `Replica: ` to indicate which SQL statements are going\n  to the replica database.\n\n### Example showing Replica redirected read\n\n```ruby\n# Read from the replica database\nr = Role.where(name: 'manager').first\nr.description = 'Manager'\n\n# Save changes back to the primary database\nr.save!\n```\n\nLog file output:\n\n    03-13-12 05:56:05 pm,[2608],b[0],[0],  Replica: Role Load (3.0ms)  SELECT `roles`.* FROM `roles` WHERE `roles`.`name` = 'manager' LIMIT 1\n    03-13-12 05:56:22 pm,[2608],b[0],[0],  AREL (12.0ms)  UPDATE `roles` SET `description` = 'Manager' WHERE `roles`.`id` = 5\n\n### Example showing how reads within a transaction go to the primary\n\n```ruby\nRole.transaction do\n  r = Role.where(name: 'manager').first\n  r.description = 'Manager'\n  r.save!\nend\n```\n\nLog file output:\n\n    03-13-12 06:02:09 pm,[2608],b[0],[0],  Role Load (2.0ms)  SELECT `roles`.* FROM `roles` WHERE `roles`.`name` = 'manager' LIMIT 1\n    03-13-12 06:02:09 pm,[2608],b[0],[0],  AREL (2.0ms)  UPDATE `roles` SET `description` = 'Manager' WHERE `roles`.`id` = 4\n\n### Forcing a read against the primary\n\nSometimes it is necessary to read from the primary:\n\n```ruby\nActiveRecordReplica.read_from_primary do\n  r = Role.where(name: 'manager').first\nend\n```\n\n## Usage Notes\n\n### delete_all\n\nDelete all executes against the primary database since it is only a delete:\n\n```\nD, [2012-11-06T19:47:29.125932 #89772] DEBUG -- :   SQL (1.0ms)  DELETE FROM \"users\"\n```\n\n### destroy_all\n\nFirst performs a read against the replica database and then deletes the corresponding\ndata from the primary\n\n```\nD, [2012-11-06T19:43:26.890674 #89002] DEBUG -- :   Replica: User Load (0.1ms)  SELECT \"users\".* FROM \"users\"\nD, [2012-11-06T19:43:26.890972 #89002] DEBUG -- :    (0.0ms)  begin transaction\nD, [2012-11-06T19:43:26.891667 #89002] DEBUG -- :   SQL (0.4ms)  DELETE FROM \"users\" WHERE \"users\".\"id\" = ?  [[\"id\", 3]]\nD, [2012-11-06T19:43:26.892697 #89002] DEBUG -- :    (0.9ms)  commit transaction\n```\n\n## Transactions\n\nBy default ActiveRecordReplica detects when a call is inside a transaction and will\nsend all reads to the _primary_ when a transaction is active.\n\nIt is now possible to send reads to database replicas and ignore whether currently\ninside a transaction:\n\nIn file config/application.rb:\n\n```ruby\n# Read from replica even when in an active transaction\nconfig.active_record_replica.ignore_transactions = true\n```\n\nIt is important to identify any code in the application that depends on being\nable to read any changes already part of the transaction, but not yet committed\nand wrap those reads with `ActiveRecordReplica.read_from_primary`\n\n```ruby\nInquiry.transaction do\n  # Create a new inquiry\n  Inquiry.create\n\n  # The above inquiry is not visible yet if already in a Rails transaction.\n  # Use `read_from_primary` to ensure it is included in the count below:\n  ActiveRecordReplica.read_from_primary do\n    count = Inquiry.count\n  end\n\nend\n```\n\n## Note\n\n`active_record_replica` is a very simple layer that inserts itself into the call chain whenever a replica is configured.\nBy observation we noticed that all reads are made to a select set of methods and\nall writes are made directly to one method: `execute`.\n\nUsing this observation `active_record_replica` only needs to intercept calls to the known select apis:\n* select_all\n* select_one\n* select_rows\n* select_value\n* select_values\n\nCalls to the above methods are redirected to the replica active record model `ActiveRecordReplica::Replica`.\nThis model is 100% managed by the regular Active Record mechanisms such as connection pools etc.\n\nThis lightweight approach ensures that all calls to the above API's are redirected to the replica without impacting:\n* Transactions\n* Writes\n* Any SQL calls directly to `execute`\n\nOne of the limitations with this approach is that any code that performs a query by calling `execute` direct will not\nbe redirected to the replica instance. In this case replace the use of `execute` with one of the the above select methods.\n\n\n## Note when using `dependent: destroy`\n\nWhen performing in-memory only model assignments Active Record will create a transaction against the primary even though\nthe transaction may never be used.\n\nEven though the transaction is unused it sends the following messages to the primary database:\n~~\nSET autocommit=0\ncommit\nSET autocommit=1\n~~\n\nThis will impact the primary database if sufficient calls are made, such as in batch workers.\n\nFor Example:\n\n~~ruby\nclass Parent \u003c ActiveRecord::Base\n  has_one :child, dependent: :destroy\nend\n\nclass Child \u003c ActiveRecord::Base\n  belongs_to :parent\nend\n\n# The following code will create an unused transaction against the primary, even when reads are going to replicas:\nparent = Parent.new\nparent.child = Child.new\n~~\n\nIf the `dependent: :destroy` is removed it no longer creates a transaction, but it also means dependents are not\ndestroyed when a parent is destroyed.\n\nFor this scenario when we are 100% confident no writes are being performed the following can be performed to\nignore any attempt Active Record makes at creating the transaction:\n\n~~ruby\nActiveRecordReplica.skip_transactions do\n  parent = Parent.new\n  parent.child = Child.new\nend\n~~\n\nTo help identify any code within a block that is creating transactions, wrap the code with\n`ActiveRecordReplica.block_transactions` to make it raise an exception anytime a transaction is attempted:\n\n~~ruby\nActiveRecordReplica.block_transactions do\n  parent = Parent.new\n  parent.child = Child.new\nend\n~~\n\n## Install\n\nAdd to `Gemfile`\n\n```ruby\ngem 'active_record_replica'\n```\n\nRun bundler to install:\n\n```\nbundle\n```\n\nOr, without Bundler:\n\n```\ngem install active_record_replica\n```\n\n## Configuration\n\nTo enable replica reads for any environment just add a _replica:_ entry to database.yml\nalong with all the usual ActiveRecord database configuration options.\n\nFor Example:\n\n```yaml\nproduction:\n  database: production\n  username: username\n  password: password\n  encoding: utf8\n  adapter:  mysql\n  host:     primary1\n  pool:     50\n  replica:\n    database: production\n    username: username\n    password: password\n    encoding: utf8\n    adapter:  mysql\n    host:     replica1\n    pool:     50\n```\n\nSometimes it is useful to turn on replica reads per host, for example to activate\nreplica reads only on the linux host 'batch':\n\n```yaml\nproduction:\n  database: production\n  username: username\n  password: password\n  encoding: utf8\n  adapter:  mysql\n  host:     primary1\n  pool:     50\n\u003c% if `hostname`.strip == 'batch' %\u003e\n  replica:\n    database: production\n    username: username\n    password: password\n    encoding: utf8\n    adapter:  mysql\n    host:     replica1\n    pool:     50\n\u003c% end %\u003e\n```\n\nIf there are multiple replicas, it is possible to randomly select a replica on startup\nto balance the load across the replicas:\n\n```yaml\nproduction:\n  database: production\n  username: username\n  password: password\n  encoding: utf8\n  adapter:  mysql\n  host:     primary1\n  pool:     50\n  replica:\n    database: production\n    username: username\n    password: password\n    encoding: utf8\n    adapter:  mysql\n    host:     \u003c%= %w(replica1 replica2 replica3).sample %\u003e\n    pool:     50\n```\n\nReplicas can also be assigned to specific hosts by using the hostname:\n\n```yaml\nproduction:\n  database: production\n  username: username\n  password: password\n  encoding: utf8\n  adapter:  mysql\n  host:     primary1\n  pool:     50\n  replica:\n    database: production\n    username: username\n    password: password\n    encoding: utf8\n    adapter:  mysql\n    host:     \u003c%= `hostname`.strip == 'app1' ? 'replica1' : 'replica2' %\u003e\n    pool:     50\n```\n\n## Set primary as default for Read\n\nThe default behavior can also set to read/write operations against primary database.\n\nCreate an initializer file config/initializer/active_record_replica.rb to force read from primary:\n\n```yaml\n    ActiveRecordReplica.read_from_primary!\n```\n\nThen use this method and supply block to read from the replica database:\n\n```yaml\nActiveRecordReplica.read_from_replica do\n   User.count\nend\n```\n\n## Dependencies\n\nSee [.travis.yml](https://github.com/reidmorrison/active_record_replica/blob/master/.travis.yml) for the list of tested Ruby platforms\n\n## Versioning\n\nThis project uses [Semantic Versioning](http://semver.org/).\n\n## Contributing\n\n1. Fork repository in Github.\n\n2. Checkout your forked repository:\n\n    ```bash\n    git clone https://github.com/your_github_username/active_record_replica.git\n    cd active_record_replica\n    ```\n\n3. Create branch for your contribution:\n\n    ```bash\n    git co -b your_new_branch_name\n    ```\n\n4. Make code changes.\n\n5. Ensure tests pass.\n\n6. Push to your fork origin.\n\n    ```bash\n    git push origin\n    ```\n\n7. Submit PR from the branch on your fork in Github.\n\n## Author\n\n[Reid Morrison](https://github.com/reidmorrison) :: @reidmorrison\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fteespring-labs%2Factive_record_replica","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fteespring-labs%2Factive_record_replica","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fteespring-labs%2Factive_record_replica/lists"}