• Stars
    star
    593
  • Rank 73,630 (Top 2 %)
  • Language
    Ruby
  • License
    MIT License
  • Created about 10 years ago
  • Updated over 3 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Switching database connection between readonly one and writable one

SwitchPoint

Gem Version Build Status Coverage Status Code Climate

Switching database connection between readonly one and writable one.

Maintenance notice

switch_point won't support upcoming ActiveRecord v6.1 or later. Developers should use the builtin multiple database feature introduced in ActiveRecord v6.0. https://guides.rubyonrails.org/active_record_multiple_databases.html Thus the supported ActiveRecord version is v3.2, v4.0, v4.1, v4.2, v5.0, v5.1, and v5.2.

switch_point won't accept any new features. Bug fixes might be accepted. If you'd like to add a new feature (and/or support ActiveRecord >= v6.1), feel free to fork switch_point gem.

Migration from switch_point to ActiveRecord multiple database feature

  1. Upgrade your activerecord gem to v6.0
    • ActiveRecord v6.0 is the only series which supports both builtin multiple database feature and switch_point.
  2. Change your application to use ActiveRecord multiple database feature
    • If you'd like to keep the number of connections during this step, it would require some tricks.
  3. Remove switch_point gem from your Gemfile
  4. Upgrade your activerecord gem to v6.1 or later

Installation

Add this line to your application's Gemfile:

gem 'switch_point'

And then execute:

$ bundle

Or install it yourself as:

$ gem install switch_point

Usage

Suppose you have 4 databases: db-blog-master, db-blog-slave, db-comment-master and db-comment-slave. Article model and Category model are stored in db-blog-{master,slave} and Comment model is stored in db-comment-{master,slave}.

Configuration

In database.yml:

production_blog_master:
  adapter: mysql2
  username: blog_writable
  host: db-blog-master
production_blog_slave:
  adapter: mysql2
  username: blog_readonly
  host: db-blog-slave
production_comment_master:
    ...

In initializer:

SwitchPoint.configure do |config|
  config.define_switch_point :blog,
    readonly: :"#{Rails.env}_blog_slave",
    writable: :"#{Rails.env}_blog_master"
  config.define_switch_point :comment,
    readonly: :"#{Rails.env}_comment_slave",
    writable: :"#{Rails.env}_comment_master"
end

In models:

class Article < ActiveRecord::Base
  use_switch_point :blog
end

class Category < ActiveRecord::Base
  use_switch_point :blog
end

class Comment < ActiveRecord::Base
  use_switch_point :comment
end

Switching connections

Article.with_readonly { Article.first } # Read from db-blog-slave
Category.with_readonly { Category.first } # Also read from db-blog-slave
Comment.with_readonly { Comment.first } # Read from db-comment-slave

Article.with_readonly do
  article = Article.first  # Read from db-blog-slave
  article.title = 'new title'
  Article.with_writable do
    article.save!  # Write to db-blog-master
    article.reload  # Read from db-blog-master
    Category.first  # Read from db-blog-master
  end
end

Note that Article and Category shares their connections.

Query cache

Model.cache and Model.uncached enables/disables query cache for both readonly connection and writable connection.

switch_point also provide a rack middleware SwitchPoint::QueryCache similar to ActiveRecord::QueryCache. It enables query cache for all models using switch_point.

# Replace ActiveRecord::QueryCache with SwitchPoint::QueryCache
config.middleware.swap ActiveRecord::QueryCache, SwitchPoint::QueryCache

# Enable query cache for :nanika1 only.
config.middleware.swap ActiveRecord::QueryCache, SwitchPoint::QueryCache, [:nanika1]

Notes

auto_writable

auto_writable is disabled by default.

When auto_writable is enabled, destructive queries is sent to writable connection even in readonly mode. But it does NOT work well on transactions.

Suppose after_save callback is set to User model. When User.create is called, it proceeds as follows.

  1. BEGIN TRANSACTION is sent to READONLY connection.
  2. switch_point switches the connection to WRITABLE.
  3. INSERT statement is sent to WRITABLE connection.
  4. switch_point reset the connection to READONLY.
  5. after_save callback is called.
    • At this point, the connection is READONLY and in a transaction.
  6. COMMIT TRANSACTION is sent to READONLY connection.

connection-related methods of model

Model has several connection-related methods: connection_handler, connection_pool, connected? and so on. Since only connection method is monkey-patched, other connection-related methods doesn't work properly. If you'd like to use those methods, send it to Model.switch_point_proxy.model_for_connection.

Internals

There's a proxy which holds two connections: readonly one and writable one. A proxy has a thread-local state indicating the current mode: readonly or writable.

Each ActiveRecord model refers to a proxy. ActiveRecord::Base.connection is hooked and delegated to the referred proxy.

When the writable connection is requested to execute destructive query, the readonly connection clears its query cache.

switch_point

Special case: ActiveRecord::Base.connection

Basically, each connection managed by a proxy isn't shared between proxies. But there's one exception: ActiveRecord::Base.

