flydata 0.5.6 → 0.5.7

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: c8603d9b83a0d1f764610c72f23c822b8442322b
4
- data.tar.gz: 653eb4641b6602f760156e8b8bd83168642e40a8
3
+ metadata.gz: 498996a0198e22b19c7417c3b40fe1be38f2826f
4
+ data.tar.gz: 1e069123fe4fead123563f63f43a4a5f0f663394
5
5
  SHA512:
6
- metadata.gz: 0d3835c0914fcb12a951e58584a435528fc42f3aa7905420b02905ac9eca26354b122d2b82d45b8b83a8ef10a6b9b13ce3684b6336508aee8a207783fe2e693b
7
- data.tar.gz: 6c0819ccc0c95efb01e6c6728c21e25d034ca7612ea02a2ed3671028c7c147065e1fd47f15f902c57cb5ce5efd39a47cadc3c5db30e07a736d94cd37e252bbbe
6
+ metadata.gz: 395895f16250333b5f7f094ff063cfafacc404c4f1628f29f10f3585844852e452f96ac18effc39043238ac770b3afbaeeeabb78095b3e3b6eae2aff44be0409
7
+ data.tar.gz: f7e2c589cdc767b5d0edc1f134755e379bb6808903ec411532a6d575388b7e2a823c0cef4766d501806f1e05dd13f526fe44a4ac85af893423499240a0367d6a
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.5.6
1
+ 0.5.7
@@ -0,0 +1,8 @@
1
+ module FlydataCore
2
+
3
+ class Agent
4
+ # fluentd buffer path
5
+ BUFFER_PATH = 'buffer/client_buf'
6
+ end
7
+
8
+ end
@@ -0,0 +1,72 @@
1
+ module FlydataCore
2
+ module Mysql
3
+
4
+ class BinlogPos
5
+ def initialize(binlog_str_or_filename_or_hash, binlog_pos = nil)
6
+ arg = binlog_str_or_filename_or_hash
7
+ if binlog_pos
8
+ @pos = binlog_pos
9
+ @filename = arg
10
+ elsif arg.kind_of?(String)
11
+ @filename, @pos = arg.split("\t")
12
+ elsif arg.kind_of?(Hash)
13
+ @pos = arg[:pos]
14
+ @filename = arg[:filename]
15
+ @filename ||= arg[:binfile]
16
+ end
17
+ if @filename.nil? || @pos.nil?
18
+ raise "Invalid initialize argument (#{arg}, #{binlog_pos})"
19
+ end
20
+ @pos = @pos.to_i
21
+ end
22
+
23
+ attr_reader :filename, :pos
24
+
25
+ def <=>(other_pos)
26
+ if other_pos.nil?
27
+ raise ArgumentError.new("comparison of BinlosPos with nil failed")
28
+ end
29
+
30
+ other_pos = BinlogPos.new(other_pos) unless other_pos.kind_of?(BinlogPos)
31
+
32
+ if @filename < other_pos.filename
33
+ -1
34
+ elsif @filename == other_pos.filename
35
+ if @pos < other_pos.pos
36
+ -1
37
+ elsif @pos == other_pos.pos
38
+ 0
39
+ else
40
+ 1
41
+ end
42
+ else
43
+ 1
44
+ end
45
+ end
46
+ def <(other_pos)
47
+ self.<=>(other_pos) < 0
48
+ end
49
+ def <=(other_pos)
50
+ self.<=>(other_pos) <= 0
51
+ end
52
+ def ==(other_pos)
53
+ return false if other_pos.nil?
54
+ self.<=>(other_pos) == 0
55
+ end
56
+ def !=(other_pos)
57
+ !self.==(other_pos)
58
+ end
59
+ def >(other_pos)
60
+ self.<=>(other_pos) > 0
61
+ end
62
+ def >=(other_pos)
63
+ self.<=>(other_pos) >= 0
64
+ end
65
+
66
+ def to_s
67
+ "#{@filename}\t#{@pos}"
68
+ end
69
+ end
70
+
71
+ end
72
+ end
@@ -2,16 +2,16 @@
2
2
  # DO NOT EDIT THIS FILE DIRECTLY
3
3
  # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
4
  # -*- encoding: utf-8 -*-
5
- # stub: flydata 0.5.6 ruby lib
5
+ # stub: flydata 0.5.7 ruby lib
6
6
 
7
7
  Gem::Specification.new do |s|
8
8
  s.name = "flydata"
9
- s.version = "0.5.6"
9
+ s.version = "0.5.7"
10
10
 
11
11
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
12
12
  s.require_paths = ["lib"]
13
13
  s.authors = ["Koichi Fujikawa", "Masashi Miyazaki", "Matthew Luu", "Mak Inada", "Sriram NS"]
14
- s.date = "2015-09-22"
14
+ s.date = "2015-09-23"
15
15
  s.description = "FlyData Agent"
16
16
  s.email = "sysadmin@flydata.com"
17
17
  s.executables = ["fdmysqldump", "flydata", "serverinfo"]
