db-charmer 1.6.14 → 1.6.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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