Research
Security News
Malicious npm Packages Inject SSH Backdoors via Typosquatted Libraries
Socket’s threat research team has detected six malicious npm packages typosquatting popular libraries to insert SSH backdoors.
Redirect ActiveRecord (Rails) reads to slave databases while ensuring all writes go to the master database.
active_record_slave redirects all database reads to slave instances while ensuring that all writes go to the master database. active_record_slave ensures that any reads that are performed within a database transaction are by default directed to the master database to ensure data consistency.
Production Ready. Actively used in large production environments.
Slave:
to indicate which SQL statements are going
to the slave database.# Read from the slave database
r = Role.where(name: 'manager').first
r.description = 'Manager'
# Save changes back to the master database
r.save!
Log file output:
03-13-12 05:56:05 pm,[2608],b[0],[0], Slave: Role Load (3.0ms) SELECT `roles`.* FROM `roles` WHERE `roles`.`name` = 'manager' LIMIT 1
03-13-12 05:56:22 pm,[2608],b[0],[0], AREL (12.0ms) UPDATE `roles` SET `description` = 'Manager' WHERE `roles`.`id` = 5
Role.transaction do
r = Role.where(name: 'manager').first
r.description = 'Manager'
r.save!
end
Log file output:
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
03-13-12 06:02:09 pm,[2608],b[0],[0], AREL (2.0ms) UPDATE `roles` SET `description` = 'Manager' WHERE `roles`.`id` = 4
Sometimes it is necessary to read from the master:
ActiveRecordSlave.read_from_master do
r = Role.where(name: 'manager').first
end
Delete all executes against the master database since it is only a delete:
D, [2012-11-06T19:47:29.125932 #89772] DEBUG -- : SQL (1.0ms) DELETE FROM "users"
First performs a read against the slave database and then deletes the corresponding data from the master
D, [2012-11-06T19:43:26.890674 #89002] DEBUG -- : Slave: User Load (0.1ms) SELECT "users".* FROM "users"
D, [2012-11-06T19:43:26.890972 #89002] DEBUG -- : (0.0ms) begin transaction
D, [2012-11-06T19:43:26.891667 #89002] DEBUG -- : SQL (0.4ms) DELETE FROM "users" WHERE "users"."id" = ? [["id", 3]]
D, [2012-11-06T19:43:26.892697 #89002] DEBUG -- : (0.9ms) commit transaction
By default ActiveRecordSlave detects when a call is inside a transaction and will send all reads to the master when a transaction is active.
It is now possible to send reads to database slaves and ignore whether currently inside a transaction:
In file config/application.rb:
# Read from slave even when in an active transaction
config.active_record_slave.ignore_transactions = true
It is important to identify any code in the application that depends on being
able to read any changes already part of the transaction, but not yet committed
and wrap those reads with ActiveRecordSlave.read_from_master
Inquiry.transaction do
# Create a new inquiry
Inquiry.create
# The above inquiry is not visible yet if already in a Rails transaction.
# Use `read_from_master` to ensure it is included in the count below:
ActiveRecordSlave.read_from_master do
count = Inquiry.count
end
end
Active Record Slave is a very simple layer that inserts itself into the call chain whenever a slave is configured.
By observation we noticed that all reads are made to a select set of methods and
all writes are made directly to one method: execute
.
Using this observation Active Record Slave only needs to intercept calls to the known select apis:
Calls to the above methods are redirected to the slave active record model ActiveRecordSlave::Slave
.
This model is 100% managed by the regular Active Record mechanisms such as connection pools etc.
This lightweight approach ensures that all calls to the above API's are redirected to the slave without impacting:
execute
One of the limitations with this approach is that any code that performs a query by calling execute
direct will not
be redirected to the slave instance. In this case replace the use of execute
with one of the the above select methods.
dependent: destroy
When performing in-memory only model assignments Active Record will create a transaction against the master even though the transaction may never be used.
Even though the transaction is unused it sends the following messages to the master database:
SET autocommit=0
commit
SET autocommit=1
This will impact the master database if sufficient calls are made, such as in batch workers.
For Example:
class Parent < ActiveRecord::Base
has_one :child, dependent: :destroy
end
class Child < ActiveRecord::Base
belongs_to :parent
end
# The following code will create an unused transaction against the master, even when reads are going to slaves:
parent = Parent.new
parent.child = Child.new
If the dependent: :destroy
is removed it no longer creates a transaction, but it also means dependents are not
destroyed when a parent is destroyed.
For this scenario when we are 100% confident no writes are being performed the following can be performed to ignore any attempt Active Record makes at creating the transaction:
ActiveRecordSlave.skip_transactions do
parent = Parent.new
parent.child = Child.new
end
To help identify any code within a block that is creating transactions, wrap the code with
ActiveRecordSlave.block_transactions
to make it raise an exception anytime a transaction is attempted:
ActiveRecordSlave.block_transactions do
parent = Parent.new
parent.child = Child.new
end
Add to Gemfile
gem 'active_record_slave'
Run bundler to install:
bundle
Or, without Bundler:
gem install active_record_slave
To enable slave reads for any environment just add a slave: entry to database.yml along with all the usual ActiveRecord database configuration options.
For Example:
production:
database: production
username: username
password: password
encoding: utf8
adapter: mysql
host: master1
pool: 50
slave:
database: production
username: username
password: password
encoding: utf8
adapter: mysql
host: slave1
pool: 50
Sometimes it is useful to turn on slave reads per host, for example to activate slave reads only on the linux host 'batch':
production:
database: production
username: username
password: password
encoding: utf8
adapter: mysql
host: master1
pool: 50
<% if `hostname`.strip == 'batch' %>
slave:
database: production
username: username
password: password
encoding: utf8
adapter: mysql
host: slave1
pool: 50
<% end %>
If there are multiple slaves, it is possible to randomly select a slave on startup to balance the load across the slaves:
production:
database: production
username: username
password: password
encoding: utf8
adapter: mysql
host: master1
pool: 50
slave:
database: production
username: username
password: password
encoding: utf8
adapter: mysql
host: <%= %w(slave1 slave2 slave3).sample %>
pool: 50
Slaves can also be assigned to specific hosts by using the hostname:
production:
database: production
username: username
password: password
encoding: utf8
adapter: mysql
host: master1
pool: 50
slave:
database: production
username: username
password: password
encoding: utf8
adapter: mysql
host: <%= `hostname`.strip == 'app1' ? 'slave1' : 'slave2' %>
pool: 50
See .travis.yml for the list of tested Ruby platforms
This project uses Semantic Versioning.
Reid Morrison :: @reidmorrison
FAQs
Unknown package
We found that active_record_slave demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Research
Security News
Socket’s threat research team has detected six malicious npm packages typosquatting popular libraries to insert SSH backdoors.
Security News
MITRE's 2024 CWE Top 25 highlights critical software vulnerabilities like XSS, SQL Injection, and CSRF, reflecting shifts due to a refined ranking methodology.
Security News
In this segment of the Risky Business podcast, Feross Aboukhadijeh and Patrick Gray discuss the challenges of tracking malware discovered in open source softare.