gamma_replication 0.1.1 → 0.1.3

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
  SHA256:
3
- metadata.gz: b68929290d56de956de9597cfbdb9c6c7e2574f26489b5b55fc0223aa7d3c6ad
4
- data.tar.gz: 550aa21f4800a9da12f9d4f7b5bb4565a999321623d700cb74ae360bac461ae5
3
+ metadata.gz: 6fcf79da2a605e4984ba168b86117cc087f4da9414fc3075949ec3de0e4150e0
4
+ data.tar.gz: 369e57afcf359c2f148a0a3c985641ccf5beaa3706aade4c48e210827d5c04ec
5
5
  SHA512:
6
- metadata.gz: 73720919700ce8c4158bee20a58f375fe5a160ba1e44aa0f4348ed3f7ba0ba60d75c3224563579c65c5616dabd4f764b997c936dee029c12a2bade898f410028
7
- data.tar.gz: 10cf80dc382210270080adf30148a7556938bb164633bce1336484f3fdf22be571622ab711e2289aefc7486b8d1c3ea54e12932a117af77540eda7ba73045023
6
+ metadata.gz: c669c00890ad60fdfe242ad8e472f4f7b63154e4755d22bd798223c47536384f94f51dc8ae6bb2cd8aef83c386f5ac3f72906dcb4b4cccb709beb5282df9a2fa
7
+ data.tar.gz: f7ff79e999e05d3dabe4e5bd73fc9c0d8bf1939d0faef44391ba212eb5c8516809e65c50c5ee972e044cb6b7d68805c172ad18924f01b53644e2146a68a761ce
@@ -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
- logger.error(e)
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
- logger.error(e)
75
- # Nothing
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
- where_clause = build_where_clause(old_record, nil, table_setting.primary_key)
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
- raise "Primary key not found in record. old_record: #{old_record.inspect}, new_record: #{new_record.inspect}"
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
- "'#{@out_client.client.escape(value.to_s)}'"
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 => e
23
- logger.error("Failed to parse Maxwell output: #{e.message}")
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module GammaReplication
4
- VERSION = "0.1.1"
4
+ VERSION = "0.1.3"
5
5
  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.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-01-22 00:00:00.000000000 Z
11
+ date: 2025-02-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport