groonga-delta 1.0.2 → 1.0.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: ccfa632a6c046239e0163f15306f5479a64c220aa9e419bd415376bfeff5eae2
4
- data.tar.gz: 3ab279d8f62f66bda8e83fc71c3cb27b5472339e5e373868ccbfde0376759c67
3
+ metadata.gz: 5837cbbd7824ebf557d24be10007166c476dadf2157a96071522307d0ffbd0d7
4
+ data.tar.gz: '086fa03eb3a6c9d1a055ec8ffecf0a8b7e9102dcee0fd1d3326ece684f4e5560'
5
5
  SHA512:
6
- metadata.gz: eaa509b863868cd7fa1944d52603c312dc980707d975db9b52dd8b74131b2eac293cecc71dab48abb0895a22203dc2c59942750186e491209a3f395b481496b0
7
- data.tar.gz: 377115552df709288fdff0f1eff36d76e1a99b14e83409f2ab29f5d263b15e94b756d387f357f50423f2015c67f66badb662100d8dac8fdacac67f53d45af0c4
6
+ metadata.gz: a3db1aedcdbf3bb39312bfb091a778091111eec880488917a4d21598834e08a703181f383f47d06e2646e873d1ea1ac5d8e42ed46d71328ee8ecb8c837aa080d
7
+ data.tar.gz: 584ecff5ca3bf44fc9f4baea4ea8bd5f6c60227ac0f05367dd0860c74a563b26f4bcac747c74a7ce1c98a77f77100c2c736817ca6daa4af8138aec1372996f1c
data/doc/text/news.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # NEWS
2
2
 
3
+ ## 1.0.3 - 2022-07-21
4
+
5
+ ### Improvements
6
+
7
+ * `import`: Dropped support for `mysqlbinlog`.
8
+
9
+ * Changed to use atomic write for `status.yaml`.
10
+
11
+ * Changed to keep 100 logs from 7 logs by default.
12
+
13
+ ### Fixes
14
+
15
+ * `import`: Fixed a bug that events to be processed can't be detected.
16
+
3
17
  ## 1.0.2 - 2022-06-21
4
18
 
5
19
  ### Improvements
@@ -64,7 +64,7 @@ module GroongaDelta
64
64
  end
65
65
 
66
66
  def log_age
67
- @data["log_age"] || 7
67
+ @data["log_age"] || 100
68
68
  end
69
69
 
70
70
  def log_max_size
@@ -59,10 +59,6 @@ module GroongaDelta
59
59
  resolve_path(@data["binlog_dir"] || "binlog")
60
60
  end
61
61
 
62
- def mysqlbinlog
63
- @data["mysqlbinlog"] || "mysqlbinlog"
64
- end
65
-
66
62
  def host
67
63
  @data["host"] || "localhost"
68
64
  end
@@ -183,20 +179,20 @@ module GroongaDelta
183
179
  case value
184
180
  when String
185
181
  case value
186
- when /\A(\d+(?:\.\d+))(?:s|sec|second|seconds)?\z/
182
+ when /\A(\d+(?:\.\d+)?)(?:s|sec|second|seconds)?\z/
187
183
  Float($1)
188
- when /\A(\d+(?:\.\d+))(?:m|min|minute|minutes)\z/
184
+ when /\A(\d+(?:\.\d+)?)(?:m|min|minute|minutes)\z/
189
185
  Float($1) * 60
190
- when /\A(\d+(?:\.\d+))(?:h|hr|hour|hours)\z/
186
+ when /\A(\d+(?:\.\d+)?)(?:h|hr|hour|hours)\z/
191
187
  Float($1) * 60 * 60
192
- when /\A(\d+(?:\.\d+))(?:d|day|days)\z/
188
+ when /\A(\d+(?:\.\d+)?)(?:d|day|days)\z/
193
189
  Float($1) * 60 * 60 * 24
194
- when /\A(\d+(?:\.\d+))(?:w|week|weeks)\z/
190
+ when /\A(\d+(?:\.\d+)?)(?:w|week|weeks)\z/
195
191
  Float($1) * 60 * 60 * 24 * 7
