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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 88258f8ab5b38d62a941024ddeef50d3e66ac13b
4
- data.tar.gz: 9f1571c1823366b2a5b576d9e213a7585713e883
3
+ metadata.gz: cd5f6596af77820e4b0fda648cf3a575cef70dd8
4
+ data.tar.gz: 73711aa60b690c1a9a7715d5ce26934d79e7727f
5
5
  SHA512:
6
- metadata.gz: a8020df0d22695e6fe842996690431bff25595e5dc3c76f46cb3ff7054dc8e56fad5dab0dc93007d6cc81d84b9875e922f2d70f0b98a37f28a479e50b63e1c55
7
- data.tar.gz: 391d375fd5dcc015f8ad867cc7495c476e2e1d652beddd18584ca2a4df262baf5aa0938f6ebb323357fd823055b73f8b32c8200b1dffb72829503e91bc32c176
6
+ metadata.gz: 9b685a7c6bd6f6c8638c86f8ff3a316ad2147942b7dcaa378daea28fbc31421cce4105097ac1a3a418d8a6639891e107c5663ee9a5177bdb25770f2d3cd555ac
7
+ data.tar.gz: 7f629766d1d1af995dc0870bf15bdc6a2c04c96105859cc5cdc4388cb104c4be11b7f0df4c17987f1b8e54a1fd6447e6847fd792196e346ee8f49da17a558cbe
@@ -20,7 +20,7 @@ module Stagehand
20
20
 
21
21
  return unless attributes.present?
22
22
 
23
- is_new = lookup(staging_record, table_name).update_all(attributes).zero?
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
- lookup(staging_record, table_name).delete_all
33
+ matching(staging_record, table_name).delete_all
34
34
  end
35
35
 
36
36
  def exists?(staging_record, table_name = nil)
37
- lookup(staging_record, table_name).exists?
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 lookup(staging_record, table_name = nil)
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(lookup(staging_record, table_name))
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.lookup(record_id, table_name).first
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(delay = 5.seconds)
18
- scope = autosyncable_entries.limit(1000)
19
-
20
+ def auto_sync(polling_delay = 5.seconds)
20
21
  loop do
21
- Rails.logger.info "Synced #{sync_entries(scope.reload)} entries"
22
- sleep(delay) if delay
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
- sync_entries(autosyncable_entries.limit(limit))
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(1000).to_a
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
- sync_entries(checklist.syncing_entries)
53
- CommitEntry.delete(checklist.affected_entries)
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
- ActiveRecord::Base.transaction do
62
- entries.each do |entry|
63
- Rails.logger.info "Synchronizing #{entry.table_name} #{entry.record_id}" if entry.content_operation?
64
- if entry.delete_operation?
65
- Stagehand::Production.delete(entry)
66
- elsif entry.save_operation?
67
- Stagehand::Production.save(entry)
68
- end
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
- return staging_versions == production_versions
134
+ self.schemas_match = staging_versions == production_versions
135
+
136
+ return schemas_match
89
137
  end
90
138
  end
91
139
  end
@@ -1,3 +1,3 @@
1
1
  module Stagehand
2
- VERSION = "0.3.1"
2
+ VERSION = "0.4.0"
3
3
  end
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.3.1
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-16 00:00:00.000000000 Z
12
+ date: 2016-04-20 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rails