If :writable key is omitted (e.g., Nanika1 model in spec/models), it uses ActiveRecord::Base.connection as writable one. When ActiveRecord::Base.connection is requested to execute destructive query, all readonly connections managed by a proxy which uses ActiveRecord::Base.connection as a writable connection clear query cache.

Contributing

  1. Fork it ( https://github.com/eagletmt/switch_point/fork )
  2. Create your feature branch (git checkout -b my-new-feature)
  3. Commit your changes (git commit -am 'Add some feature')
  4. Push to the branch (git push origin my-new-feature)
  5. Create a new Pull Request

More Repositories

1

ghcmod-vim

Happy Haskell programming on Vim, powered by ghc-mod
Vim Script
434
star
2

neco-ghc

A completion plugin for Haskell, using ghc-mod
Vim Script
363
star
3

hako

Deploy Docker container
Ruby
276
star
4

faml

Faster implementation of Haml template language
Ruby
187
star
5

project-euler-c

solutions for Project Euler in C
C
60
star
6

android-repository-history

History of Android repository XML
Rust
29
star
7

guzuta

Custom repository manager for ArchLinux pacman
Rust
27
star
8

eagletmt-recutils

My recutils
Rust
27
star
9

misc

ใ„ใ‚ใ„ใ‚
Rust
24
star
10

comic_walker

Client library for ComicWalker
Ruby
23
star
11

aws-lambda-rie-gateway

Convert HTTP request to API Gateway payload for aws-lambda-rie
Rust
15
star
12

libssh-ruby

Ruby binding for libssh
C
14
star
13

envop

Set environment variables from 1Password Secure Notes
Rust
13
star
14

haml_parser

Parser of Haml template language
Ruby
12
star
15

unite-haddock

unite.vim source for haddock
Vim Script
12
star
16

coqtop-vim

Interact with coqtop within Vim
Vim Script
11
star
17

slack-gtk

Native desktop client for slack.com
C++
10
star
18

mitamae-secrets

itamae-secrets for mitamae
Ruby
9
star
19

PKGBUILDs

my PKGBUILDs for Arch Linux
Shell
8
star
20

slack-thread-expander

Expand threaded messages without "Also sent to the channel"
Rust
7
star
21

onlinejudge-vim

enjoy online judges with Vim
Vim Script
6
star
22

kaede

Scheduler for recpt1 recorder using Syoboi Calendar
Ruby
5
star
23

ghci-vim

interact with GHCi
Vim Script
5
star
24

dotfiles

my dotfiles
Vim Script
5
star
25

assert_trait

Macro for static assert that values implements traits.
Rust
5
star
26

barbeque_client-rs

Barbeque client for Rust
Rust
4
star
27

contests

Programming contest is fun
C++
3
star
28

idolmap

Rust
3
star
29

akabei

Custom repository manager for ArchLinux pacman
Ruby
3
star
30

mruby-etc

etc module for mruby
C
3
star
31

django-runner

Run arbitrary Python script within Django environment
Python
3
star
32

dump-tf-schema

Dump schema of Terraform providers
Rust
3
star
33

activerecord-attribute_converter

Transparent conversion for ActiveRecord
Ruby
3
star
34

s3_assets_uploader

Upload Rails assets to S3
Ruby
3
star
35

faraday_middleware-cookiejar

Faraday middleware for managing cookies
Ruby
2
star
36

arch.wanko.cc

http://arch.wanko.cc
Rust
2
star
37

ttcoder

TokyoTechCoder
Ruby
2
star
38

revision_plate-golang

Serve application's REVISION
Go
2
star
39

setup

Ruby
2
star
40

poj-vim

Vim script for POJ
Vim Script
1
star
41

denv-rust

Rust
1
star
42

mitamae-pacman

Provide MItamae::InlineBackends::PacmanBackend for faster package management
C
1
star
43

procon

My submissions
C++
1
star
44

dvb-recpt1

recpt1 for earth-pt1 driver
C++
1
star
45

btrfs-usage

Show detailed btrfs usage in machine-readable format
C
1
star
46

eagletmt.github.com

1
star
47

uim-skk-server-completion

uim-skk ใง server completion ใงใใ‚‹ใ‚ˆใ†ใซใ™ใ‚‹
1
star
48

skkserv-rust

Rust
1
star
49

xz-rust

Pure Rust implementation of reading/writing xz files (WIP)
Rust
1
star
50

fastladder-bookwalker

Rust
1
star
51

hako-etcenv

Provide variables from etcenv to hako
Ruby
1
star
52

sentry-notify-hipchat

A Sentry notification plugin for HipChat
Python
1
star
53

sentry-breakdown

Render Sentry project breakdown of usage report (WIP)
Rust
1
star
54

rundeck-simple-slack-notification

Groovy
1
star
55

ruby-minisat

Ruby binding for MiniSat
Ruby
1
star
56

fastladder-pixiv

Rust
1
star
57

tower-cookie-store

Cookie session store for Tower and Axum
Rust
1
star
58

bf-interp

brainf*ck interpreter written in Haskell
Haskell
1
star
59

hako-vault

Provide variables from Vault to hako
Ruby
1
star
60

project-euler-haskell

solutions for Project Euler in Haskell
Haskell
1
star