db-charmer 1.6.14 → 1.6.15

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,2 @@
1
+ /doc
2
+ /pkg
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.6.14
1
+ 1.6.15
data/db-charmer.gemspec CHANGED
@@ -1,57 +1,61 @@
1
1
  # Generated by jeweler
2
2
  # DO NOT EDIT THIS FILE DIRECTLY
3
- # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
4
  # -*- encoding: utf-8 -*-
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{db-charmer}
8
- s.version = "1.6.14"
8
+ s.version = "1.6.15"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Alexey Kovyrin"]
12
- s.date = %q{2011-01-09}
12
+ s.date = %q{2011-02-28}
13
13
  s.description = %q{ActiveRecord Connections Magic (slaves, multiple connections, etc)}
14
14
  s.email = %q{alexey@kovyrin.net}
15
15
  s.extra_rdoc_files = [
16
16
  "LICENSE",
17
- "README.rdoc"
17
+ "README.rdoc"
18
18
  ]
19
19
  s.files = [
20
- "CHANGES",
21
- "LICENSE",
22
- "Makefile",
23
- "README.rdoc",
24
- "Rakefile",
25
- "VERSION",
26
- "db-charmer.gemspec",
27
- "init.rb",
28
- "lib/db_charmer.rb",
29
- "lib/db_charmer/abstract_adapter_extensions.rb",
30
- "lib/db_charmer/active_record_extensions.rb",
31
- "lib/db_charmer/association_preload.rb",
32
- "lib/db_charmer/connection_factory.rb",
33
- "lib/db_charmer/connection_proxy.rb",
34
- "lib/db_charmer/connection_switch.rb",
35
- "lib/db_charmer/core_extensions.rb",
36
- "lib/db_charmer/db_magic.rb",
37
- "lib/db_charmer/finder_overrides.rb",
38
- "lib/db_charmer/multi_db_migrations.rb",
39
- "lib/db_charmer/multi_db_proxy.rb",
40
- "lib/db_charmer/scope_proxy.rb",
41
- "lib/db_charmer/sharding.rb",
42
- "lib/db_charmer/sharding/connection.rb",
43
- "lib/db_charmer/sharding/method/db_block_map.rb",
44
- "lib/db_charmer/sharding/method/hash_map.rb",
45
- "lib/db_charmer/sharding/method/range.rb",
46
- "lib/db_charmer/stub_connection.rb",
47
- "lib/tasks/databases.rake"
20
+ ".gitignore",
21
+ "CHANGES",
22
+ "LICENSE",
23
+ "Makefile",
24
+ "README.rdoc",
25
+ "Rakefile",
26
+ "VERSION",
27
+ "db-charmer.gemspec",
28
+ "init.rb",
29
+ "lib/db_charmer.rb",
30
+ "lib/db_charmer/abstract_adapter_extensions.rb",
31
+ "lib/db_charmer/active_record_extensions.rb",
32
+ "lib/db_charmer/association_preload.rb",
33
+ "lib/db_charmer/connection_factory.rb",
34
+ "lib/db_charmer/connection_proxy.rb",
35
+ "lib/db_charmer/connection_switch.rb",
36
+ "lib/db_charmer/core_extensions.rb",
37
+ "lib/db_charmer/db_magic.rb",
38
+ "lib/db_charmer/finder_overrides.rb",
39
+ "lib/db_charmer/multi_db_migrations.rb",
40
+ "lib/db_charmer/multi_db_proxy.rb",
41
+ "lib/db_charmer/scope_proxy.rb",
42
+ "lib/db_charmer/sharding.rb",
43
+ "lib/db_charmer/sharding/connection.rb",
44
+ "lib/db_charmer/sharding/method/db_block_group_map.rb",
45
+ "lib/db_charmer/sharding/method/db_block_map.rb",
46
+ "lib/db_charmer/sharding/method/hash_map.rb",
47
+ "lib/db_charmer/sharding/method/range.rb",
48
+ "lib/db_charmer/stub_connection.rb",
49
+ "lib/tasks/databases.rake"
48
50
  ]
