wal 0.0.29 → 0.0.30
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 +4 -4
- data/exe/wal +5 -121
- data/lib/wal/logging_watcher.rb +46 -0
- data/lib/wal/runner.rb +164 -0
- data/lib/wal/version.rb +1 -1
- data/lib/wal.rb +15 -0
- data/rbi/wal.rbi +7 -1
- metadata +4 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 629931aa6280e9b0f00fbc62ae9cc588771be9631fe2ee4ab5b346f884d52b27
|
|
4
|
+
data.tar.gz: 9144033bc75fceec8b94f33bfb67b3577bc0a1c9731353c56b6c499afd213b33
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 3eb7fcf6d3ceea6f5d72cbcc75ffd67c99fcd688a06cfb8a8e412a428e4c6c5ef7dc44178ab4219d94e4a6e15ea8b4d3c45f00bbf96c33dec6103f688295ca50
|
|
7
|
+
data.tar.gz: f5b8260db91d66be7cf77a391d9a98285bb5773638778afffb2aa02b9b89a306937310104fffed49d8a658f33f0ae818ea35adb138461a3e0634ce341e63af2c
|
data/exe/wal
CHANGED
|
@@ -28,47 +28,6 @@ require "./config/environment"
|
|
|
28
28
|
|
|
29
29
|
db_config = ActiveRecord::Base.configurations.configs_for(name: "primary").configuration_hash
|
|
30
30
|
|
|
31
|
-
class Wal::LoggingReplicator
|
|
32
|
-
def initialize(slot, replicator)
|
|
33
|
-
@slot = slot
|
|
34
|
-
@replicator = replicator
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
def replicate_forever(watcher, publications:)
|
|
38
|
-
replication = @replicator.replicate(watcher, publications:)
|
|
39
|
-
count = 0
|
|
40
|
-
start = Time.now
|
|
41
|
-
loop do
|
|
42
|
-
case (event = replication.next)
|
|
43
|
-
when Wal::BeginTransactionEvent
|
|
44
|
-
start = Time.now
|
|
45
|
-
count = 0
|
|
46
|
-
if event.estimated_size > 0
|
|
47
|
-
Wal.logger&.info("[#{@slot}] Begin transaction=#{event.transaction_id} size=#{event.estimated_size}")
|
|
48
|
-
end
|
|
49
|
-
when Wal::CommitTransactionEvent
|
|
50
|
-
if count > 0
|
|
51
|
-
elapsed = ((Time.now - start) * 1000.0).round(1)
|
|
52
|
-
Wal.logger&.info("[#{@slot}] Commit transaction=#{event.transaction_id} elapsed=#{elapsed} events=#{count}")
|
|
53
|
-
end
|
|
54
|
-
when Wal::InsertEvent
|
|
55
|
-
Wal.logger&.debug("[#{@slot}] Insert transaction=#{event.transaction_id} table=#{event.table} primary_key=#{event.primary_key}")
|
|
56
|
-
count += 1
|
|
57
|
-
when Wal::UpdateEvent
|
|
58
|
-
Wal.logger&.debug("[#{@slot}] Update transaction=#{event.transaction_id} table=#{event.table} primary_key=#{event.primary_key}")
|
|
59
|
-
count += 1
|
|
60
|
-
when Wal::DeleteEvent
|
|
61
|
-
Wal.logger&.debug("[#{@slot}] Delete transaction=#{event.transaction_id} table=#{event.table} primary_key=#{event.primary_key}")
|
|
62
|
-
count += 1
|
|
63
|
-
else
|
|
64
|
-
count += 1
|
|
65
|
-
end
|
|
66
|
-
end
|
|
67
|
-
rescue StopIteration
|
|
68
|
-
nil
|
|
69
|
-
end
|
|
70
|
-
end
|
|
71
|
-
|
|
72
31
|
def watch(db_config, cli)
|
|
73
32
|
watcher = cli["--watcher"].constantize.new
|
|
74
33
|
use_temporary_slot = cli["--tmp-slot"] || false
|
|
@@ -78,90 +37,15 @@ def watch(db_config, cli)
|
|
|
78
37
|
replicator_class = cli["--replicator"].presence&.constantize || Wal::Replicator
|
|
79
38
|
|
|
80
39
|
puts "Watcher started for #{replication_slot} slot (#{publications.join(", ")})"
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
40
|
+
replicator_class
|
|
41
|
+
.new(replication_slot:, use_temporary_slot:, db_config:)
|
|
42
|
+
.replicate_forever(Wal::LoggingWatcher.new(replication_slot, watcher), publications:)
|
|
84
43
|
puts "Watcher finished for #{replication_slot}"
|
|
85
44
|
end
|
|
86
45
|
|
|
87
|
-
def start_worker(db_config, slot, config)
|
|
88
|
-
watcher = config["watcher"].constantize.new
|
|
89
|
-
temporary = config["temporary"] || false
|
|
90
|
-
publications = config["publications"] || []
|
|
91
|
-
replicator_class = config["replicator"].presence&.constantize || Wal::Replicator
|
|
92
|
-
auto_restart = config["auto_restart"].nil? || config["auto_restart"]
|
|
93
|
-
max_retries = config["retries"]&.to_i || (2**32 - 1)
|
|
94
|
-
retries = 0
|
|
95
|
-
backoff = config["retry_backoff"]&.to_f || 1
|
|
96
|
-
backoff_expoent = config["retry_backoff_expoent"]&.to_f
|
|
97
|
-
|
|
98
|
-
Thread.new(slot, watcher, temporary, publications) do |replication_slot, watcher, use_temporary_slot, publications|
|
|
99
|
-
replication_slot = "#{replication_slot}_#{SecureRandom.alphanumeric(4)}" if use_temporary_slot
|
|
100
|
-
puts "Watcher started for #{replication_slot} slot (#{publications.join(", ")})"
|
|
101
|
-
|
|
102
|
-
begin
|
|
103
|
-
replicator = replicator_class.new(replication_slot:, use_temporary_slot:, db_config:)
|
|
104
|
-
replicator = Wal::LoggingReplicator.new(replication_slot, replicator)
|
|
105
|
-
replicator.replicate_forever(watcher, publications:)
|
|
106
|
-
if auto_restart
|
|
107
|
-
backoff_time = backoff_expoent ? (backoff * retries) ** backoff_expoent : backoff
|
|
108
|
-
puts "Watcher finished for #{replication_slot}, auto restarting in #{backoff_time.floor(2)}..."
|
|
109
|
-
sleep backoff_time
|
|
110
|
-
puts "Restarting #{replication_slot}"
|
|
111
|
-
redo
|
|
112
|
-
end
|
|
113
|
-
rescue StandardError => err
|
|
114
|
-
if retries < max_retries
|
|
115
|
-
Wal.logger&.error("[#{replication_slot}] Error #{err}")
|
|
116
|
-
retries += 1
|
|
117
|
-
backoff_time = backoff_expoent ? (backoff * retries) ** backoff_expoent : backoff
|
|
118
|
-
puts "Restarting #{replication_slot} in #{backoff_time.floor(2)}s..."
|
|
119
|
-
sleep backoff_time
|
|
120
|
-
puts "Restarting #{replication_slot}"
|
|
121
|
-
retry
|
|
122
|
-
end
|
|
123
|
-
raise
|
|
124
|
-
end
|
|
125
|
-
|
|
126
|
-
puts "Watcher finished for #{replication_slot}"
|
|
127
|
-
|
|
128
|
-
Process.kill("TERM", Process.pid)
|
|
129
|
-
end
|
|
130
|
-
end
|
|
131
|
-
|
|
132
|
-
def start(db_config, cli)
|
|
133
|
-
slots = YAML.load_file(cli["<config-file>"])["slots"]
|
|
134
|
-
|
|
135
|
-
workers = slots.map do |slot, config|
|
|
136
|
-
start_worker(db_config, slot, config)
|
|
137
|
-
end
|
|
138
|
-
|
|
139
|
-
ping = Thread.new do
|
|
140
|
-
loop do
|
|
141
|
-
ActiveRecord::Base.connection_pool.with_connection do |conn|
|
|
142
|
-
conn.execute("SELECT pg_logical_emit_message(true, 'wal_ping', '{}')")
|
|
143
|
-
end
|
|
144
|
-
sleep 20
|
|
145
|
-
end
|
|
146
|
-
end
|
|
147
|
-
|
|
148
|
-
workers << ping
|
|
149
|
-
|
|
150
|
-
stop_workers = proc do
|
|
151
|
-
puts "Stopping WAL workers..."
|
|
152
|
-
workers.each(&:kill)
|
|
153
|
-
puts "WAL workers stopped"
|
|
154
|
-
exit 0
|
|
155
|
-
end
|
|
156
|
-
|
|
157
|
-
Signal.trap("TERM", &stop_workers)
|
|
158
|
-
Signal.trap("INT", &stop_workers)
|
|
159
|
-
|
|
160
|
-
workers.each(&:join)
|
|
161
|
-
end
|
|
162
|
-
|
|
163
46
|
if cli["watch"]
|
|
164
47
|
watch(db_config, cli)
|
|
165
48
|
elsif cli["start"]
|
|
166
|
-
|
|
49
|
+
runner = Wal::Runner.new(config_file: cli["<config-file>"], db_config:)
|
|
50
|
+
runner.start
|
|
167
51
|
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
module Wal
|
|
2
|
+
class LoggingWatcher
|
|
3
|
+
include Wal::Watcher
|
|
4
|
+
|
|
5
|
+
def initialize(slot, watcher)
|
|
6
|
+
@slot = slot
|
|
7
|
+
@watcher = watcher
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def should_watch_table?(table)
|
|
11
|
+
@watcher.should_watch_table? table
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def valid_context_prefix?(prefix)
|
|
15
|
+
@watcher.valid_context_prefix? prefix
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def on_event(event)
|
|
19
|
+
case event
|
|
20
|
+
when Wal::BeginTransactionEvent
|
|
21
|
+
@start = Time.now
|
|
22
|
+
@count = 0
|
|
23
|
+
if event.estimated_size > 0
|
|
24
|
+
Wal.logger&.debug("[#{@slot}] Begin transaction=#{event.transaction_id} size=#{event.estimated_size}")
|
|
25
|
+
end
|
|
26
|
+
when Wal::CommitTransactionEvent
|
|
27
|
+
if @count > 0
|
|
28
|
+
elapsed = ((Time.now - @start) * 1000.0).round(1)
|
|
29
|
+
Wal.logger&.info("[#{@slot}] Commit transaction=#{event.transaction_id} elapsed=#{elapsed} events=#{@count}")
|
|
30
|
+
end
|
|
31
|
+
when Wal::InsertEvent
|
|
32
|
+
Wal.logger&.debug("[#{@slot}] Insert transaction=#{event.transaction_id} table=#{event.table} primary_key=#{event.primary_key}")
|
|
33
|
+
@count += 1
|
|
34
|
+
when Wal::UpdateEvent
|
|
35
|
+
Wal.logger&.debug("[#{@slot}] Update transaction=#{event.transaction_id} table=#{event.table} primary_key=#{event.primary_key}")
|
|
36
|
+
@count += 1
|
|
37
|
+
when Wal::DeleteEvent
|
|
38
|
+
Wal.logger&.debug("[#{@slot}] Delete transaction=#{event.transaction_id} table=#{event.table} primary_key=#{event.primary_key}")
|
|
39
|
+
@count += 1
|
|
40
|
+
else
|
|
41
|
+
@count += 1
|
|
42
|
+
end
|
|
43
|
+
@watcher.on_event(event)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
data/lib/wal/runner.rb
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
module Wal
|
|
2
|
+
class Runner
|
|
3
|
+
class Worker
|
|
4
|
+
attr_reader :name, :slot_configs, :db_config
|
|
5
|
+
|
|
6
|
+
def initialize(name:, slot_configs:, db_config:)
|
|
7
|
+
@name = name
|
|
8
|
+
@slot_configs = slot_configs
|
|
9
|
+
@db_config = db_config
|
|
10
|
+
@threads = []
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def run
|
|
14
|
+
@threads = slot_configs.map { |slot, config| start_worker(slot, config) }
|
|
15
|
+
setup_signal_handlers
|
|
16
|
+
@threads.each(&:join)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
private
|
|
20
|
+
|
|
21
|
+
def start_worker(slot, config)
|
|
22
|
+
watcher = config["watcher"].constantize.new
|
|
23
|
+
temporary = config["temporary"] || false
|
|
24
|
+
publications = config["publications"] || []
|
|
25
|
+
replicator_class = config["replicator"].presence&.constantize || Wal::Replicator
|
|
26
|
+
auto_restart = config["auto_restart"].nil? || config["auto_restart"]
|
|
27
|
+
max_retries = config["retries"]&.to_i || (2**32 - 1)
|
|
28
|
+
backoff = config["retry_backoff"]&.to_f || 1
|
|
29
|
+
backoff_exponent = config["retry_backoff_expoent"]&.to_f
|
|
30
|
+
|
|
31
|
+
Thread.new(slot, watcher, temporary, publications) do |replication_slot, watcher, use_temporary_slot, publications|
|
|
32
|
+
retries = 0
|
|
33
|
+
replication_slot = "#{replication_slot}_#{SecureRandom.alphanumeric(4)}" if use_temporary_slot
|
|
34
|
+
puts "Watcher started for #{replication_slot} slot (#{publications.join(", ")})"
|
|
35
|
+
|
|
36
|
+
begin
|
|
37
|
+
replicator_class
|
|
38
|
+
.new(replication_slot:, use_temporary_slot:, db_config:)
|
|
39
|
+
.replicate_forever(Wal::LoggingWatcher.new(replication_slot, watcher), publications:)
|
|
40
|
+
if auto_restart
|
|
41
|
+
backoff_time = backoff_exponent ? (backoff * retries) ** backoff_exponent : backoff
|
|
42
|
+
puts "Watcher finished for #{replication_slot}, auto restarting in #{backoff_time.floor(2)}..."
|
|
43
|
+
sleep backoff_time
|
|
44
|
+
puts "Restarting #{replication_slot}"
|
|
45
|
+
redo
|
|
46
|
+
end
|
|
47
|
+
rescue StandardError => err
|
|
48
|
+
if retries < max_retries
|
|
49
|
+
Wal.logger&.error("[#{replication_slot}] Error #{err}")
|
|
50
|
+
Wal.logger&.error([err.message, *err.backtrace].join("\n"))
|
|
51
|
+
retries += 1
|
|
52
|
+
backoff_time = backoff_exponent ? (backoff * retries) ** backoff_exponent : backoff
|
|
53
|
+
puts "Restarting #{replication_slot} in #{backoff_time.floor(2)}s..."
|
|
54
|
+
sleep backoff_time
|
|
55
|
+
puts "Restarting #{replication_slot}"
|
|
56
|
+
retry
|
|
57
|
+
end
|
|
58
|
+
raise
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
puts "Watcher finished for #{replication_slot}"
|
|
62
|
+
Process.kill("TERM", Process.pid)
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def setup_signal_handlers
|
|
67
|
+
stop_threads = proc do
|
|
68
|
+
puts "[#{name}] Stopping WAL threads..."
|
|
69
|
+
@threads.each(&:kill)
|
|
70
|
+
puts "[#{name}] WAL threads stopped"
|
|
71
|
+
exit 0
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
Signal.trap("TERM", &stop_threads)
|
|
75
|
+
Signal.trap("INT", &stop_threads)
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
attr_reader :config_file, :db_config
|
|
80
|
+
|
|
81
|
+
def initialize(config_file:, db_config:)
|
|
82
|
+
@config_file = config_file
|
|
83
|
+
@db_config = db_config
|
|
84
|
+
@child_pids = []
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def start
|
|
88
|
+
slots = YAML.load_file(config_file)["slots"]
|
|
89
|
+
workers_slots = slots.group_by { |_slot, config| config["worker"] || "default" }
|
|
90
|
+
|
|
91
|
+
Wal.fork_hooks[:before_fork]&.call
|
|
92
|
+
|
|
93
|
+
workers_slots.each do |worker_name, slot_configs|
|
|
94
|
+
pid = fork_worker(worker_name, slot_configs)
|
|
95
|
+
@child_pids << pid
|
|
96
|
+
puts "Spawned worker '#{worker_name}' with PID #{pid}"
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
@ping_thread = start_ping_thread
|
|
100
|
+
setup_signal_handlers
|
|
101
|
+
wait_for_workers
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
private
|
|
105
|
+
|
|
106
|
+
def fork_worker(worker_name, slot_configs)
|
|
107
|
+
Process.fork do
|
|
108
|
+
Wal.fork_hooks[:after_fork]&.call
|
|
109
|
+
puts "[#{worker_name}] Starting worker process (PID: #{Process.pid})"
|
|
110
|
+
worker = Worker.new(name: worker_name, slot_configs: slot_configs, db_config: db_config)
|
|
111
|
+
worker.run
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def start_ping_thread
|
|
116
|
+
Thread.new do
|
|
117
|
+
loop do
|
|
118
|
+
ActiveRecord::Base.connection_pool.with_connection do |conn|
|
|
119
|
+
conn.execute("SELECT pg_logical_emit_message(true, 'wal_ping', '{}')")
|
|
120
|
+
end
|
|
121
|
+
sleep 20
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def setup_signal_handlers
|
|
127
|
+
stop_workers = proc do
|
|
128
|
+
puts "Stopping all worker processes..."
|
|
129
|
+
@ping_thread&.kill
|
|
130
|
+
@child_pids.each do |pid|
|
|
131
|
+
Process.kill("TERM", pid)
|
|
132
|
+
rescue Errno::ESRCH
|
|
133
|
+
# Process already dead
|
|
134
|
+
end
|
|
135
|
+
@child_pids.each do |pid|
|
|
136
|
+
Process.wait(pid)
|
|
137
|
+
rescue Errno::ECHILD
|
|
138
|
+
# Already reaped
|
|
139
|
+
end
|
|
140
|
+
puts "All worker processes stopped"
|
|
141
|
+
exit 0
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
Signal.trap("TERM", &stop_workers)
|
|
145
|
+
Signal.trap("INT", &stop_workers)
|
|
146
|
+
|
|
147
|
+
@stop_workers = stop_workers
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def wait_for_workers
|
|
151
|
+
loop do
|
|
152
|
+
exited_pid = Process.wait(-1)
|
|
153
|
+
if @child_pids.include?(exited_pid)
|
|
154
|
+
@child_pids.delete(exited_pid)
|
|
155
|
+
puts "Worker process #{exited_pid} exited unexpectedly, shutting down..."
|
|
156
|
+
@stop_workers.call
|
|
157
|
+
end
|
|
158
|
+
rescue Errno::ECHILD
|
|
159
|
+
puts "All worker processes have exited"
|
|
160
|
+
break
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
end
|
data/lib/wal/version.rb
CHANGED
data/lib/wal.rb
CHANGED
|
@@ -4,20 +4,35 @@ require "active_support"
|
|
|
4
4
|
require "active_record"
|
|
5
5
|
require_relative "wal/watcher"
|
|
6
6
|
require_relative "wal/noop_watcher"
|
|
7
|
+
require_relative "wal/logging_watcher"
|
|
7
8
|
require_relative "wal/record_watcher"
|
|
8
9
|
require_relative "wal/streaming_watcher"
|
|
9
10
|
require_relative "wal/replicator"
|
|
10
11
|
require_relative "wal/active_record_context_extension"
|
|
11
12
|
require_relative "wal/railtie"
|
|
13
|
+
require_relative "wal/runner"
|
|
12
14
|
require_relative "wal/version"
|
|
13
15
|
|
|
14
16
|
module Wal
|
|
15
17
|
class << self
|
|
16
18
|
attr_accessor :logger
|
|
19
|
+
attr_accessor :fork_hooks
|
|
17
20
|
|
|
18
21
|
def logger
|
|
19
22
|
@logger ||= Logger.new($stdout, level: :info)
|
|
20
23
|
end
|
|
24
|
+
|
|
25
|
+
def fork_hooks
|
|
26
|
+
@fork_hooks ||= {}
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def before_fork(&block)
|
|
30
|
+
fork_hooks[:before_fork] = block
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def after_fork(&block)
|
|
34
|
+
fork_hooks[:after_fork] = block
|
|
35
|
+
end
|
|
21
36
|
end
|
|
22
37
|
|
|
23
38
|
def self.configure(&block)
|
data/rbi/wal.rbi
CHANGED
|
@@ -7,11 +7,17 @@ module Wal
|
|
|
7
7
|
UpdateEvent,
|
|
8
8
|
DeleteEvent,
|
|
9
9
|
) }
|
|
10
|
-
VERSION = "0.0.
|
|
10
|
+
VERSION = "0.0.30"
|
|
11
11
|
|
|
12
12
|
class << self
|
|
13
13
|
sig { returns(T.class_of(Logger)) }
|
|
14
14
|
attr_accessor :logger
|
|
15
|
+
|
|
16
|
+
sig { params(block: T.proc.void).void }
|
|
17
|
+
def before_fork(&block); end
|
|
18
|
+
|
|
19
|
+
sig { params(block: T.proc.void).void }
|
|
20
|
+
def after_fork(&block); end
|
|
15
21
|
end
|
|
16
22
|
|
|
17
23
|
sig { params(block: T.proc.params(config: T.class_of(Wal)).void).void }
|
metadata
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: wal
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.0.
|
|
4
|
+
version: 0.0.30
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Rodrigo Navarro
|
|
8
8
|
bindir: exe
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date: 2026-01-
|
|
10
|
+
date: 2026-01-21 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: pg
|
|
@@ -95,11 +95,13 @@ files:
|
|
|
95
95
|
- lib/wal/active_record_context_extension.rb
|
|
96
96
|
- lib/wal/generators/migration.rb
|
|
97
97
|
- lib/wal/generators/templates/migration.rb.erb
|
|
98
|
+
- lib/wal/logging_watcher.rb
|
|
98
99
|
- lib/wal/migration.rb
|
|
99
100
|
- lib/wal/noop_watcher.rb
|
|
100
101
|
- lib/wal/railtie.rb
|
|
101
102
|
- lib/wal/record_watcher.rb
|
|
102
103
|
- lib/wal/replicator.rb
|
|
104
|
+
- lib/wal/runner.rb
|
|
103
105
|
- lib/wal/streaming_watcher.rb
|
|
104
106
|
- lib/wal/version.rb
|
|
105
107
|
- lib/wal/watcher.rb
|