Skip to content

Commit

Permalink
Project rename so we can cover additional adapters in future
Browse files Browse the repository at this point in the history
  • Loading branch information
lloydwatkin committed Mar 28, 2024
1 parent d70a6b3 commit 0c76ba5
Show file tree
Hide file tree
Showing 15 changed files with 70 additions and 68 deletions.
6 changes: 6 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
version: 2
updates:
- package-ecosystem: "bundler"
directory: "/"
schedule:
interval: "weekly"
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ jobs:
image: mysql:5.7
env:
MYSQL_ALLOW_EMPTY_PASSWORD: yes
MYSQL_DATABASE: mysql2_split_test
MYSQL_DATABASE: janus_test
ports:
- 3306:3306
options: >-
Expand Down
6 changes: 2 additions & 4 deletions Gemfile.lock
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
PATH
remote: .
specs:
mysql2-split (0.1.0)
forwardable (~> 1)
janus-ar (0.1.0)

GEM
remote: http://rubygems.org/
Expand Down Expand Up @@ -30,7 +29,6 @@ GEM
connection_pool (2.4.1)
diff-lcs (1.5.1)
drb (2.2.1)
forwardable (1.3.3)
i18n (1.14.4)
concurrent-ruby (~> 1.0)
json (2.7.1)
Expand Down Expand Up @@ -92,8 +90,8 @@ PLATFORMS
DEPENDENCIES
activerecord (>= 7.1.0)
activesupport (>= 7.1.0)
janus-ar!
mysql2
mysql2-split!
rake
rspec (~> 3)
rubocop (~> 1.62.0)
Expand Down
22 changes: 11 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
# MySQL2 Split