@@ -37,6 +37,7 @@ Gem::Specification.new do |s|
37
37
  "flydata-core/Gemfile.lock",
38
38
  "flydata-core/circle.yml",
39
39
  "flydata-core/lib/flydata-core.rb",
40
+ "flydata-core/lib/flydata-core/agent.rb",
40
41
  "flydata-core/lib/flydata-core/config/user_maintenance.rb",
41
42
  "flydata-core/lib/flydata-core/core_ext.rb",
42
43
  "flydata-core/lib/flydata-core/core_ext/module.rb",
@@ -47,6 +48,7 @@ Gem::Specification.new do |s|
47
48
  "flydata-core/lib/flydata-core/fluent-plugins/multi_buffer.rb",
48
49
  "flydata-core/lib/flydata-core/fluent/config_helper.rb",
49
50
  "flydata-core/lib/flydata-core/logger.rb",
51
+ "flydata-core/lib/flydata-core/mysql/binlog_pos.rb",
50
52
  "flydata-core/lib/flydata-core/mysql/command_generator.rb",
51
53
  "flydata-core/lib/flydata-core/mysql/compatibility_checker.rb",
52
54
  "flydata-core/lib/flydata-core/mysql/config.rb",
@@ -92,6 +94,7 @@ Gem::Specification.new do |s|
92
94
  "flydata.gemspec",
93
95
  "lib/fly_data_model.rb",
94
96
  "lib/flydata.rb",
97
+ "lib/flydata/agent.rb",
95
98
  "lib/flydata/api/agent.rb",
96
99
  "lib/flydata/api/base.rb",
97
100
  "lib/flydata/api/data_entry.rb",
@@ -228,7 +231,7 @@ Gem::Specification.new do |s|
228
231
  ]
229
232
  s.homepage = "http://flydata.com/"
230
233
  s.licenses = ["All right reserved."]
231
- s.rubygems_version = "2.4.6"
234
+ s.rubygems_version = "2.4.3"
232
235
  s.summary = "FlyData Agent"
233
236
 
234
237
  if s.respond_to? :specification_version then
@@ -29,6 +29,7 @@ module Flydata
29
29
  FLYDATA_SERVERINFO = File.join(FLYDATA_GEM_BIN, 'serverinfo')
30
30
  FLYDATA_LOG = File.join(FLYDATA_HOME, 'flydata.log')
31
31
  FLYDATA_CONF = File.join(FLYDATA_HOME, 'flydata.conf')
32
+ FLYDATA_LOCK = File.join(FLYDATA_HOME, 'flydata.lock')
32
33
  FLYDATA_HELPER_HOME = File.join(FLYDATA_HOME, 'helper')
33
34
 
34
35
  VERSION_PATH = File.join(FLYDATA_GEM_HOME, 'VERSION')
@@ -0,0 +1,17 @@
1
+ require 'flydata-core/agent'
2
+
3
+ module Flydata
4
+
5
+ class Agent
6
+ def initialize(flydata_home)
7
+ @flydata_home = flydata_home
8
+ end
9
+
10
+ def delete_buffer_files
11
+ files = Dir.glob("#{@flydata_home}/#{FlydataCore::Agent::BUFFER_PATH}*")
12
+ File.delete(*files)
13
+ files
14
+ end
15
+ end
16
+
17
+ end
@@ -10,13 +10,19 @@ module Flydata
10
10
  super
11
11
  end
12
12
 
13
- def buffer_stat(data_entry_id, params = {})
14
- tables = params[:tables] ? params[:tables].join(',') : ''
15
- @client.get("/#{@model_name.pluralize}/#{data_entry_id}/buffer_stat/#{params[:mode]}?tables=#{tables}")
13
+ def buffer_stat(data_entry_id, options = {})
14
+ tables = options[:tables] ? options[:tables].join(',') : ''
15
+ @client.get("/#{@model_name.pluralize}/#{data_entry_id}/buffer_stat/#{options[:mode]}?tables=#{tables}")
16
16
  end
17
17
 
18
- def cleanup_sync(data_entry_id, tables)
19
- @client.post("/#{@model_name.pluralize}/#{data_entry_id}/cleanup_sync", nil, {tables: tables.join(',')})
18
+ def table_status(data_entry_id, options = {})
19
+ tables = options[:tables] ? options[:tables].join(',') : ''
20
+ @client.post("/#{@model_name.pluralize}/#{data_entry_id}/table_status", nil, {tables: tables})
21
+ end
22
+
23
+ def cleanup_sync(data_entry_id, tables, options = {})
24
+ params = options.merge({tables: tables.join(',')})
25
+ @client.post("/#{@model_name.pluralize}/#{data_entry_id}/cleanup_sync", nil, params)
20
26
  end
21
27
 
22
28
  # Update validity of tables
@@ -22,6 +22,11 @@ module Flydata
22
22
  return
23
23
  end
24
24
 
