gamma_replication 0.1.1 → 0.1.3
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
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6fcf79da2a605e4984ba168b86117cc087f4da9414fc3075949ec3de0e4150e0
|
4
|
+
data.tar.gz: 369e57afcf359c2f148a0a3c985641ccf5beaa3706aade4c48e210827d5c04ec
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c669c00890ad60fdfe242ad8e472f4f7b63154e4755d22bd798223c47536384f94f51dc8ae6bb2cd8aef83c386f5ac3f72906dcb4b4cccb709beb5282df9a2fa
|
7
|
+
data.tar.gz: f7ff79e999e05d3dabe4e5bd73fc9c0d8bf1939d0faef44391ba212eb5c8516809e65c50c5ee972e044cb6b7d68805c172ad18924f01b53644e2146a68a761ce
|
data/exe/gamma_replication
CHANGED
@@ -12,6 +12,8 @@ class GammaReplicationCLI < Thor
|
|
12
12
|
option :data, aliases: "-d", desc: "Table Sync Settings yaml", required: true
|
13
13
|
option :hook_dir, aliases: "-h", desc: "Hook script directory", default: "."
|
14
14
|
option :maxwell_config, aliases: "-m", desc: "Maxwell configuration file path", required: true
|
15
|
+
option :enable_stats, type: :boolean, desc: "Enable statistics output (default: true)"
|
16
|
+
option :stats_interval, type: :numeric, desc: "Statistics output interval in seconds (default: 10800)"
|
15
17
|
def start
|
16
18
|
GammaReplication::Command::Start.new(options).execute
|
17
19
|
end
|
@@ -21,6 +23,8 @@ class GammaReplicationCLI < Thor
|
|
21
23
|
option :data, aliases: "-d", desc: "Table Sync Settings yaml", required: true
|
22
24
|
option :hook_dir, aliases: "-h", desc: "Hook script directory", default: "."
|
23
25
|
option :maxwell_config, aliases: "-m", desc: "Maxwell configuration file path", required: true
|
26
|
+
option :enable_stats, type: :boolean, desc: "Enable statistics output (default: true)"
|
27
|
+
option :stats_interval, type: :numeric, desc: "Statistics output interval in seconds (default: 10800)"
|
24
28
|
def dryrun
|
25
29
|
GammaReplication::Command::Dryrun.new(options).execute
|
26
30
|
end
|
@@ -8,6 +8,9 @@ module GammaReplication
|
|
8
8
|
setup_database(opts)
|
9
9
|
setup_parser(opts)
|
10
10
|
setup_maxwell(opts)
|
11
|
+
initialize_stats
|
12
|
+
@enable_stats = opts[:enable_stats].nil? ? true : opts[:enable_stats]
|
13
|
+
@stats_interval = (opts[:stats_interval] || 3 * 60 * 60).to_i # Default: 3 hours in seconds
|
11
14
|
end
|
12
15
|
|
13
16
|
def execute
|
@@ -18,6 +21,9 @@ module GammaReplication
|
|
18
21
|
hash[table.table_name] = table
|
19
22
|
end
|
20
23
|
before_start if respond_to?(:before_start)
|
24
|
+
|
25
|
+
setup_stats_reporter if @enable_stats
|
26
|
+
|
21
27
|
@maxwell_client.start do |data|
|
22
28
|
process_maxwell_data(data)
|
23
29
|
end
|
@@ -25,6 +31,52 @@ module GammaReplication
|
|
25
31
|
|
26
32
|
private
|
27
33
|
|
34
|
+
def initialize_stats
|
35
|
+
@stats = {
|
36
|
+
total: { insert: 0, update: 0, delete: 0 },
|
37
|
+
by_table: {}
|
38
|
+
}
|
39
|
+
@stats_mutex = Mutex.new
|
40
|
+
end
|
41
|
+
|
42
|
+
def setup_stats_reporter
|
43
|
+
@stats_thread = Thread.new do
|
44
|
+
loop do
|
45
|
+
sleep @stats_interval
|
46
|
+
output_stats
|
47
|
+
reset_stats
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def update_stats(table_name, operation)
|
53
|
+
return unless @enable_stats
|
54
|
+
|
55
|
+
@stats_mutex.synchronize do
|
56
|
+
@stats[:total][operation] += 1
|
57
|
+
@stats[:by_table][table_name] ||= { insert: 0, update: 0, delete: 0 }
|
58
|
+
@stats[:by_table][table_name][operation] += 1
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def reset_stats
|
63
|
+
@stats_mutex.synchronize do
|
64
|
+
initialize_stats
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def output_stats
|
69
|
+
@stats_mutex.synchronize do
|
70
|
+
total_stats = "Total[ins:#{@stats[:total][:insert]} | upd:#{@stats[:total][:update]} | del:#{@stats[:total][:delete]}]"
|
71
|
+
|
72
|
+
table_stats = @stats[:by_table].map do |table, counts|
|
73
|
+
"#{table}[ins:#{counts[:insert]} | upd:#{counts[:update]} | del:#{counts[:delete]}]"
|
74
|
+
end.join("\n")
|
75
|
+
|
76
|
+
logger.info("STATS | #{total_stats}\n#{table_stats}")
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
28
80
|
def setup_database(opts)
|
29
81
|
@database_settings = GammaReplication::DatabaseSettings.new(opts[:settings])
|
30
82
|
@in_client = GammaReplication::DatabaseConnector::MysqlConnector.new(@database_settings.in_database)
|
@@ -48,7 +100,8 @@ module GammaReplication
|
|
48
100
|
|
49
101
|
process_data_by_type(data)
|
50
102
|
rescue StandardError => e
|
51
|
-
|
103
|
+
error_message = e.message.to_s.split("\n").first
|
104
|
+
logger.error("Error: #{error_message.gsub(/\s+/, " ")}")
|
52
105
|
end
|
53
106
|
|
54
107
|
def should_process_data?(data)
|
@@ -64,15 +117,24 @@ module GammaReplication
|
|
64
117
|
table_setting = @table_settings[data["table"]]
|
65
118
|
case data["type"]
|
66
119
|
when "insert"
|
120
|
+
return unless data["data"].present?
|
121
|
+
|
122
|
+
update_stats(data["table"], :insert)
|
67
123
|
process_insert(table_setting, data)
|
68
124
|
when "update"
|
125
|
+
return unless data["data"].present?
|
126
|
+
|
127
|
+
update_stats(data["table"], :update)
|
69
128
|
process_update(table_setting, data)
|
70
129
|
when "delete"
|
130
|
+
return unless data["old"].present?
|
131
|
+
|
132
|
+
update_stats(data["table"], :delete)
|
71
133
|
process_delete(table_setting, data)
|
72
134
|
end
|
73
135
|
rescue StandardError => e
|
74
|
-
|
75
|
-
#
|
136
|
+
error_message = e.message.to_s.split("\n").first
|
137
|
+
logger.error("Error processing #{data["type"]} for table #{data["table"]}: #{error_message.gsub(/\s+/, " ")}")
|
76
138
|
end
|
77
139
|
|
78
140
|
def process_insert(table_setting, data)
|
@@ -100,8 +162,9 @@ module GammaReplication
|
|
100
162
|
|
101
163
|
def process_delete(table_setting, data)
|
102
164
|
old_record = data["old"]
|
103
|
-
|
165
|
+
return unless old_record.present? && old_record[table_setting.primary_key].present?
|
104
166
|
|
167
|
+
where_clause = build_where_clause(old_record, nil, table_setting.primary_key)
|
105
168
|
query = "DELETE FROM #{table_setting.table_name} WHERE #{where_clause}"
|
106
169
|
execute_query(query)
|
107
170
|
end
|
@@ -120,7 +183,8 @@ module GammaReplication
|
|
120
183
|
elsif new_record.present? && new_record[primary_key].present?
|
121
184
|
"`#{primary_key}` = #{format_value(new_record[primary_key])}"
|
122
185
|
else
|
123
|
-
|
186
|
+
logger.error("Primary key not found. old_record: #{old_record.inspect}, new_record: #{new_record.inspect}")
|
187
|
+
raise "Primary key '#{primary_key}' not found in record"
|
124
188
|
end
|
125
189
|
end
|
126
190
|
|
@@ -133,7 +197,34 @@ module GammaReplication
|
|
133
197
|
when Time
|
134
198
|
"'#{value.strftime("%Y-%m-%d %H:%M:%S")}'"
|
135
199
|
else
|
136
|
-
|
200
|
+
if json_column?(value)
|
201
|
+
sanitized_value = sanitize_json(value)
|
202
|
+
"'#{@out_client.client.escape(sanitized_value)}'"
|
203
|
+
else
|
204
|
+
"'#{@out_client.client.escape(value.to_s)}'"
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
def json_column?(value)
|
210
|
+
value.is_a?(String) && (value.start_with?("{") || value.start_with?("["))
|
211
|
+
end
|
212
|
+
|
213
|
+
def sanitize_json(value)
|
214
|
+
JSON.parse(value)
|
215
|
+
value
|
216
|
+
rescue JSON::ParserError => e
|
217
|
+
sanitized = value.gsub(/([{,]\s*)(\w+)(\s*:)/) do
|
218
|
+
"#{::Regexp.last_match(1)}\"#{::Regexp.last_match(2)}\"#{::Regexp.last_match(3)}"
|
219
|
+
end
|
220
|
+
sanitized = sanitized.gsub(/:\s*([^",\s\d\[\]{}-].*?)(,|\}|$)/) do
|
221
|
+
": \"#{::Regexp.last_match(1)}\"#{::Regexp.last_match(2)}"
|
222
|
+
end
|
223
|
+
begin
|
224
|
+
JSON.parse(sanitized)
|
225
|
+
sanitized
|
226
|
+
rescue JSON::ParserError
|
227
|
+
value.to_json
|
137
228
|
end
|
138
229
|
end
|
139
230
|
|
@@ -19,8 +19,9 @@ module GammaReplication
|
|
19
19
|
io.each do |line|
|
20
20
|
data = JSON.parse(line.strip)
|
21
21
|
block.call(data) if block_given?
|
22
|
-
rescue JSON::ParserError
|
23
|
-
|
22
|
+
rescue JSON::ParserError
|
23
|
+
# Ignore Maxwell's non-JSON output (startup messages, etc)
|
24
|
+
next
|
24
25
|
end
|
25
26
|
end
|
26
27
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: gamma_replication
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Shinsuke Nishio
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2025-
|
11
|
+
date: 2025-02-18 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|