196
- when /\A(\d+(?:\.\d+))(?:month|months)\z/
192
+ when /\A(\d+(?:\.\d+)?)(?:month|months)\z/
197
193
  # Same as systemd. See systemd.time(7)
198
194
  Float($1) * 60 * 60 * 24 * 30.44
199
- when /\A(\d+(?:\.\d+))(?:y|year|years)\z/
195
+ when /\A(\d+(?:\.\d+)?)(?:y|year|years)\z/
200
196
  # Same as systemd. See systemd.time(7)
201
197
  Float($1) * 60 * 60 * 24 * 365.25
202
198
  else
@@ -38,12 +38,16 @@ module GroongaDelta
38
38
  @status.update("mysql" => new_data)
39
39
  end
40
40
 
41
- def file
42
- self["file"]
41
+ def last_file
42
+ self["last_file"] || self["file"] # For backward compatibility
43
43
  end
44
44
 
45
- def position
46
- self["position"]
45
+ def last_position
46
+ self["last_position"] || self["position"] # For backward compatibility
47
+ end
48
+
49
+ def last_table_map_file
50
+ self["last_table_map_file"] || self["file"]
47
51
  end
48
52
 
49
53
  def last_table_map_position
@@ -15,6 +15,7 @@
15
15
 
16
16
  require "arrow"
17
17
  require "mysql2"
18
+ require "mysql2-replication"
18
19
 
19
20
  require_relative "error"
20
21
  require_relative "local-writer"
@@ -32,92 +33,7 @@ module GroongaDelta
32
33
  end
33
34
 
34
35
  def import
35
- case ENV["GROONGA_DELTA_IMPORT_MYSQL_SOURCE_BACKEND"]
36
- when "mysqlbinlog"
37
- require "mysql_binlog"
38
- import_mysqlbinlog
39
- when "mysql2-replication"
40
- require "mysql2-replication"
41
- import_mysql2_replication
42
- else
43
- begin
44
- require "mysql2-replication"
45
- rescue LoadError
46
- require "mysql_binlog"
47
- import_mysqlbinlog
48
- else
49
- import_mysql2_replication
50
- end
51
- end
52
- end
53
-
54
- private
55
- def import_mysqlbinlog
56
- file, position, last_table_map_position = read_current_status
57
- FileUtils.mkdir_p(@binlog_dir)
58
- local_file = File.join(@binlog_dir, file)
59
- unless File.exist?(local_file.succ)
60
- command_line = [@config.mysqlbinlog].flatten
61
- command_line << "--host=#{@config.host}" if @config.host
62
- command_line << "--port=#{@config.port}" if @config.port
63
- command_line << "--socket=#{@config.socket}" if @config.socket
64
- if @config.replication_slave_user
65
- command_line << "--user=#{@config.replication_slave_user}"
66
- end
67
- if @config.replication_slave_password
68
- command_line << "--password=#{@config.replication_slave_password}"
69
- end
70
- command_line << "--read-from-remote-server"
71
- command_line << "--raw"
72
- command_line << "--result-file=#{@binlog_dir}/"
73
- command_line << file
74
- spawn_process(*command_line) do |pid, output_read, error_read|
75
- end
76
- end
77
- reader = MysqlBinlog::BinlogFileReader.new(local_file)
78
- binlog = MysqlBinlog::Binlog.new(reader)
79
- binlog.checksum = @config.checksum
80
- binlog.ignore_rotate = true
81
- binlog.each_event do |event|
82
- next if event[:position] < last_table_map_position
83
- case event[:type]
84
- when :rotate_event
85
- file = event[:event][:name]
86
- when :table_map_event
87
- last_table_map_position = event[:position]
88
- when :write_rows_event_v1,
89
- :write_rows_event_v2,
90
- :update_rows_event_v1,
91
- :update_rows_event_v2,
92
- :delete_rows_event_v1,
93
- :delete_rows_event_v2
94
- next if event[:position] < position
95
- normalized_type = event[:type].to_s.gsub(/_v\d\z/, "").to_sym
96
- import_rows_event(normalized_type,
97
- event[:event][:table][:db],
98
- event[:event][:table][:table],
99
- file,
100
- event[:header][:next_position],
101
- last_table_map_position) do
102
- case normalized_type
103
- when :write_rows_event,
104
- :update_rows_event
105
- event[:event][:row_image].collect do |row_image|
106
- build_row(row_image[:after][:image])
107
- end
108
- when :delete_rows_event
109
- event[:event][:row_image].collect do |row_image|
110
- build_row(row_image[:before][:image])
111
- end
112
- end
113
- end
114
- position = event[:header][:next_position]
115
- end
116
- end
117
- end
118
-
119
- def import_mysql2_replication
120
- file, position, last_table_map_position = read_current_status
36
+ current_status = read_current_status
121
37
  is_mysql_56_or_later = mysql(@config.select_user,
122
38
  @config.select_password) do |select_client|