25
+ if agent_locked?
26
+ $log.error "Previous process was terminated abnormally. To start, remove the lock file after checking data integrity."
27
+ raise "Agent was not shutdown properly. Agent is stopped temporarily in order to avoid sync consistency issues. Please contact support@flydata.com to solve the issue."
28
+ end
29
+
25
30
  # Ends orphan_proceses if there is any
26
31
  orphan_processes.each do |pid|
27
32
  Process.kill(:TERM, pid)
@@ -72,8 +77,9 @@ EOF
72
77
  raise 'Something has gone wrong..'
73
78
  end
74
79
  def flush_client_buffer(options = {})
80
+ force_flush = options.has_key?(:force) ? options[:force] : true
75
81
  unless process_exist?
76
- return true if client_buffer_empty?
82
+ return true if !force_flush || client_buffer_empty?
77
83
  log_info_stdout("Process doesn't exist. But, the client buffer is not empty!!") unless options[:quiet]
78
84
  raw_start(options)
79
85
  end
@@ -124,6 +130,10 @@ EOF
124
130
  raise 'Something has gone wrong...'
125
131
  end
126
132
  end
133
+ # call the method only when no legit process is running.
134
+ def agent_locked?
135
+ File.exists?(FLYDATA_LOCK)
136
+ end
127
137
 
128
138
  private
129
139
  # Return a list of fluentd parent processes run by the same user for the
@@ -2,6 +2,7 @@ require 'msgpack'
2
2
  require 'mysql2'
3
3
  require 'rest_client'
4
4
  require 'sys/filesystem'
5
+ require 'flydata/agent'
5
6
  require 'flydata/command/base'
6
7
  require 'flydata/command/conf'
7
8
  require 'flydata/command/sender'
@@ -13,6 +14,7 @@ require 'flydata/parser/mysql/dump_parser'
13
14
  require 'flydata/preference/data_entry_preference'
14
15
  require 'flydata/sync_file_manager'
15
16
  require 'flydata-core/table_def'
17
+ require 'flydata-core/mysql/binlog_pos'
16
18
  require 'flydata/mysql/table_ddl'
17
19
  require 'flydata-core/mysql/command_generator'
18
20
  #require 'ruby-prof'
@@ -159,7 +161,7 @@ EOS
159
161
  message = ''
160
162
  if sync_resumed && !tables.empty?
161
163
  log_info_stdout <<EOS
162
- Initial sync is in progress. In this case, you can only reset the initial sync.To reset specific table(s), please resume and complete the initial sync by running the 'flydata start' command first.
164
+ Initial sync is in progress. In this case, you can only reset the initial sync. To reset specific table(s), please resume and complete the initial sync by running the 'flydata start' command first.
163
165
  If you'd like to reset the initial sync in progress, run the 'flydata reset' command with no arguments.
164
166
  EOS
165
167
  return
@@ -308,6 +310,205 @@ EOS
308
310
  end
309
311
  run_exclusive :fix_binlogpos
310
312
 
