shameless 0.2.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +25 -0
- data/README.md +3 -1
- data/lib/shameless/cell.rb +61 -13
- data/lib/shameless/configuration.rb +3 -1
- data/lib/shameless/index.rb +22 -3
- data/lib/shameless/model.rb +60 -14
- data/lib/shameless/store.rb +46 -11
- data/lib/shameless/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ffc551d90e92b4f9174e15573bfdefb8a327c20f
|
4
|
+
data.tar.gz: 4e721f69cad576d714069eb2cdb281c13e98d256
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2ea84086ac343ccba0b8a44c5c257dfa02130355ffc5efd71dee633db5df9cc6631b15c8d2e24999bf1f211d663caf694664d9e2c5b561708aedd43d91769e1a
|
7
|
+
data.tar.gz: 9d939c4abd011c615cdda23894dd0e82459304b88a487d57de11006f100c3ac0b791fada345520cd6c8d2227e3352d937f69ceb4f1ab379e498d859cf8c8fa1d
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,28 @@
|
|
1
|
+
### 0.3.0 (2016-11-18)
|
2
|
+
|
3
|
+
* Add `Cell#uuid`
|
4
|
+
* Add `Cell#id`
|
5
|
+
* Add `Model.fetch_latest_cells`
|
6
|
+
* Initialize `ref_key` with zero, not one
|
7
|
+
* Add `Model#present?` and `Cell#present?`
|
8
|
+
* Allow `Cell#save` to be called even without making any changes
|
9
|
+
* Add `Model#fetch` and `Cell#fetch`
|
10
|
+
* Add `Model#reload` and `Cell#reload`
|
11
|
+
* Add `Model#previous` and `Cell#previous`
|
12
|
+
* `Model.put` now correctly looks up and updates an existing instance
|
13
|
+
* Add `Model#update` and `Cell#update`
|
14
|
+
* Expose `Model#base`
|
15
|
+
* Add `Configuration#legacy_created_at_is_bigint`
|
16
|
+
* Keep a reference to only one model class per table name
|
17
|
+
* Make `Store#find_shard` public
|
18
|
+
* Make `Store#each_shard` public
|
19
|
+
* Name index tables `*_:name_index_*`
|
20
|
+
* Add `Configuration#database_extensions`, they're being passed to the Sequel adapter
|
21
|
+
* Add `Configuration#connection_options`, they're being passed to the Sequel adapter
|
22
|
+
* Don't prefix table names with underscore when store name is `nil`
|
23
|
+
* Add `Store#each_partition`
|
24
|
+
* Add `Store#disconnect`
|
25
|
+
|
1
26
|
### 0.2.0 (2016-11-14)
|
2
27
|
|
3
28
|
* Add `Store#padded_shard` to get the formatted shard number for a shardable value
|
data/README.md
CHANGED
@@ -44,6 +44,8 @@ The core object of shameless is a `Store`. Here's how you can set one up:
|
|
44
44
|
RateStore = Shameless::Store.new(:rate_store) do |c|
|
45
45
|
c.partition_urls = [ENV['RATE_STORE_DATABASE_URL_0'], ENV['RATE_STORE_DATABASE_URL_1']
|
46
46
|
c.shards_count = 512 # total number of shards across all partitions
|
47
|
+
c.connection_options = {max_connections: 10} # connection options passed to Sequel.connect
|
48
|
+
c.database_extensions = [:newrelic_instrumentation]
|
47
49
|
end
|
48
50
|
```
|
49
51
|
|
@@ -93,7 +95,7 @@ class Rate
|
|
93
95
|
end
|
94
96
|
```
|
95
97
|
|
96
|
-
The default index is called a primary index, the corresponding tables would be called `
|
98
|
+
The default index is called a primary index, the corresponding tables would be called `rate_store_rate_primary_index_[000000-000511]`. You can add additional indices you'd like to query by:
|
97
99
|
|
98
100
|
```ruby
|
99
101
|
class Rate
|
data/lib/shameless/cell.rb
CHANGED
@@ -5,13 +5,20 @@ module Shameless
|
|
5
5
|
BASE = 'base'
|
6
6
|
|
7
7
|
def self.base(model, body)
|
8
|
-
|
8
|
+
serialized_body = serialize_body(body)
|
9
|
+
new(model, BASE, body: serialized_body)
|
9
10
|
end
|
10
11
|
|
11
|
-
def
|
12
|
+
def self.serialize_body(body)
|
13
|
+
MessagePack.pack(body)
|
14
|
+
end
|
15
|
+
|
16
|
+
attr_reader :model, :name, :id
|
17
|
+
|
18
|
+
def initialize(model, name, values = nil)
|
12
19
|
@model = model
|
13
20
|
@name = name
|
14
|
-
|
21
|
+
initialize_from_values(values)
|
15
22
|
end
|
16
23
|
|
17
24
|
def [](key)
|
@@ -24,32 +31,65 @@ module Shameless
|
|
24
31
|
end
|
25
32
|
|
26
33
|
def save
|
34
|
+
load
|
27
35
|
@created_at = Time.now
|
28
|
-
@
|
36
|
+
@created_at = (@created_at.to_f * 1000).to_i if @model.class.store.configuration.legacy_created_at_is_bigint
|
37
|
+
@ref_key ||= -1
|
29
38
|
@ref_key += 1
|
30
39
|
@model.put_cell(cell_values)
|
31
40
|
end
|
32
41
|
|
42
|
+
def update(values)
|
43
|
+
values.each do |key, value|
|
44
|
+
self[key] = value
|
45
|
+
end
|
46
|
+
|
47
|
+
save
|
48
|
+
end
|
49
|
+
|
33
50
|
def ref_key
|
34
|
-
|
51
|
+
load
|
35
52
|
@ref_key
|
36
53
|
end
|
37
54
|
|
38
55
|
def created_at
|
39
|
-
|
56
|
+
load
|
40
57
|
@created_at
|
41
58
|
end
|
42
59
|
|
43
60
|
def body
|
44
|
-
|
61
|
+
load
|
45
62
|
@body
|
46
63
|
end
|
47
64
|
|
65
|
+
def previous
|
66
|
+
if ref_key && previous_cell_values = @model.fetch_cell(@name, ref_key - 1)
|
67
|
+
self.class.new(@model, @name, previous_cell_values)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def reload
|
72
|
+
@body = @ref_key = @created_at = nil
|
73
|
+
end
|
74
|
+
|
75
|
+
def fetch(key, default)
|
76
|
+
body.key?(key.to_s) ? self[key] : default
|
77
|
+
end
|
78
|
+
|
79
|
+
def present?
|
80
|
+
load
|
81
|
+
!@ref_key.nil?
|
82
|
+
end
|
83
|
+
|
84
|
+
def uuid
|
85
|
+
@model.uuid
|
86
|
+
end
|
87
|
+
|
48
88
|
private
|
49
89
|
|
50
90
|
def cell_values
|
51
91
|
{
|
52
|
-
uuid:
|
92
|
+
uuid: uuid,
|
53
93
|
column_name: @name,
|
54
94
|
ref_key: ref_key,
|
55
95
|
created_at: created_at,
|
@@ -58,7 +98,7 @@ module Shameless
|
|
58
98
|
end
|
59
99
|
|
60
100
|
def serialized_body
|
61
|
-
|
101
|
+
self.class.serialize_body(body)
|
62
102
|
end
|
63
103
|
|
64
104
|
def deserialize_body(body)
|
@@ -67,12 +107,20 @@ module Shameless
|
|
67
107
|
|
68
108
|
private
|
69
109
|
|
70
|
-
def
|
110
|
+
def load
|
71
111
|
if @body.nil?
|
72
112
|
values = @model.fetch_cell(@name)
|
73
|
-
|
74
|
-
@
|
75
|
-
|
113
|
+
initialize_from_values(values)
|
114
|
+
@body ||= {}
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def initialize_from_values(values)
|
119
|
+
if values
|
120
|
+
@id = values[:id]
|
121
|
+
@body = deserialize_body(values[:body])
|
122
|
+
@ref_key = values[:ref_key]
|
123
|
+
@created_at = values[:created_at]
|
76
124
|
end
|
77
125
|
end
|
78
126
|
end
|
@@ -1,6 +1,8 @@
|
|
1
1
|
module Shameless
|
2
2
|
class Configuration
|
3
|
-
attr_accessor :partition_urls, :shards_count
|
3
|
+
attr_accessor :partition_urls, :shards_count, :connection_options, :database_extensions
|
4
|
+
|
5
|
+
attr_accessor :legacy_created_at_is_bigint
|
4
6
|
|
5
7
|
def shards_per_partition_count
|
6
8
|
shards_count / partitions_count
|
data/lib/shameless/index.rb
CHANGED
@@ -31,18 +31,33 @@ module Shameless
|
|
31
31
|
|
32
32
|
def put(values)
|
33
33
|
shardable_value = values.fetch(@shard_on)
|
34
|
-
index_values = (
|
34
|
+
index_values = index_values(values, true)
|
35
35
|
|
36
36
|
@model.store.put(table_name, shardable_value, index_values)
|
37
37
|
end
|
38
38
|
|
39
39
|
def where(query)
|
40
40
|
shardable_value = query.fetch(@shard_on)
|
41
|
+
query = index_values(query, false)
|
41
42
|
@model.store.where(table_name, shardable_value, query).map {|r| @model.new(r[:uuid]) }
|
42
43
|
end
|
43
44
|
|
44
45
|
def table_name
|
45
|
-
"#{@model.table_name}_#{
|
46
|
+
"#{@model.table_name}_#{full_name}"
|
47
|
+
end
|
48
|
+
|
49
|
+
def full_name
|
50
|
+
"#{@name}_index"
|
51
|
+
end
|
52
|
+
|
53
|
+
def index_values(values, all_required)
|
54
|
+
(@columns.keys + [:uuid]).each_with_object({}) do |column, o|
|
55
|
+
if all_required
|
56
|
+
o[column] = values.fetch(column)
|
57
|
+
else
|
58
|
+
o[column] = values[column] if values.key?(column)
|
59
|
+
end
|
60
|
+
end
|
46
61
|
end
|
47
62
|
|
48
63
|
def create_tables!
|
@@ -57,8 +72,12 @@ module Shameless
|
|
57
72
|
end
|
58
73
|
end
|
59
74
|
|
75
|
+
def column?(key)
|
76
|
+
@columns.keys.any? {|c| c.to_s == key.to_s }
|
77
|
+
end
|
78
|
+
|
60
79
|
def prevent_readonly_attribute_mutation!(key)
|
61
|
-
if
|
80
|
+
if column?(key)
|
62
81
|
raise ReadonlyAttributeMutation, "The attribute #{key} cannot be modified because it's part of the #{@name} index"
|
63
82
|
end
|
64
83
|
end
|
data/lib/shameless/model.rb
CHANGED
@@ -17,7 +17,7 @@ module Shameless
|
|
17
17
|
index = Index.new(name, self, &block)
|
18
18
|
@indices << index
|
19
19
|
|
20
|
-
define_singleton_method(
|
20
|
+
define_singleton_method(index.full_name) { index }
|
21
21
|
end
|
22
22
|
|
23
23
|
def cell(name)
|
@@ -30,13 +30,19 @@ module Shameless
|
|
30
30
|
end
|
31
31
|
|
32
32
|
def put(values)
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
model
|
37
|
-
|
38
|
-
|
39
|
-
|
33
|
+
if model = where(values).first
|
34
|
+
model_values = reject_index_values(values)
|
35
|
+
model.update(model_values)
|
36
|
+
model
|
37
|
+
else
|
38
|
+
uuid = SecureRandom.uuid
|
39
|
+
|
40
|
+
new(uuid, values).tap do |model|
|
41
|
+
model.save
|
42
|
+
|
43
|
+
index_values = values.merge(uuid: uuid)
|
44
|
+
@indices.each {|i| i.put(index_values) }
|
45
|
+
end
|
40
46
|
end
|
41
47
|
end
|
42
48
|
|
@@ -44,14 +50,28 @@ module Shameless
|
|
44
50
|
@store.put(table_name, shardable_value, cell_values)
|
45
51
|
end
|
46
52
|
|
47
|
-
def fetch_cell(shardable_value, uuid, cell_name)
|
53
|
+
def fetch_cell(shardable_value, uuid, cell_name, ref_key)
|
48
54
|
query = {uuid: uuid, column_name: cell_name}
|
55
|
+
query[:ref_key] = ref_key if ref_key
|
49
56
|
|
50
57
|
@store.where(table_name, shardable_value, query).order(:ref_key).last
|
51
58
|
end
|
52
59
|
|
60
|
+
def fetch_latest_cells(shard:, cursor:, limit:)
|
61
|
+
query = ['id > ?', cursor]
|
62
|
+
@store.where(table_name, shard, query).limit(limit).map do |cell_values|
|
63
|
+
model = new(cell_values[:uuid])
|
64
|
+
name = cell_values[:column_name].to_sym
|
65
|
+
Cell.new(model, name, cell_values)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
53
69
|
def table_name
|
54
|
-
|
70
|
+
[@store.name, @name].compact.join('_')
|
71
|
+
end
|
72
|
+
|
73
|
+
def table_names
|
74
|
+
[table_name, *@indices.map(&:table_name)]
|
55
75
|
end
|
56
76
|
|
57
77
|
def create_tables!
|
@@ -61,7 +81,9 @@ module Shameless
|
|
61
81
|
t.varchar :column_name, null: false
|
62
82
|
t.integer :ref_key, null: false
|
63
83
|
t.mediumblob :body
|
64
|
-
|
84
|
+
|
85
|
+
created_at_type = @store.configuration.legacy_created_at_is_bigint ? :bigint : :datetime
|
86
|
+
t.column :created_at, created_at_type, null: false
|
65
87
|
|
66
88
|
t.index %i[uuid column_name ref_key], unique: true
|
67
89
|
end
|
@@ -73,6 +95,10 @@ module Shameless
|
|
73
95
|
primary_index.where(query)
|
74
96
|
end
|
75
97
|
|
98
|
+
def reject_index_values(values)
|
99
|
+
values.reject {|k, _| @indices.any? {|i| i.column?(k) } }
|
100
|
+
end
|
101
|
+
|
76
102
|
def prevent_readonly_attribute_mutation!(key)
|
77
103
|
@indices.each {|i| i.prevent_readonly_attribute_mutation!(key) }
|
78
104
|
end
|
@@ -80,7 +106,7 @@ module Shameless
|
|
80
106
|
private
|
81
107
|
|
82
108
|
module InstanceMethods
|
83
|
-
attr_reader :uuid
|
109
|
+
attr_reader :uuid, :base
|
84
110
|
|
85
111
|
def initialize(uuid, base_body = nil)
|
86
112
|
@uuid = uuid
|
@@ -95,6 +121,10 @@ module Shameless
|
|
95
121
|
@base[field] = value
|
96
122
|
end
|
97
123
|
|
124
|
+
def update(values)
|
125
|
+
@base.update(values)
|
126
|
+
end
|
127
|
+
|
98
128
|
def save
|
99
129
|
@base.save
|
100
130
|
end
|
@@ -107,12 +137,28 @@ module Shameless
|
|
107
137
|
@base.created_at
|
108
138
|
end
|
109
139
|
|
140
|
+
def previous
|
141
|
+
@base.previous
|
142
|
+
end
|
143
|
+
|
144
|
+
def reload
|
145
|
+
@base.reload
|
146
|
+
end
|
147
|
+
|
148
|
+
def fetch(key, default)
|
149
|
+
@base.fetch(key, default)
|
150
|
+
end
|
151
|
+
|
152
|
+
def present?
|
153
|
+
@base.present?
|
154
|
+
end
|
155
|
+
|
110
156
|
def put_cell(cell_values)
|
111
157
|
self.class.put_cell(shardable_value, cell_values)
|
112
158
|
end
|
113
159
|
|
114
|
-
def fetch_cell(cell_name)
|
115
|
-
self.class.fetch_cell(shardable_value, uuid, cell_name)
|
160
|
+
def fetch_cell(cell_name, ref_key = nil)
|
161
|
+
self.class.fetch_cell(shardable_value, uuid, cell_name, ref_key)
|
116
162
|
end
|
117
163
|
|
118
164
|
def prevent_readonly_attribute_mutation!(key)
|
data/lib/shameless/store.rb
CHANGED
@@ -4,7 +4,7 @@ require 'shameless/model'
|
|
4
4
|
|
5
5
|
module Shameless
|
6
6
|
class Store
|
7
|
-
attr_reader :name
|
7
|
+
attr_reader :name, :configuration
|
8
8
|
|
9
9
|
def initialize(name, &block)
|
10
10
|
@name = name
|
@@ -15,8 +15,7 @@ module Shameless
|
|
15
15
|
def attach(model_class, name = nil)
|
16
16
|
model_class.extend(Model)
|
17
17
|
model_class.attach_to(self, name)
|
18
|
-
|
19
|
-
@models << model_class
|
18
|
+
models_hash[name] = model_class
|
20
19
|
end
|
21
20
|
|
22
21
|
def put(table_name, shardable_value, values)
|
@@ -27,8 +26,20 @@ module Shameless
|
|
27
26
|
find_table(table_name, shardable_value).where(query)
|
28
27
|
end
|
29
28
|
|
29
|
+
def disconnect
|
30
|
+
if instance_variable_defined?(:@partitions)
|
31
|
+
partitions.each(&:disconnect)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def each_partition(&block)
|
36
|
+
partitions.each do |partition|
|
37
|
+
block.call(partition, table_names_on_partition(partition))
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
30
41
|
def create_tables!
|
31
|
-
|
42
|
+
models.each(&:create_tables!)
|
32
43
|
end
|
33
44
|
|
34
45
|
def create_table!(table_name, &block)
|
@@ -44,14 +55,42 @@ module Shameless
|
|
44
55
|
format_shard(shard)
|
45
56
|
end
|
46
57
|
|
58
|
+
def each_shard(&block)
|
59
|
+
0.upto(@configuration.shards_count - 1, &block)
|
60
|
+
end
|
61
|
+
|
62
|
+
def find_shard(shardable_value)
|
63
|
+
shardable_value % @configuration.shards_count
|
64
|
+
end
|
65
|
+
|
47
66
|
private
|
48
67
|
|
68
|
+
def models_hash
|
69
|
+
@models_hash ||= {}
|
70
|
+
end
|
71
|
+
|
72
|
+
def models
|
73
|
+
models_hash.values
|
74
|
+
end
|
75
|
+
|
49
76
|
def partitions
|
50
|
-
@partitions ||= @configuration.partition_urls.map {|url|
|
77
|
+
@partitions ||= @configuration.partition_urls.map {|url| connect(url) }
|
51
78
|
end
|
52
79
|
|
53
|
-
def
|
54
|
-
|
80
|
+
def connect(url)
|
81
|
+
Sequel.connect(url, @configuration.connection_options || Sequel::OPTS).tap do |db|
|
82
|
+
db.extension *@configuration.database_extensions
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def table_names_on_partition(partition)
|
87
|
+
partition_index = partitions.index(partition)
|
88
|
+
first_shard = partition_index * @configuration.shards_per_partition_count
|
89
|
+
last_shard = first_shard + @configuration.shards_per_partition_count - 1
|
90
|
+
shards = first_shard..last_shard
|
91
|
+
table_names = models.flat_map(&:table_names)
|
92
|
+
|
93
|
+
table_names.flat_map {|t| shards.map {|s| table_name_with_shard(t, s) } }
|
55
94
|
end
|
56
95
|
|
57
96
|
def table_name_with_shard(table_name, shard)
|
@@ -63,10 +102,6 @@ module Shameless
|
|
63
102
|
shard.to_s.rjust(6, '0')
|
64
103
|
end
|
65
104
|
|
66
|
-
def find_shard(shardable_value)
|
67
|
-
shardable_value % @configuration.shards_count
|
68
|
-
end
|
69
|
-
|
70
105
|
def find_table(table_name, shardable_value)
|
71
106
|
shard = find_shard(shardable_value)
|
72
107
|
partition = find_partition_for_shard(shard)
|
data/lib/shameless/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: shameless
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Olek Janiszewski
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-11-
|
11
|
+
date: 2016-11-18 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: msgpack
|