123
39
  mysql_version(select_client) >= Gem::Version.new("5.6")
@@ -130,83 +46,85 @@ module GroongaDelta
130
46
  replication_client = Mysql2Replication::Client.new(client,
131
47
  checksum: "NONE")
132
48
  end
133
- replication_client.file_name = file
134
- current_event_position = last_table_map_position
135
- replication_client.start_position = current_event_position
49
+ current_file = current_status.last_table_map_file
50
+ replication_client.file_name = current_file
51
+ current_event_position = current_status.last_table_map_position
52
+ if current_event_position
53
+ replication_client.start_position = current_event_position
54
+ end
136
55
  replication_client.open do
137
56
  replication_client.each do |event|
138
57
  begin
139
58
  @logger.debug do
140
59
  event.inspect
141
60
  end
142
- next if current_event_position < position
143
61
  case event
144
62
  when Mysql2Replication::RotateEvent
145
- file = event.file_name
63
+ current_file = event.file_name
64
+ current_status.last_file = current_file
65
+ current_status.last_position = event.position
146
66
  when Mysql2Replication::TableMapEvent
147
- last_table_map_event = current_event_position
67
+ current_status.last_table_map_file = current_file
68
+ current_status.last_table_map_position = current_event_position
148
69
  when Mysql2Replication::RowsEvent
149
- event_name = event.class.name.split("::").last
150
- normalized_type =
151
- event_name.scan(/[A-Z][a-z]+/).
152
- collect(&:downcase).
153
- join("_").
154
- to_sym
155
- import_rows_event(normalized_type,
156
- event.table_map.database,
157
- event.table_map.table,
158
- file,
159
- event.next_position,
160
- last_table_map_position) do
161
- case normalized_type
162
- when :update_rows_event
163
- event.updated_rows
164
- else
165
- event.rows
166
- end
167
- end
70
+ next if current_file != current_status.last_file
71
+ next if current_event_position < current_status.last_position
72
+ import_rows_event(event, current_status)
168
73
  end
169
74
  ensure
170
- current_event_position = event.next_position
75
+ # next_position may be 0. For example,
76
+ # Mysql2Replication::FormatDescriptionEvent#next_position
77
+ # returns 0.
78
+ next_position = event.next_position
79
+ current_event_position = next_position unless next_position.zero?
171
80
  end
172
81
  end
173
82
  end
174
83
  end
175
84
  end
176
85
 
177
- def import_rows_event(type,
178
- database_name,
179
- table_name,
180
- file,
181
- next_position,
182
- last_table_map_position,
183
- &block)
86
+ private
87
+ def import_rows_event(event, current_status)
88
+ database_name = event.table_map.database
89
+ table_name = event.table_map.table
90
+
184
91
  source_table = @mapping[database_name, table_name]
185
92
  return if source_table.nil?
186
93
 
187
94
  table = find_table(database_name, table_name)
188
95
  groonga_table = source_table.groonga_table
189
- target_rows = block.call
96
+ case event
97
+ when Mysql2Replication::UpdateRowsEvent
98
+ target_rows = event.updated_rows
99
+ else
100
+ target_rows = event.rows
101
+ end
190
102
  groonga_records = target_rows.collect do |row|
191
103
  record = build_record(table, row)
192
104
  groonga_table.generate_record(record)
193
105
  end
194
106
  return if groonga_records.empty?
195
107
 
196
- case type
197
- when :write_rows_event,
198
- :update_rows_event
108
+ case event
109
+ when Mysql2Replication::WriteRowsEvent,
110
+ Mysql2Replication::UpdateRowsEvent
199
111
  @writer.write_upserts(groonga_table.name, groonga_records)