313
+ def repair
314
+ de = data_entry
315
+ set_current_tables
316
+ # Stop agent. Check sync and make sure the state is :STUCK_AT_UPLOAD
317
+ # Get table status for the tables.
318
+ status, pos_mismatch_tables, gap_tables, table_status_hash = _check(stop_agent:true)
319
+ if status.include? :STUCK_AT_PROCESS
320
+ e = AgentError.new("Data is stuck while processing")
321
+ e.description = <<EOS
322
+ Data appears to be stuck while processing. Contact FlyData Support (support@flydata.com) to solve the issue.
323
+ EOS
324
+ raise e
325
+ end
326
+
327
+ if status.include? :OK
328
+ log_info_stdout
329
+ log_info_stdout "Sync is in good condition. Nothing to repair."
330
+ return
331
+ end
332
+
333
+ tables = []
334
+ gt = gap_tables.collect{|bt| bt[:table] } if gap_tables
335
+ pt = pos_mismatch_tables.collect{|bt| bt[:table] } if pos_mismatch_tables
336
+ tables = gt | pt # position mismatch can be due to query queue items discarded by the copy handler so it also needs to be in the target table list.
337
+
338
+ log_info_stdout <<EOS
339
+
340
+ The following issues have been found and will be repaired.
341
+
342
+ EOS
343
+ log_info_stdout <<EOS unless tables.empty?
344
+ - Sync is broken due to missing data for the following tables
345
+ #{tables.join("\n ")}
346
+
347
+ EOS
348
+
349
+ log_info_stdout <<EOS if status.include? :ABNORMAL_SHUTDOWN
350
+ - Agent process was not shut down correctly. Files may be corrupt.
351
+
352
+ EOS
353
+
354
+ return unless ask_yes_no("Proceed?")
355
+
356
+ oldest_binlog = get_oldest_available_binlog
357
+ unrepairable_tables = []
358
+ # Determine the master binlog positions
359
+ sent_binlog_pos = nil
360
+ @full_tables.each do |table|
361
+ binlog_str = table_status_hash[table]["src_pos"]
362
+ unless binlog_str
363
+ raise "Table `#{table}` has no 'src_pos' in its table status"
364
+ end
365
+ binlog_pos = FlydataCore::Mysql::BinlogPos.new(binlog_str)
366
+ if tables.empty?
367
+ if sent_binlog_pos.nil? || sent_binlog_pos < binlog_pos
368
+ sent_binlog_pos = binlog_pos
369
+ end
370
+ else
371
+ if tables.include?(table)
372
+ if sent_binlog_pos.nil? || sent_binlog_pos > binlog_pos
373
+ if oldest_binlog && binlog_pos < oldest_binlog
374
+ unrepairable_tables << table
375
+ else
376
+ sent_binlog_pos = binlog_pos
377
+ end
378
+ end
379
+ end
380
+ end
381
+ end
382
+ if oldest_binlog && sent_binlog_pos < oldest_binlog
383
+ e = AgentError.new("Repair failed due to expired binlog")
384
+ e.description = <<EOS
385
+ Repair failed because the starting binlog position `#{sent_binlog_pos} no longer exists. Run full initial sync instead.
386
+ EOS
387
+ raise e
388
+ end
389
+ master_binlog_pos = FlydataCore::Mysql::BinlogPos.new(sent_binlog_pos.filename, 4)
390
+
391
+ # Delete agent buffer
392
+ log_info_stdout "Deleting data in the agent buffer..."
393
+ files = Flydata::Agent.new(FLYDATA_HOME).delete_buffer_files
394
+ unless files.empty?
395
+ $log.debug "Deleted buffer files\n " + files.join("\n ")
396
+ end
397
+
398
+ # Delete query queue items for the tables
399
+ log_info_stdout "Deleting data stuck in the server buffer..."
400
+ cleanup_sync_server(de, tables, queue_only: true) unless tables.empty?
401
+
402
+ # Save the positions (binlog and seq)
403
+ log_info_stdout "Fixing table positions..."
404
+ @full_tables.each do |table|
405
+ binlog_str = table_status_hash[table]["src_pos"]
406
+ unless binlog_str
407
+ raise "Table `#{table}` has no 'src_pos' in its table status"
408
+ end
409
+ binlog_pos = FlydataCore::Mysql::BinlogPos.new(binlog_str)
410
+ pos = table_status_hash[table]["seq"]
411
+ old_binlog_pos, old_pos = save_table_positions(table, binlog_pos, pos)
412
+ if pos.to_i != old_pos.to_i && !tables.include?(table)
413
+ $log.debug "Fixed broken table position. table:#{table} pos:#{old_pos} -> #{pos}"
414
+ end
415
+ end
416
+
417
+ log_info_stdout "Fixing the master position files..."
418
+ save_master_binlog_positions(master_binlog_pos, sent_binlog_pos)
419
+
420
+ # Remove the lock file if exists.
421
+ File.delete(FLYDATA_LOCK) if File.exists?(FLYDATA_LOCK)
422
+
423
+ log_info_stdout "Repair is done. Start Agent with `flydata start` command."
424
+ end
425
+ run_exclusive :repair
426
+
427
+
428
+ def check(options = {})
429
+ status, pos_mismatch_tables, gap_tables = _check(options)
430
+
431
+ if status.include? :OK
432
+ message = "\nNo errors are found. Sync is clean.\n"
433
+ else
434
+ message = "\nFollowing errors are found.\n"
435
+
436
+ if status.include? :STUCK_AT_PROCESS
437
+ message += " - Data is stuck while processing\n"
438
+ end
439
+ if status.include? :STUCK_AT_UPLOAD
440
+ message += " - Data is stuck while uploading\n"
441
+ end
442
+ if status.include? :ABNORMAL_SHUTDOWN
443
+ message += " - Agent was not shut down correctly\n"
444
+ end
445
+ if gap_tables
446
+ message += " - Sync data is missing for the following table(s)\n"
447
+ gap_tables.each do |bt|
448
+ message += " table:#{bt[:table]}\n"
449
+ end
450
+ message += "\n"
451
+ end
452
+ if pos_mismatch_tables
453
+ message += " - Incorrect table position(s)\n"
454
+ pos_mismatch_tables.each do |bt|
455
+ message += " table:#{bt[:table]}, agent position:#{bt[:agent_seq] ? bt[:agent_seq] : '(missing)'}, server position:#{bt[:server_seq]}\n"
456
+ end
457
+ message += "\n"
458
+ end
459
+ end
460
+ log_info_stdout message
461
+ end
462
+ run_exclusive :check
463
+
464
+ def _check(options = {})
465
+ options[:stop_agent] ||= false
466
+
467
+ set_current_tables
468
+ sender = Flydata::Command::Sender.new
469
+ start_process = !options[:stop_agent] && sender.process_exist?
470
+ pos_mismatch_tables = nil
471
+ gap_tables = nil
472
+ data_stuck_at = nil
473
+ abnormal_shutdown = false
474
+ begin
475
+ begin
476
+ flush_buffer_and_stop(@full_tables, force: false, timeout: 55)
477
+ rescue ServerDataProcessingTimeout => e
478
+ data_stuck_at = e.state
479
+ end
480
+
481
+ # Agent is stopped but locked. There was an abnormal shutdown.
482
+ abnormal_shutdown = sender.agent_locked?
483
+ table_status_hash = get_table_status(@full_tables)
484
+ pos_mismatch_tables = check_position_files(table_status_hash)
485
+ gap_tables = check_gaps(table_status_hash)
486
+ ensure
487
+ Flydata::Command::Sender.new.start(quiet: true) if start_process
488
+ end
489
+
490
+ status = []
491
+ if data_stuck_at == :PROCESS
492
+ status << :STUCK_AT_PROCESS
493
+ end
494
+ if data_stuck_at == :UPLOAD
495
+ status << :STUCK_AT_UPLOAD
496
+ end
497
+ if gap_tables
498
+ status << :TABLE_GAPS
499
+ end
500
+ if pos_mismatch_tables
501
+ status << :TABLE_POS_MISMATCH
502
+ end
503
+ if abnormal_shutdown
504
+ status << :ABNORMAL_SHUTDOWN
505
+ end
506
+ if status.empty?
507
+ status << :OK
508
+ end
509
+ [status, pos_mismatch_tables, gap_tables, table_status_hash]
510
+ end
511
+
311
512
  private