![Build Status](https://github.com/olioex/mysql2-split/workflows/ci/badge.svg)
![Build Status](https://github.com/olioex/janus-ar/workflows/ci/badge.svg)

MySQL2Split is generic primary/replica proxy for ActiveRecord 7.1+ and MySQL. It handles the switching of connections between primary and replica database servers. It comes with an ActiveRecord database adapter implementation.

Mysql2Split is heavily inspired by [Makara](https://github.com/instacart/makara) from TaskRabbit and then Instacart. Unfortunately this project is unmaintained and broke for us with Rails 7.1. This is an attempt to start afresh on the project. It is definitely not as fully featured as Makara at this stage.
Janus is heavily inspired by [Makara](https://github.com/instacart/makara) from TaskRabbit and then Instacart. Unfortunately this project is unmaintained and broke for us with Rails 7.1. This is an attempt to start afresh on the project. It is definitely not as fully featured as Makara at this stage.

## Installation

Use the current version of the gem from [rubygems](https://rubygems.org/gems/makara) in your `Gemfile`.
Use the current version of the gem from [rubygems](https://rubygems.org/gems/janus-ar) in your `Gemfile`.

```ruby
gem 'mysql2-split'
gem 'janus-ar'
```

This project assumes that your read/write endpoints are handled by a separate system (e.g. DNS).
Expand All @@ -26,8 +26,8 @@ Update your **database.yml** as follows:

```yml
development:
adapter: mysql2_split
mysql2_split:
adapter: janus_mysql2
janus:
primary:
<<: *default
database: database_name
Expand All @@ -48,21 +48,21 @@ in systems such as sidekiq, for instance.
If you need to clear the current context, releasing any stuck connections, all you have to do is:
```ruby
Mysql2Split::Context.release_all
Janus::Context.release_all
```

#### Forcing connection to primary server

```ruby
Mysql2Split::Context.stick_to_primary
Janus::Context.stick_to_primary
```

### Logging

You can set a logger instance to ::Mysql2Split::Logging::Logger.logger and Mysql2Split.
You can set a logger instance to ::Janus::Logging::Logger.logger and Janus.

```ruby
Mysql2Split::Logging::Logger.logger = ::Logger.new(STDOUT)
Janus::Logging::Logger.logger = ::Logger.new(STDOUT)
```

### What queries goes where?
Expand All @@ -71,6 +71,6 @@ In general: Any `SELECT` statements will execute against your replica(s), anythi

There are some edge cases:
* `SET` operations will be sent to all connections
* Execution of specific methods such as `connect!`, `disconnect!`, and `clear_cache!` are invoked on all underlying connections
* Execution of specific methods such as `connect!`, `disconnect!`, `reconnect!`, and `clear_cache!` are invoked on all underlying connections
* Calls inside a transaction will always be sent to the primary (otherwise changes from within the transaction could not be read back on most transaction isolation levels)
* Locking reads (e.g. `SELECT ... FOR UPDATE`) will always be sent to the primary
12 changes: 5 additions & 7 deletions mysql2-split.gemspec → janus-ar.gemspec
Original file line number Diff line number Diff line change
@@ -1,28 +1,26 @@
# frozen_string_literal: true

require File.expand_path('lib/mysql2_split/version.rb', __dir__)
require File.expand_path('lib/janus/version.rb', __dir__)

Gem::Specification.new do |gem|
gem.authors = ['Lloyd Watkin']
gem.email = ['lloyd@olioex.com']
gem.description = 'Read/Write proxy for ActiveRecord using primary/replca databases'
gem.summary = 'Read/Write proxy for ActiveRecord using primary/replca databases'
gem.homepage = 'https://github.com/olioex/mysql2-split'
gem.homepage = 'https://github.com/olioex/janus-ar'
gem.licenses = ['MIT']
gem.metadata = {
'source_code_uri' => 'https://github.com/olioex/mysql2-split'
'source_code_uri' => 'https://github.com/olioex/janus-ar'
}

gem.files = `git ls-files`.split($OUTPUT_RECORD_SEPARATOR)
gem.executables = gem.files.grep(%r{^bin/}).map { |f| File.basename(f) }
gem.name = 'mysql2-split'
gem.name = 'janus-ar'
gem.require_paths = ['lib']
gem.version = Mysql2Split::VERSION
gem.version = Janus::VERSION

gem.required_ruby_version = '>= 3.2.0'

gem.add_dependency 'forwardable', '~> 1'

gem.add_development_dependency 'activerecord', '>= 7.1.0'
gem.add_development_dependency 'activesupport', '>= 7.1.0'
gem.add_development_dependency 'mysql2'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,19 @@

require 'active_record/connection_adapters/abstract_adapter'
require 'active_record/connection_adapters/mysql2_adapter'
require_relative '../../mysql2_split'
require_relative '../../janus'

module ActiveRecord
module ConnectionHandling
def mysql2_split_connection(config)
ActiveRecord::ConnectionAdapters::Mysql2SplitAdapter.new(config)
def janus_mysql2_connection(config)
ActiveRecord::ConnectionAdapters::JanusMysql2Adapter.new(config)
end
end
end

module ActiveRecord
module ConnectionAdapters
class Mysql2SplitAdapter < ActiveRecord::ConnectionAdapters::Mysql2Adapter
class JanusMysql2Adapter < ActiveRecord::ConnectionAdapters::Mysql2Adapter
SQL_PRIMARY_MATCHERS = [
/\A\s*select.+for update\Z/i, /select.+lock in share mode\Z/i,
/\A\s*select.+(nextval|currval|lastval|get_lock|release_lock|pg_advisory_lock|pg_advisory_unlock)\(/i,
Expand All @@ -25,8 +25,8 @@ class Mysql2SplitAdapter < ActiveRecord::ConnectionAdapters::Mysql2Adapter
SQL_SKIP_ALL_MATCHERS = [/\A\s*set\s+local\s/i].freeze

def initialize(*args)
@replica_config = args[0][:mysql2_split]['replica']
args[0] = args[0][:mysql2_split]['primary']
@replica_config = args[0][:janus]['replica']
args[0] = args[0][:janus]['primary']

super(*args)
@connection_parameters ||= args[0]
Expand All @@ -40,8 +40,8 @@ def execute(sql)
end
return send_to_replica(sql, connection: :replica, method: :execute) if can_go_to_replica?(sql)

Mysql2Split::Context.stick_to_primary if write_query?(sql)
Mysql2Split::Context.used_connection(:primary)
Janus::Context.stick_to_primary if write_query?(sql)
Janus::Context.used_connection(:primary)

super(sql)
end
Expand All @@ -53,8 +53,8 @@ def execute_and_free(sql, name = nil, async: false) # :nodoc:#
end
return send_to_replica(sql, connection: :replica) if can_go_to_replica?(sql)

Mysql2Split::Context.stick_to_primary if write_query?(sql)
Mysql2Split::Context.used_connection(:primary)
Janus::Context.stick_to_primary if write_query?(sql)
Janus::Context.used_connection(:primary)

super(sql, name, async:)
end
Expand Down Expand Up @@ -86,15 +86,15 @@ def should_send_to_all?(sql)
end

def can_go_to_replica?(sql)
return false if Mysql2Split::Context.use_primary? ||
return false if Janus::Context.use_primary? ||
open_transactions.positive? ||
SQL_PRIMARY_MATCHERS.any? { |matcher| sql =~ matcher }

true
end

def send_to_replica(sql, connection: nil, method: :exec_query)
Mysql2Split::Context.used_connection(connection) if connection
Janus::Context.used_connection(connection) if connection
if method == :execute
replica_connection.execute(sql)
else
Expand Down
19 changes: 19 additions & 0 deletions lib/janus.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# frozen_string_literal: true

require 'active_support'

module Janus
autoload :Context, 'janus/context'
autoload :VERSION, 'janus/version'

module Logging
autoload :Subscriber, 'janus/logging/subscriber'
autoload :Logger, 'janus/logging/logger'
end
end

ActiveSupport.on_load(:active_record) do
ActiveRecord::LogSubscriber.log_subscribers.each do |subscriber|
subscriber.extend Janus::Logging::Subscriber
end
end
4 changes: 2 additions & 2 deletions lib/mysql2_split/context.rb → lib/janus/context.rb
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# frozen_string_literal: true

module Mysql2Split
module Janus
class Context
THREAD_KEY = :mysql2_split_context
THREAD_KEY = :janus_ar_context

# Stores the staged data with an expiration time based on the current time,
# and clears any expired entries. Returns true if any changes were made to
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
# frozen_string_literal: true

module Mysql2Split
module Janus
module Logging
class Logger
class << self
def log(message, format = :info)
logger&.send(format, "[Mysql2Split] #{message}")
logger&.send(format, "[Janus] #{message}")
end

attr_accessor :logger
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# frozen_string_literal: true

module Mysql2Split
module Janus
module Logging
module Subscriber
IGNORE_PAYLOAD_NAMES = %w[SCHEMA EXPLAIN].freeze
Expand All @@ -17,7 +17,7 @@ def sql(event)
protected

def current_wrapper_name(_event)
connection = Mysql2Split::Context.last_used_connection
connection = Janus::Context.last_used_connection
return nil unless connection

"[#{connection}]"
Expand Down
6 changes: 3 additions & 3 deletions lib/mysql2_split/version.rb → lib/janus/version.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# frozen_string_literal: true

module Mysql2Split
unless defined?(::Mysql2Split::VERSION)
module Janus
unless defined?(::Janus::VERSION)
module VERSION
MAJOR = 0
MINOR = 1
Expand All @@ -13,5 +13,5 @@ def self.to_s
end
end
end
::Mysql2Split::VERSION
::Janus::VERSION
end
19 changes: 0 additions & 19 deletions lib/mysql2_split.rb

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# frozen_string_literal: true

require 'mysql2_split/context'
require 'janus/context'

RSpec.describe Mysql2Split::Context do
RSpec.describe Janus::Context do
describe '#initialize' do
it 'sets the primary flag and expiry' do
context = described_class.new(primary: true, expiry: 60)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# frozen_string_literal: true

RSpec.describe Mysql2Split::Logging::Logger do
RSpec.describe Janus::Logging::Logger do
describe '.log' do
let(:logger) { double('logger') }

Expand All @@ -9,7 +9,7 @@
end

it 'logs the message with the specified format' do
expect(logger).to receive(:send).with(:info, '[Mysql2Split] Test message')
expect(logger).to receive(:send).with(:info, '[Janus] Test message')
described_class.log('Test message', :info)
end

Expand Down
2 changes: 1 addition & 1 deletion spec/spec_helper.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
# frozen_string_literal: true

require './lib/mysql2_split'
require './lib/janus'

0 comments on commit 0c76ba5

Please sign in to comment.