49
51
  s.homepage = %q{http://github.com/kovyrin/db-charmer}
52
+ s.rdoc_options = ["--charset=UTF-8"]
50
53
  s.require_paths = ["lib"]
51
- s.rubygems_version = %q{1.4.1}
54
+ s.rubygems_version = %q{1.3.7}
52
55
  s.summary = %q{ActiveRecord Connections Magic}
53
56
 
54
57
  if s.respond_to? :specification_version then
58
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
55
59
  s.specification_version = 3
56
60
 
57
61
  if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
@@ -0,0 +1,261 @@
1
+ # This is a more sophisticated sharding method based on a two layer database-backed
2
+ # blocks map that holds block-shard associations. Record blocks are mapped to tablegroups
3
+ # and groups are mapped to shards.
4
+ #
5
+ # It automatically creates new blocks for new keys and assigns them to existing groups.
6
+ # Warning: make sure to create at least one shard and one group before inserting any records.
7
+ #
8
+ module DbCharmer
9
+ module Sharding
10
+ module Method
11
+ class DbBlockGroupMap
12
+ # Shard connection info model
13
+ class Shard < ActiveRecord::Base
14
+ validates_presence_of :db_host
15
+ validates_presence_of :db_port
16
+ validates_presence_of :db_user
17
+ validates_presence_of :db_pass
18
+ validates_presence_of :db_name_prefix
19
+
20
+ has_many :groups, :class_name => 'DbCharmer::Sharding::Method::DbBlockGroupMap::Group'
21
+ end
22
+
23
+ # Table group info model
24
+ class Group < ActiveRecord::Base
25
+ validates_presence_of :shard_id
26
+ belongs_to :shard, :class_name => 'DbCharmer::Sharding::Method::DbBlockGroupMap::Shard'
27
+ end
28
+
29
+ #---------------------------------------------------------------------------------------------------------------
30
+ # Sharder name
31
+ attr_accessor :name
32
+
33
+ # Mapping db connection
34
+ attr_accessor :connection, :connection_name
35
+
36
+ # Mapping table name
37
+ attr_accessor :map_table
38
+
39
+ # Tablegroups table name
40
+ attr_accessor :groups_table
41
+
42
+ # Shards table name
43
+ attr_accessor :shards_table
44
+
45
+ # Sharding keys block size
46
+ attr_accessor :block_size
47
+
48
+ def initialize(config)
49
+ @name = config[:name] or raise(ArgumentError, "Missing required :name parameter!")
50
+ @connection = DbCharmer::ConnectionFactory.connect(config[:connection], true)
51
+ @block_size = (config[:block_size] || 10000).to_i
52
+
53
+ @map_table = config[:map_table] or raise(ArgumentError, "Missing required :map_table parameter!")
54
+ @groups_table = config[:groups_table] or raise(ArgumentError, "Missing required :groups_table parameter!")
55
+ @shards_table = config[:shards_table] or raise(ArgumentError, "Missing required :shards_table parameter!")
56
+
57
+ # Local caches
58
+ @shard_info_cache = {}
59
+ @group_info_cache = {}
60
+
61
+ @blocks_cache = Rails.cache
62
+ @blocks_cache_prefix = config[:blocks_cache_prefix] || "#{@name}_block:"
63
+ end
64
+
65
+ #---------------------------------------------------------------------------------------------------------------
66
+ def shard_for_key(key)
67
+ block = block_for_key(key)
68
+
69
+ # Auto-allocate new blocks
70
+ block ||= allocate_new_block_for_key(key)
71
+ raise ArgumentError, "Invalid key value, no shards found for this key and could not create a new block!" unless block
72
+
73
+ # Load shard
74
+ group_id = block['group_id'].to_i
75
+ shard_info = shard_info_by_group_id(group_id)
76
+
77
+ # Get config
78
+ shard_connection_config(shard_info, group_id)
79
+ end
80
+
81
+ #---------------------------------------------------------------------------------------------------------------
82
+ # Returns a block for a key
83
+ def block_for_key(key, cache = true)
84
+ # Cleanup the cache if asked to
85
+ key_range = [ block_start_for_key(key), block_end_for_key(key) ]
86
+ block_cache_key = "%d-%d" % key_range
87
+
88
+ if cache
89
+ cached_block = get_cached_block(block_cache_key)
90
+ return cached_block if cached_block
91
+ end
92
+
93
+ # Fetch cached value or load from db
94
+ block = begin
95
+ sql = "SELECT * FROM #{map_table} WHERE start_id = #{key_range.first} AND end_id = #{key_range.last} LIMIT 1"
96
+ connection.select_one(sql, 'Find a shard block')
97
+ end
98
+
99
+ set_cached_block(block_cache_key, block)
100
+
101
+ return block
102
+ end
103
+
104
+ #---------------------------------------------------------------------------------------------------------------
105
+ def get_cached_block(block_cache_key)
106
+ @blocks_cache.read("#{@blocks_cache_prefix}#{block_cache_key}")
107
+ end
108
+
109
+ def set_cached_block(block_cache_key, block)
110
+ @blocks_cache.write("#{@blocks_cache_prefix}#{block_cache_key}", block)
111
+ end
112
+
113
+ #---------------------------------------------------------------------------------------------------------------
114
+ # Load group info
115
+ def group_info_by_id(group_id, cache = true)
116
+ # Cleanup the cache if asked to
117
+ @group_info_cache[group_id] = nil unless cache
118
+
119
+ # Either load from cache or from db
120
+ @group_info_cache[group_id] ||= begin
121
+ prepare_shard_models
122
+ Group.find_by_id(group_id)
123
+ end
124
+ end
125
+
126
+ # Load shard info
127
+ def shard_info_by_id(shard_id, cache = true)
128
+ # Cleanup the cache if asked to
129
+ @shard_info_cache[shard_id] = nil unless cache
130
+
131
+ # Either load from cache or from db
132
+ @shard_info_cache[shard_id] ||= begin
133
+ prepare_shard_models
134
+ Shard.find_by_id(shard_id)
135
+ end
136
+ end
137
+
138
+ # Load shard info using mapping info for a group
139
+ def shard_info_by_group_id(group_id)
140
+ # Load group
141
+ group_info = group_info_by_id(group_id)
142
+ raise ArgumentError, "Invalid group_id: #{group_id}" unless group_info
143
+
144
+ shard_info = shard_info_by_id(group_info.shard_id)
145
+ raise ArgumentError, "Invalid shard_id: #{group_info.shard_id}" unless shard_info
146
+
147
+ return shard_info
148
+ end
149
+
150
+ #---------------------------------------------------------------------------------------------------------------
151
+ def allocate_new_block_for_key(key)
152
+ # Can't find any groups to use for blocks allocation!
153
+ return nil unless group = least_loaded_group
154
+
155
+ # Figure out block limits
156
+ start_id = block_start_for_key(key)
157
+ end_id = block_end_for_key(key)
158
+
159
+ # Try to insert a new mapping (ignore duplicate key errors)
160
+ sql = <<-SQL
161
+ INSERT IGNORE INTO #{map_table}
162
+ SET start_id = #{start_id},
163
+ end_id = #{end_id},
164
+ group_id = #{group.id},
165
+ block_size = #{block_size},
166
+ created_at = NOW(),
167
+ updated_at = NOW()
168
+ SQL
169
+ connection.execute(sql, "Allocate new block")
170
+
171
+ # Increment the blocks counter on the shard
172
+ Group.update_counters(group.id, :blocks_count => +1)
173
+
174
+ # Retry block search after creation
175
+ block_for_key(key)
176
+ end
177
+
178
+ def least_loaded_group
179
+ prepare_shard_models
180
+
181
+ # Select group
182
+ group = Group.first(:conditions => { :enabled => true, :open => true }, :order => 'blocks_count ASC')
183
+ raise "Can't find any tablegroups to use for blocks allocation!" unless group
184
+ return group
185
+ end
186
+
187
+ #---------------------------------------------------------------------------------------------------------------
188
+ def block_start_for_key(key)
189
+ block_size.to_i * (key.to_i / block_size.to_i)
190
+ end
191
+
192
+ def block_end_for_key(key)
193
+ block_size.to_i + block_start_for_key(key)
194
+ end
195
+
196
+ #---------------------------------------------------------------------------------------------------------------
197
+ # Create configuration (use mapping connection as a template)
198
+ def shard_connection_config(shard, group_id)
199
+ # Format connection name
200
+ shard_name = "db_charmer_db_block_group_map_#{name}_s%d_g%d" % [ shard.id, group_id]
201
+
202
+ # Here we get the mapping connection's configuration
203
+ # They do not expose configs so we hack in and get the instance var
204
+ # FIXME: Find a better way, maybe move config method to our ar extenstions
205
+ connection.instance_variable_get(:@config).clone.merge(
206
+ # Name for the connection factory
207
+ :name => shard_name,
208
+ # Connection params
209
+ :host => shard.db_host,
210
+ :port => shard.db_port,
211
+ :username => shard.db_user,
212
+ :password => shard.db_pass,
213
+ :database => group_database_name(shard, group_id)
214
+ )
215
+ end
216
+
217
+ def group_database_name(shard, group_id)
218
+ "%s_%05d" % [ shard.db_name_prefix, group_id ]
219
+ end
220
+
221
+ #---------------------------------------------------------------------------------------------------------------
222
+ def create_shard(params)
223
+ params = params.symbolize_keys
224
+ [ :db_host, :db_port, :db_user, :db_pass, :db_name_prefix ].each do |arg|
225
+ raise ArgumentError, "Missing required parameter: #{arg}" unless params[arg]
226
+ end
227
+
228
+ # Prepare model
229
+ prepare_shard_models
230
+
231
+ # Create the record
232
+ Shard.create! do |shard|
233
+ shard.db_host = params[:db_host]
234
+ shard.db_port = params[:db_port]
235
+ shard.db_user = params[:db_user]
236
+ shard.db_pass = params[:db_pass]
237
+ shard.db_name_prefix = params[:db_name_prefix]
238
+ end
239
+ end
240
+
241
+ def shard_connections
242
+ # Find all groups
243
+ prepare_shard_models
244
+ groups = Group.all(:conditions => { :enabled => true })
245
+ # Map them to shards
246
+ groups.map { |group| shard_connection_config(group.shard, group.id) }
247
+ end
248
+
249
+ # Prepare model for working with our shards table
250
+ def prepare_shard_models
251
+ Shard.set_table_name(shards_table)
252
+ Shard.switch_connection_to(connection)
253
+
254
+ Group.set_table_name(groups_table)
255
+ Group.switch_connection_to(connection)
256
+ end
257
+
258
+ end
259
+ end
260
+ end
261
+ end
@@ -23,6 +23,10 @@ module DbCharmer
23
23
  def support_default_shard?
24
24
  ranges.has_key?(:default)
25
25
  end
26
+
27
+ def shard_connections
28
+ ranges.values.uniq
29
+ end
26
30
  end
27
31
  end
28
32
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: db-charmer
3
3
  version: !ruby/object:Gem::Version
4
- hash: 19
5
- prerelease:
4
+ hash: 17
5
+ prerelease: false
6
6
  segments:
7
7
  - 1
8
8
  - 6
9
- - 14
10
- version: 1.6.14
9
+ - 15
10
+ version: 1.6.15
11
11
  platform: ruby
12
12
  authors:
13
13
  - Alexey Kovyrin
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2011-01-09 00:00:00 -05:00
18
+ date: 2011-02-28 00:00:00 -05:00
19
19
  default_executable:
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency
@@ -57,6 +57,7 @@ extra_rdoc_files:
57
57
  - LICENSE
58
58
  - README.rdoc
59
59
  files:
60
+ - .gitignore
60
61
  - CHANGES
61
62
  - LICENSE
62
63
  - Makefile
@@ -80,6 +81,7 @@ files:
80
81
  - lib/db_charmer/scope_proxy.rb
81
82
  - lib/db_charmer/sharding.rb
82
83
  - lib/db_charmer/sharding/connection.rb
84
+ - lib/db_charmer/sharding/method/db_block_group_map.rb
83
85
  - lib/db_charmer/sharding/method/db_block_map.rb
84
86
  - lib/db_charmer/sharding/method/hash_map.rb
85
87
  - lib/db_charmer/sharding/method/range.rb
@@ -90,8 +92,8 @@ homepage: http://github.com/kovyrin/db-charmer
90
92
  licenses: []
91
93
 
92
94
  post_install_message:
93
- rdoc_options: []
94
-
95
+ rdoc_options:
96
+ - --charset=UTF-8
95
97
  require_paths:
96
98
  - lib
97
99
  required_ruby_version: !ruby/object:Gem::Requirement
@@ -115,7 +117,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
115
117
  requirements: []
116
118
 
117
119
  rubyforge_project:
118
- rubygems_version: 1.4.1
120
+ rubygems_version: 1.3.7
119
121
  signing_key:
120
122
  specification_version: 3
121
123
  summary: ActiveRecord Connections Magic