312
513
 
313
514
  # Initial sync
@@ -711,7 +912,7 @@ EOM
711
912
  end
712
913
  prev_message = status['message']
713
914
  if timeout > 0 && Time.now - start_time > timeout
714
- raise ServerDataProcessingTimeout.new
915
+ raise ServerDataProcessingTimeout.new(nil, state: state)
715
916
  end
716
917
  print_progress(status)
717
918
  sleep 10
@@ -824,12 +1025,12 @@ EOM
824
1025
  end
825
1026
  end
826
1027
 
827
- def cleanup_sync_server(de, tables = [])
1028
+ def cleanup_sync_server(de, tables = [], options = {})
828
1029
  print("Cleaning the queued items on the FlyData Servers.")
829
1030
  log_info("Cleaning the queued items on the FlyData Servers.")
830
1031
  worker = Thread.new do
831
1032
  begin
832
- flydata.data_entry.cleanup_sync(de['id'], tables)
1033
+ flydata.data_entry.cleanup_sync(de['id'], tables, options)
833
1034
  rescue RestClient::RequestTimeout, RestClient::GatewayTimeout
834
1035
  # server is taking time to cleanup. Try again
835
1036
  retry
@@ -932,13 +1133,14 @@ Thank you for using FlyData!
932
1133
 
933
1134
  def flush_buffer_and_stop(tables = [], options = {})
934
1135
  sender = Flydata::Command::Sender.new
935
- sender.flush_client_buffer
1136
+ sender.flush_client_buffer(options)
936
1137
  sender.stop(quiet: true)
937
1138
 
938
1139
  return if options[:skip_flush]
939
1140
 
940
- wait_for_server_data_processing(
941
- timeout: SERVER_DATA_PROCESSING_TIMEOUT, tables: tables)
1141
+ timeout = options.has_key?(:timeout) ? options[:timeout]
1142
+ : SERVER_DATA_PROCESSING_TIMEOUT
1143
+ wait_for_server_data_processing( timeout: timeout, tables: tables)
942
1144
  end
943
1145
 
944
1146
  # Utility methods
@@ -1050,6 +1252,100 @@ Thank you for using FlyData!
1050
1252
  end
1051
1253
  raise "These tables are not registered tables: #{inval_table.join(", ")}" unless inval_table.empty?
1052
1254
  end
