shameless 0.2.0 → 0.3.0
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.
- 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
|