culturecode_stagehand 0.3.1 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/stagehand/production.rb +9 -5
- data/lib/stagehand/staging/commit_entry.rb +1 -1
- data/lib/stagehand/staging/synchronizer.rb +76 -28
- data/lib/stagehand/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: cd5f6596af77820e4b0fda648cf3a575cef70dd8
|
4
|
+
data.tar.gz: 73711aa60b690c1a9a7715d5ce26934d79e7727f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9b685a7c6bd6f6c8638c86f8ff3a316ad2147942b7dcaa378daea28fbc31421cce4105097ac1a3a418d8a6639891e107c5663ee9a5177bdb25770f2d3cd555ac
|
7
|
+
data.tar.gz: 7f629766d1d1af995dc0870bf15bdc6a2c04c96105859cc5cdc4388cb104c4be11b7f0df4c17987f1b8e54a1fd6447e6847fd792196e346ee8f49da17a558cbe
|
data/lib/stagehand/production.rb
CHANGED
@@ -20,7 +20,7 @@ module Stagehand
|
|
20
20
|
|
21
21
|
return unless attributes.present?
|
22
22
|
|
23
|
-
is_new =
|
23
|
+
is_new = matching(staging_record, table_name).update_all(attributes).zero?
|
24
24
|
|
25
25
|
# Ensure we always return a record, even when updating instead of creating
|
26
26
|
Record.new.tap do |record|
|
@@ -30,11 +30,11 @@ module Stagehand
|
|
30
30
|
end
|
31
31
|
|
32
32
|
def delete(staging_record, table_name = nil)
|
33
|
-
|
33
|
+
matching(staging_record, table_name).delete_all
|
34
34
|
end
|
35
35
|
|
36
36
|
def exists?(staging_record, table_name = nil)
|
37
|
-
|
37
|
+
matching(staging_record, table_name).exists?
|
38
38
|
end
|
39
39
|
|
40
40
|
# Returns true if the staging record's attributes are different from the production record's attributes
|
@@ -44,9 +44,13 @@ module Stagehand
|
|
44
44
|
production_record_attributes(staging_record, table_name) != staging_record_attributes(staging_record, table_name)
|
45
45
|
end
|
46
46
|
|
47
|
+
def find(*args)
|
48
|
+
matching(*args).first
|
49
|
+
end
|
50
|
+
|
47
51
|
# Returns a scope that limits results any occurrences of the specified record.
|
48
52
|
# Record can be specified by passing a staging record, or an id and table_name.
|
49
|
-
def
|
53
|
+
def matching(staging_record, table_name = nil)
|
50
54
|
table_name, id = Stagehand::Key.generate(staging_record, :table_name => table_name)
|
51
55
|
prepare_to_modify(table_name)
|
52
56
|
return Record.where(:id => id)
|
@@ -60,7 +64,7 @@ module Stagehand
|
|
60
64
|
end
|
61
65
|
|
62
66
|
def production_record_attributes(staging_record, table_name = nil)
|
63
|
-
Record.connection.select_one(
|
67
|
+
Record.connection.select_one(matching(staging_record, table_name))
|
64
68
|
end
|
65
69
|
|
66
70
|
def staging_record_attributes(staging_record, table_name = nil)
|
@@ -109,7 +109,7 @@ module Stagehand
|
|
109
109
|
private
|
110
110
|
|
111
111
|
def build_production_record
|
112
|
-
production_record = Stagehand::Production.
|
112
|
+
production_record = Stagehand::Production.find(record_id, table_name)
|
113
113
|
return unless production_record
|
114
114
|
|
115
115
|
production_record = record_class.new(production_record.attributes)
|
@@ -2,6 +2,9 @@ module Stagehand
|
|
2
2
|
module Staging
|
3
3
|
module Synchronizer
|
4
4
|
extend self
|
5
|
+
mattr_accessor :schemas_match
|
6
|
+
|
7
|
+
BATCH_SIZE = 1000
|
5
8
|
|
6
9
|
# Immediately attempt to sync the changes from the block if possible
|
7
10
|
# The block is wrapped in a transaction to prevent changes to records while being synced
|
@@ -14,22 +17,33 @@ module Stagehand
|
|
14
17
|
end
|
15
18
|
end
|
16
19
|
|
17
|
-
def auto_sync(
|
18
|
-
scope = autosyncable_entries.limit(1000)
|
19
|
-
|
20
|
+
def auto_sync(polling_delay = 5.seconds)
|
20
21
|
loop do
|
21
|
-
|
22
|
-
sleep(
|
22
|
+
sync(BATCH_SIZE)
|
23
|
+
sleep(polling_delay) if polling_delay
|
23
24
|
end
|
24
25
|
end
|
25
26
|
|
26
27
|
def sync(limit = nil)
|
27
|
-
|
28
|
+
synced_count = 0
|
29
|
+
deleted_count = 0
|
30
|
+
|
31
|
+
iterate_autosyncable_entries do |entry|
|
32
|
+
sync_entries(entry)
|
33
|
+
synced_count += 1
|
34
|
+
deleted_count += CommitEntry.matching(entry).delete_all
|
35
|
+
break if synced_count == limit
|
36
|
+
end
|
37
|
+
|
38
|
+
Rails.logger.info "Synced #{synced_count} entries"
|
39
|
+
Rails.logger.info "Removed #{deleted_count} stale entries"
|
40
|
+
|
41
|
+
return synced_count
|
28
42
|
end
|
29
43
|
|
30
44
|
def sync_all
|
31
45
|
loop do
|
32
|
-
entries = CommitEntry.order(:id => :desc).limit(
|
46
|
+
entries = CommitEntry.order(:id => :desc).limit(BATCH_SIZE).to_a
|
33
47
|
break unless entries.present?
|
34
48
|
|
35
49
|
latest_entries = entries.uniq(&:key)
|
@@ -48,44 +62,78 @@ module Stagehand
|
|
48
62
|
|
49
63
|
private
|
50
64
|
|
65
|
+
# Lazily iterate through millions of commit entries
|
66
|
+
# Returns commit entries in ID descending order
|
67
|
+
def iterate_autosyncable_entries(&block)
|
68
|
+
sessions = CommitEntry.order(:id => :desc).distinct.pluck(:session)
|
69
|
+
offset = 0
|
70
|
+
|
71
|
+
while sessions.present?
|
72
|
+
autosyncable_entries(:session => sessions.shift(30)).offset(offset).limit(BATCH_SIZE).each do |entry|
|
73
|
+
with_confirmed_autosyncability(entry, &block)
|
74
|
+
end
|
75
|
+
offset += BATCH_SIZE
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
# Executes the code in the block if the record referred to by the entry is in fact, autosyncable.
|
80
|
+
# This confirmation is used to guard against writes to the record that occur after loading an initial list of
|
81
|
+
# entries that are autosyncable, but before the record is actually synced. To prevent this, a lock on the record
|
82
|
+
# is acquired and then the record's autosync eligibility is rechecked before calling the block.
|
83
|
+
# NOTE: This method must be called from within a transaction
|
84
|
+
def with_confirmed_autosyncability(entry, &block)
|
85
|
+
ActiveRecord::Base.transaction do
|
86
|
+
CommitEntry.connection.execute("SELECT 1 FROM #{entry.table_name} WHERE id = #{entry.record_id}")
|
87
|
+
block.call(entry) if autosyncable_entries(:record_id => entry.record_id, :table_name => entry.table_name).exists?
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
# Returns commit entries in ID descending order
|
92
|
+
def autosyncable_entries(scope = nil)
|
93
|
+
entries = CommitEntry.content_operations.not_in_progress
|
94
|
+
|
95
|
+
unless Configuration.ghost_mode?
|
96
|
+
subquery = CommitEntry.group('record_id, table_name').having('count(commit_id) = 0').where(scope)
|
97
|
+
entries = entries.joins("JOIN (#{subquery.select('MAX(id) AS max_id').to_sql}) subquery ON id = max_id")
|
98
|
+
end
|
99
|
+
|
100
|
+
return entries.order(:id => :desc)
|
101
|
+
end
|
102
|
+
|
51
103
|
def sync_checklist(checklist)
|
52
|
-
|
53
|
-
|
104
|
+
ActiveRecord::Base.transaction do
|
105
|
+
sync_entries(checklist.syncing_entries)
|
106
|
+
CommitEntry.delete(checklist.affected_entries)
|
107
|
+
end
|
54
108
|
end
|
55
109
|
|
56
110
|
def sync_entries(entries)
|
57
111
|
return 0 if Configuration.single_connection? # Avoid deadlocking if the databases are the same
|
58
|
-
|
59
112
|
raise SchemaMismatch unless schemas_match?
|
60
113
|
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
114
|
+
entries = Array.wrap(entries)
|
115
|
+
|
116
|
+
entries.each do |entry|
|
117
|
+
Rails.logger.info "Synchronizing #{entry.table_name} #{entry.record_id}" if entry.content_operation?
|
118
|
+
if entry.delete_operation?
|
119
|
+
Stagehand::Production.delete(entry)
|
120
|
+
elsif entry.save_operation?
|
121
|
+
Stagehand::Production.save(entry)
|
69
122
|
end
|
70
123
|
end
|
71
124
|
|
72
125
|
return entries.length
|
73
126
|
end
|
74
127
|
|
75
|
-
def autosyncable_entries
|
76
|
-
if Configuration.ghost_mode?
|
77
|
-
CommitEntry
|
78
|
-
else
|
79
|
-
CommitEntry.where(:id =>
|
80
|
-
CommitEntry.select('MAX(id) AS id').content_operations.not_in_progress.group('record_id, table_name').having('count(commit_id) = 0'))
|
81
|
-
end
|
82
|
-
end
|
83
|
-
|
84
128
|
def schemas_match?
|
129
|
+
return schemas_match unless schemas_match.nil?
|
130
|
+
|
85
131
|
versions_scope = ActiveRecord::SchemaMigration.order(:version)
|
86
132
|
staging_versions = Stagehand::Database.staging_connection.select_values(versions_scope)
|
87
133
|
production_versions = Stagehand::Database.production_connection.select_values(versions_scope)
|
88
|
-
|
134
|
+
self.schemas_match = staging_versions == production_versions
|
135
|
+
|
136
|
+
return schemas_match
|
89
137
|
end
|
90
138
|
end
|
91
139
|
end
|
data/lib/stagehand/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: culturecode_stagehand
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Nicholas Jakobsen
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2016-04-
|
12
|
+
date: 2016-04-20 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rails
|