1255
+
1256
+ def check_position_files(table_status_hash)
1257
+ de = data_entry
1258
+ sync_fm = create_sync_file_manager(de)
1259
+ pos_mismatch_tables = []
1260
+ table_status_hash.keys.collect do |table|
1261
+ table_status = table_status_hash[table]
1262
+
1263
+ server_sequence = table_status["seq"]
1264
+ agent_sequence = sync_fm.get_table_position(table)
1265
+ agent_sequence = agent_sequence.to_i if agent_sequence
1266
+ if server_sequence != agent_sequence
1267
+ pos_mismatch_tables << {table: table, agent_seq: agent_sequence, server_seq: server_sequence}
1268
+ end
1269
+ end
1270
+ pos_mismatch_tables.empty? ? nil : pos_mismatch_tables
1271
+ end
1272
+
1273
+ def check_gaps(table_status_hash)
1274
+ gap_tables = table_status_hash.values.select {|table_status|
1275
+ table_status["num_items"] > 0 && table_status["next_item"] == false
1276
+ }.collect{|table_status| {table: table_status["table_name"]}}
1277
+ gap_tables.empty? ? nil : gap_tables
1278
+ end
1279
+
1280
+ def save_table_positions(table, binlog_pos, pos)
1281
+ de = data_entry
1282
+ sync_fm = create_sync_file_manager(de)
1283
+ s = sync_fm.get_table_binlog_pos(table)
1284
+ old_binlog_pos = s ? FlydataCore::Mysql::BinlogPos.new(s) : nil
1285
+ old_pos = sync_fm.get_table_position(table)
1286
+ if pos.to_i != old_pos.to_i
1287
+ sync_fm.save_table_position(table, pos)
1288
+ $log.debug "table pos updated. table:#{table} pos:#{old_pos} -> #{pos}"
1289
+ end
1290
+ if binlog_pos != old_binlog_pos
1291
+ sync_fm.save_table_binlog_pos(table, binlog_pos.to_s, destination: :positions)
1292
+ $log.debug "table binlog updated. table:#{table} binlog:`#{old_binlog_pos}` -> `#{binlog_pos}`"
1293
+ end
1294
+ [old_binlog_pos, old_pos]
1295
+ end
1296
+
1297
+ def save_master_binlog_positions(master_binlog_pos, sent_binlog_pos)
1298
+ de = data_entry
1299
+ sync_fm = create_sync_file_manager(de)
1300
+ s = sync_fm.load_binlog
1301
+ old_master_binlog_pos = s ? FlydataCore::Mysql::BinlogPos.new(s) : nil
1302
+ s = sync_fm.load_sent_binlog
1303
+ old_sent_binlog_pos = s ? FlydataCore::Mysql::BinlogPos.new(s) : nil
1304
+ if master_binlog_pos != old_master_binlog_pos
1305
+ sync_fm.save_binlog(master_binlog_pos.to_s)
1306
+ $log.debug "master binlog positions updated. `#{old_master_binlog_pos}` -> `#{master_binlog_pos}`"
1307
+ end
1308
+ if sent_binlog_pos != old_sent_binlog_pos
1309
+ sync_fm.save_sent_binlog(sent_binlog_pos.to_s)
1310
+ $log.debug "sent binlog positions updated. `#{old_sent_binlog_pos}` -> `#{sent_binlog_pos}`"
1311
+ end
1312
+ [old_master_binlog_pos, old_sent_binlog_pos]
1313
+ end
1314
+
1315
+ def get_table_status(tables)
1316
+ de = data_entry
1317
+ sync_fm = create_sync_file_manager(de)
1318
+ result = flydata.data_entry.table_status(de['id'], mode: env_mode, tables: tables)
1319
+ result = result["table_status"]
1320
+
1321
+ table_status_hash = result.inject({}){|h, ts| h[ts["table_name"]] = ts; h}
1322
+ missing_tables = tables - table_status_hash.keys
1323
+ unless missing_tables.empty?
1324
+ raise "table status is not available for these table(s): #{missing_tables.join(",")}"
1325
+ end
1326
+
1327
+ populate_initial_binlog_positions(table_status_hash, sync_fm)
1328
+ table_status_hash
1329
+ end
1330
+
1331
+ # table_status has no binlog position for sequence "0". Populate the info
1332
+ # from 'table.binlog.pos.init' file.
1333
+ def populate_initial_binlog_positions(table_status_hash, sync_fm)
1334
+ table_status_hash.keys.each do |table|
1335
+ if table_status_hash[table]["src_pos"] == "-"
1336
+ init_binlog_pos = sync_fm.get_table_binlog_pos_init(table)
1337
+ unless init_binlog_pos
1338
+ raise "File `#{table}.binlog.pos.init` is missing"
1339
+ end
1340
+ table_status_hash[table]["src_pos"] = init_binlog_pos
1341
+ end
1342
+ end
1343
+ end
1344
+
1345
+ # TODO implement
1346
+ def get_oldest_available_binlog
1347
+ nil
1348
+ end
1053
1349
  end
1054
1350
  end
1055
1351
  end
@@ -29,6 +29,11 @@ EOM
29
29
  end
30
30
 
31
31
  class ServerDataProcessingTimeout < AgentError
32
+ def initialize(message, options)
33
+ super(message)
34
+ @state = options[:state]
35
+ end
36
+ attr_reader :state
32
37
  end
33
38
 
34
39
  class DumpParseError < AgentError
@@ -135,9 +135,17 @@ class MysqlBinlogFlydataInput < MysqlBinlogInput
135
135
  )
136
136
  @record_dispatcher = Mysql::FlydataBinlogRecordDispatcher.new(@context)
137
137
  @idle_event_detector = IdleEventDetector.new(@initial_idle_interval, @continuous_idle_interval, @check_interval, @idle_timeout)
138
+ @lock_file = Flydata::FLYDATA_LOCK
138
139
  end
139
140
 
140
141
  def start
142
+ if File.exists?(@lock_file)
143
+ $log.error "Previous process was terminated abnormally. To start, remove the lock file after checking data integrity."
144
+ @abort = true
145
+ Process.kill(:TERM, Process.ppid)
146
+ return
147
+ end
148
+
141
149
  super
142
150
  @idle_event_detector.start do |reason, timestamp|
143
151
  case reason
@@ -155,6 +163,8 @@ class MysqlBinlogFlydataInput < MysqlBinlogInput
155
163
 
156
164
  positions_path = @context.sync_fm.table_positions_dir_path
