File: seamless_database_pool.rb

package info (click to toggle)
ruby-seamless-database-pool 1.0.20-5
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 288 kB
  • sloc: ruby: 1,544; makefile: 6
file content (148 lines) | stat: -rw-r--r-- 6,903 bytes parent folder | download | duplicates (5)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
require File.join(File.dirname(__FILE__), 'seamless_database_pool', 'connection_statistics.rb')
require File.join(File.dirname(__FILE__), 'seamless_database_pool', 'controller_filter.rb')
require File.join(File.dirname(__FILE__), 'active_record', 'connection_adapters', 'seamless_database_pool_adapter.rb')
require File.join(File.dirname(__FILE__), 'seamless_database_pool', 'railtie.rb') if defined?(Rails::Railtie)

$LOAD_PATH << File.dirname(__FILE__) unless $LOAD_PATH.include?(File.dirname(__FILE__))

# This module allows setting the read pool connection type. Generally you will use one of
#
#   - use_random_read_connection
#   - use_persistent_read_connection
#   - use_master_connection
#
# Each of these methods can take an optional block. If they are called with a block, they
# will set the read connection type only within the block. Otherwise they will set the default
# read connection type. If none is ever called, the read connection type will be :master.

module SeamlessDatabasePool
    
  # Adapter name to class name map. This exists because there isn't an obvious way to translate things like
  # sqlite3 to SQLite3. The adapters that ship with ActiveRecord are defined here. If you use
  # an adapter that doesn't translate directly to camel case, then add the mapping here in an initializer.
  ADAPTER_TO_CLASS_NAME_MAP = {"sqlite" => "SQLite", "sqlite3" => "SQLite3", "postgresql" => "PostgreSQL"}
  
  READ_CONNECTION_METHODS = [:master, :persistent, :random]

  class << self
    # Call this method to use a random connection from the read pool for every select statement.
    # This method is good if your replication is very fast. Otherwise there is a chance you could
    # get inconsistent results from one request to the next. This can result in mysterious failures
    # if your code selects a value in one statement and then uses in another statement. You can wind
    # up trying to use a value from one server that hasn't been replicated to another one yet.
    # This method is best if you have few processes which generate a lot of queries and you have
    # fast replication.
    def use_random_read_connection
      if block_given?
        set_read_only_connection_type(:random){yield}
      else
        Thread.current[:read_only_connection] = :random
      end
    end
  
    # Call this method to pick a random connection from the read pool and use it for all subsequent
    # select statements. This provides consistency from one select statement to the next. This
    # method should always be called with a block otherwise you can end up with an imbalanced read
    # pool. This method is best if you have lots of processes which have a relatively few select
    # statements or a slow replication mechanism. Generally this is the best method to use for web
    # applications.
    def use_persistent_read_connection
      if block_given?
        set_read_only_connection_type(:persistent){yield}
      else
        Thread.current[:read_only_connection] = {}
      end
    end
  
    # Call this method to use the master connection for all subsequent select statements. This
    # method is most useful when you are doing lots of updates since it guarantees consistency
    # if you do a select immediately after an update or insert.
    #
    # The master connection will also be used for selects inside any transaction blocks. It will
    # also be used if you pass :readonly => false to any ActiveRecord.find method.
    def use_master_connection
      if block_given?
        set_read_only_connection_type(:master){yield}
      else
        Thread.current[:read_only_connection] = :master
      end
    end
  
    # Set the read only connection type to either :master, :random, or :persistent.
    def set_read_only_connection_type(connection_type)
      saved_connection = Thread.current[:read_only_connection]
      retval = nil
      begin
        connection_type = {} if connection_type == :persistent
        Thread.current[:read_only_connection] = connection_type
        retval = yield if block_given?
      ensure
        Thread.current[:read_only_connection] = saved_connection
      end
      return retval
    end
  
    # Get the read only connection type currently in use. Will be one of :master, :random, or :persistent.
    def read_only_connection_type(default = :master)
      connection_type = Thread.current[:read_only_connection] || default
      connection_type = :persistent if connection_type.kind_of?(Hash)
      return connection_type
    end
  
    # Get a read only connection from a connection pool.
    def read_only_connection(pool_connection)
      return pool_connection.master_connection if pool_connection.using_master_connection?
      connection_type = Thread.current[:read_only_connection]
    
      if connection_type.kind_of?(Hash)
        connection = connection_type[pool_connection]
        unless connection
          connection = pool_connection.random_read_connection
          connection_type[pool_connection] = connection
        end
        return connection
      elsif connection_type == :random
        return pool_connection.random_read_connection
      else
        return pool_connection.master_connection
      end
    end
  
    # This method is provided as a way to change the persistent connection when it fails and a new one is substituted.
    def set_persistent_read_connection(pool_connection, read_connection)
      connection_type = Thread.current[:read_only_connection]
      connection_type[pool_connection] = read_connection if connection_type.kind_of?(Hash)
    end
  
    def clear_read_only_connection
      Thread.current[:read_only_connection] = nil
    end
    
    # Get the connection adapter class for an adapter name. The class will be loaded from
    # ActiveRecord::ConnectionAdapters::NameAdapter where Name is the camelized version of the name.
    # If the adapter class does not fit this pattern (i.e. sqlite3 => SQLite3Adapter), then add
    # the mapping to the +ADAPTER_TO_CLASS_NAME_MAP+ Hash.
    def adapter_class_for(name)
      name = name.to_s
      class_name = ADAPTER_TO_CLASS_NAME_MAP[name] || name.camelize
      "ActiveRecord::ConnectionAdapters::#{class_name}Adapter".constantize
    end
    
    # Pull out the master configuration for compatibility with such things as the Rails' rake db:*
    # tasks which only support known adapters.
    def master_database_configuration(database_configs)
      configs = {}
      database_configs.each do |key, values|
        if values['adapter'] == 'seamless_database_pool'
          values['adapter'] = values.delete('pool_adapter')
          values = values.merge(values['master']) if values['master'].is_a?(Hash)
          values.delete('pool_weight')
          values.delete('master')
          values.delete('read_pool')
        end
        configs[key] = values
      end
      configs
    end
  end
end