200
- when :delete_rows_event
112
+ when Mysql2Replication::DeleteRowsEvent
201
113
  groonga_record_keys = groonga_records.collect do |record|
202
114
  record[:_key]
203
115
  end
204
116
  @writer.write_deletes(groonga_table.name,
205
117
  groonga_record_keys)
206
118
  end
207
- @status.update("file" => file,
208
- "position" => next_position,
209
- "last_table_map_position" => last_table_map_position)
119
+ current_status.last_position = event.next_position
120
+ new_status = {
121
+ "last_file" => current_status.last_file,
122
+ "last_position" => current_status.last_position,
123
+ "last_table_map_file" => current_status.last_table_map_file,
124
+ "last_table_map_position" => current_status.last_table_map_position,
125
+ }
126
+ @logger.info("Update status: #{new_status.inspect}")
127
+ @status.update(new_status)
210
128
  end
211
129
 
212
130
  def wait_process(command_line, pid, output_read, error_read)
@@ -280,17 +198,20 @@ module GroongaDelta
280
198
  end
281
199
 
282
200
  def read_current_status
283
- if @status.file
284
- [@status.file, @status.position, @status.last_table_map_position]
201
+ if @status.last_file
202
+ CurrentStatus.new(@status.last_file,
203
+ @status.last_position,
204
+ @status.last_table_map_file,
205
+ @status.last_table_map_position)
285
206
  else
286
- file = nil
287
- position = 0
207
+ last_file = nil
208
+ last_position = 0
288
209
  mysql(@config.replication_client_user,
289
210
  @config.replication_client_password) do |replication_client|
290
211
  replication_client.query("FLUSH TABLES WITH READ LOCK")
291
212
  result = replication_client.query("SHOW MASTER STATUS").first
292
- file = result["File"]
293
- position = result["Position"]
213
+ last_file = result["File"]
214
+ last_position = result["Position"]
294
215
  mysql(@config.select_user,
295
216
  @config.select_password) do |select_client|
296
217
  start_transaction = "START TRANSACTION " +
@@ -304,10 +225,19 @@ module GroongaDelta
304
225
  select_client.query("ROLLBACK")
305
226
  end
306
227
  end
307
- @status.update("file" => file,
308
- "position" => position,
309
- "last_table_map_position" => position)
310
- [file, position, position]
228
+ current_status = CurrentStatus.new(last_file,
229
+ last_position,
230
+ last_file,
231
+ last_position)
232
+ new_status = {
233
+ "last_file" => current_status.last_file,
234
+ "last_position" => current_status.last_position,
235
+ "last_table_map_file" => current_status.last_file,
236
+ "last_table_map_position" => current_status.last_position,
237
+ }
238
+ @logger.info("Update status: #{new_status.inspect}")
239
+ @status.update(new_status)
240
+ current_status
311
241
  end
312
242
  end
313
243
 
@@ -405,5 +335,10 @@ module GroongaDelta
405
335
  end
406
336
  record
407
337
  end
338
+
339
+ CurrentStatus = Struct.new(:last_file,
340
+ :last_position,
341
+ :last_table_map_file,
342
+ :last_table_map_position)
408
343
  end
409
344
  end
@@ -35,9 +35,11 @@ module GroongaDelta
35
35
  def update(data)
36
36
  @data.update(data)
37
37
  FileUtils.mkdir_p(@dir)
38
- File.open(@path, "w") do |output|
38
+ path_writing = "#{@path}.writing"
39
+ File.open(path_writing, "w") do |output|
39
40
  output.puts(YAML.dump(@data))
40
41
  end
42
+ FileUtils.mv(path_writing, @path)
41
43
  end
42
44
  end
43
45
  end
@@ -14,5 +14,5 @@
14
14
  # along with this program. If not, see <http://www.gnu.org/licenses/>.
15
15
 
16
16
  module GroongaDelta
17
- VERSION = "1.0.2"
17
+ VERSION = "1.0.3"
18
18
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: groonga-delta
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.2
4
+ version: 1.0.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sutou Kouhei
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-06-21 00:00:00.000000000 Z
11
+ date: 2022-07-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: groonga-client