157
165
  Dir.mkdir positions_path unless File.exists? positions_path
166
+
167
+ File.open(@lock_file, "w") {|f| f.write(Process.pid)}
158
168
  rescue Binlog::Error
159
169
  if (/basic_string::_M_replace_aux/ === $!.to_s)
160
170
  # TODO Fix the root cause in mysql-replication-listener
@@ -172,6 +182,8 @@ EOS
172
182
  end
173
183
 
174
184
  def run
185
+ return if instance_variable_defined? :@abort
186
+
175
187
  @context.table_meta.update
176
188
  Flydata::Mysql::TableDdl.migrate_tables(@context.tables, @db_opts,
177
189
  @context.sync_fm, @position_file,
@@ -252,6 +264,13 @@ EOS
252
264
  end
253
265
 
254
266
  def shutdown
267
+ return if instance_variable_defined? :@abort
268
+
269
+ if File.exists?(@lock_file) &&
270
+ Process.pid == File.open(@lock_file, "r") {|f| f.read}.to_i
271
+ File.delete(@lock_file)
272
+ end
273
+
255
274
  @idle_event_detector.stop
256
275
  if @thread and @thread.alive?
257
276
  $log.info "Requesting stop Kodama"
@@ -6,9 +6,12 @@ module Flydata
6
6
  DUMP_DIR = ENV['FLYDATA_DUMP'] || File.join(FLYDATA_HOME, 'dump')
7
7
  BACKUP_DIR = ENV['FLYDATA_BACKUP'] || File.join(FLYDATA_HOME, 'backup')
8
8
  TABLE_POSITIONS_DIR = ENV['FLYDATA_TABLE_POSITIONS'] || File.join(FLYDATA_HOME, 'positions')
9
+ SYNC_TABLE_POSITIONS = 0
10
+
9
11
  def initialize(data_entry)
10
12
  @data_entry = data_entry
11
13
  @table_position_files = {} # File objects keyed by table name
14
+ @sync_table_positions_count = SYNC_TABLE_POSITIONS
12
15
  end
13
16
 
14
17
  def close
@@ -112,6 +115,13 @@ module Flydata
112
115
  end
113
116
  end
114
117
 
118
+ def load_sent_binlog(file_path = sent_binlog_path)
119
+ return nil unless File.exists?(file_path)
120
+ f, pos = IO.read(file_path).strip.split("\t")
121
+ return nil if f.nil? || f.empty? || pos.nil?
122
+ { binfile: f, pos: pos.to_i }
123
+ end
124
+
115
125
  def sent_binlog_path(master_binlog_path = binlog_path)
116
126
  unless master_binlog_path && master_binlog_path.end_with?('binlog.pos')
117
127
  raise ArgumentError.new("Invalid binlog path. binlog path needs to end with 'binlog.pos'")
@@ -174,7 +184,7 @@ module Flydata
174
184
  file = File.join(table_positions_dir_path, table_name + ".pos")
175
185
  retry_count = 0
176
186
  begin
177
- @table_position_files[table_name] ||= File.open(file, "r+")
187
+ @table_position_files[table_name] ||= (f = File.open(file, File::RDWR); f.sync = true; f)
178
188
  rescue Errno::ENOENT
179
189
  raise if retry_count > 0 # Already retried. Must be a differentfile causing the error
180
190
  # File not exist. Create one with initial value of '0'
@@ -184,22 +194,43 @@ module Flydata
184
194
  end
185
195
  f = @table_position_files[table_name]
186
196
  seq = f.read
197
+ prev_seq_len = seq.size
198
+ f.rewind
187
199
  seq = seq.to_i + 1
188
200
  seq = FlydataCore::QueryJob::SYNC_FIRST_SEQ if seq == 1
189
- begin
190
- yield(seq)
191
- ensure
192
- # when an error happened in yield, the sequence number should remain
193
- # as is. For the next call to read the value correctly, the position
194
- # must be rewound.
195
- f.rewind
201
+ new_seq_len = seq.to_s.size
202
+
203
+ seq_to_write = seq.to_s
204
+ if new_seq_len < prev_seq_len
205
+ seq_to_write += " " * (prev_seq_len - new_seq_len)
196
206
  end
197
- f.truncate(0)
198
- f.write(seq)
199
- f.flush
207
+ # logical transaction starts
208
+ yield(seq)
209
+ f.write(seq_to_write)
210
+ if @sync_table_positions_count > 1
211
+ @sync_table_positions_count -= 1
212
+ elsif @sync_table_positions_count == 1
213
+ f.fsync
214
+ @sync_table_positions_count = SYNC_TABLE_POSITIONS
215
+ end
216
+ # logical transaction ends
217
+ f.truncate(new_seq_len) if new_seq_len < prev_seq_len
200
218
  f.rewind
201
219
  end
202
220
 
221
+ def save_table_position(table_name, seq)
222
+ file = File.join(table_positions_dir_path, table_name + ".pos")
223
+
224
+ File.open(file, "w") {|f| f.write(seq)}
225
+ end
226
+
227
+ def get_table_position(table_name)
228
+ file = File.join(table_positions_dir_path, table_name + ".pos")
229
+ return nil unless File.exists?(file)
230
+
231
+ File.open(file) {|f| f.read}
232
+ end
233
+
203
234
  def sync_info_file
204
235
  File.join(dump_dir, "sync.info")
205
236
  end
@@ -217,8 +248,8 @@ module Flydata
217
248
  tables: items[1].split(" ") }
218
249
  end
219
250
 
220
- def get_table_binlog_pos(table_name)
221
- file = File.join(table_positions_dir_path, table_name + ".binlog.pos")
251
+ def get_table_binlog_pos_init(table_name)
252
+ file = File.join(table_positions_dir_path, table_name + ".binlog.pos.init")
222
253
  return nil unless File.exists?(file)
223
254
  File.open(file, 'r').readline
224
255
  end
@@ -263,15 +294,28 @@ module Flydata
263
294
  end
264
295
  end
265
296
 
266
- def save_table_binlog_pos(tables, binlog_pos)
297
+ def save_table_binlog_pos(tables, binlog_pos, options = {})
298
+ dest_dir = case options[:destination]
299
+ when :positions; table_positions_dir_path
300
+ when :dump; dump_dir
301
+ else dump_dir
302
+ end
303
+
304
+ tables = [ tables ] unless tables.kind_of?(Array)
267
305
  tables.each do |table_name|
268
- file = File.join(dump_dir, table_name + ".binlog.pos")
306
+ file = File.join(dest_dir, table_name + ".binlog.pos")
269
307
  File.open(file, "w") do |f|
270
308
  f.write(binlog_content(binlog_pos))
271
309
  end
272
310
  end
273
311
  end
274
312
 
313
+ def get_table_binlog_pos(table_name)
314
+ file = File.join(table_positions_dir_path, table_name + ".binlog.pos")
315
+ return nil unless File.exists?(file)
316
+ File.open(file, 'r').readline
317
+ end
318
+
275
319
  def install_table_binlog_files(tables)
276
320
  FileUtils.mkdir_p(table_positions_dir_path) unless Dir.exists?(table_positions_dir_path)
277
321
  tables.each do |table_name|
@@ -331,7 +375,9 @@ module Flydata
331
375
  end
332
376
 
333
377
  def binlog_content(binlog_pos)
334
- [binlog_pos[:binfile], binlog_pos[:pos]].join("\t")
378
+ binlog_pos.kind_of?(Hash) ?
379
+ [binlog_pos[:binfile], binlog_pos[:pos]].join("\t")
380
+ : binlog_pos
335
381
  end
336
382
 
337
383
  def load_mysql_table_marshal_dump
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: flydata
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.6
4
+ version: 0.5.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Koichi Fujikawa
@@ -12,7 +12,7 @@ authors:
12
12
  autorequire:
13
13
  bindir: bin
14
14
  cert_chain: []
15
- date: 2015-09-22 00:00:00.000000000 Z
15
+ date: 2015-09-23 00:00:00.000000000 Z
16
16
  dependencies:
17
17
  - !ruby/object:Gem::Dependency
18
18
  name: rest-client
@@ -474,6 +474,7 @@ files:
474
474
  - flydata-core/Gemfile.lock
475
475
  - flydata-core/circle.yml
476
476
  - flydata-core/lib/flydata-core.rb
477
+ - flydata-core/lib/flydata-core/agent.rb
477
478
  - flydata-core/lib/flydata-core/config/user_maintenance.rb
478
479
  - flydata-core/lib/flydata-core/core_ext.rb
479
480
  - flydata-core/lib/flydata-core/core_ext/module.rb
@@ -484,6 +485,7 @@ files:
484
485
  - flydata-core/lib/flydata-core/fluent-plugins/multi_buffer.rb
485
486
  - flydata-core/lib/flydata-core/fluent/config_helper.rb
486
487
  - flydata-core/lib/flydata-core/logger.rb
488
+ - flydata-core/lib/flydata-core/mysql/binlog_pos.rb
487
489
  - flydata-core/lib/flydata-core/mysql/command_generator.rb
488
490
  - flydata-core/lib/flydata-core/mysql/compatibility_checker.rb
489
491
  - flydata-core/lib/flydata-core/mysql/config.rb
@@ -529,6 +531,7 @@ files:
529
531
  - flydata.gemspec
530
532
  - lib/fly_data_model.rb
531
533
  - lib/flydata.rb
534
+ - lib/flydata/agent.rb
532
535
  - lib/flydata/api/agent.rb
533
536
  - lib/flydata/api/base.rb
534
537
  - lib/flydata/api/data_entry.rb
@@ -682,7 +685,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
682
685
  version: '0'
683
686
  requirements: []
684
687
  rubyforge_project:
685
- rubygems_version: 2.4.6
688
+ rubygems_version: 2.4.3
686
689
  signing_key:
687
690
  specification_version: 4
688
691
  summary: FlyData Agent