wal 0.0.32 → 0.0.34
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 +13 -19
- data/lib/wal/logging_watcher.rb +25 -8
- data/lib/wal/runner.rb +37 -15
- data/lib/wal/version.rb +1 -1
- data/lib/wal.rb +13 -5
- data/rbi/wal.rbi +12 -3
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f7a8f82addc9ffce03d2f1ee6a77bac1f3ae2074a5b427a8c60ca087df2aded6
|
|
4
|
+
data.tar.gz: 75559dccba1edf1d0f1a60ebf866726705fe48166d697a1859ec6da644e963f3
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 0d0d206e647386006b07e1a89e1a5596346a50894d0260d5def25876902abcbd398c07889a03ac43c88041343e576b2793eec8a1eecdcf47ff1f025587721a38
|
|
7
|
+
data.tar.gz: 62b2dbbeadf2c8076f2dee4ad1a95491552dfb0be3af646b03ffa449dd54009cfd940020fe4dfe3b69c2c5e7a707bdafdc2959baebe109ba28d288b31a086031
|
data/exe/wal
CHANGED
|
@@ -7,7 +7,7 @@ begin
|
|
|
7
7
|
Usage:
|
|
8
8
|
wal watch --watcher <watcher-class>
|
|
9
9
|
(--slot <replication-slot> | --tmp-slot)
|
|
10
|
-
|
|
10
|
+
--publication <publication>...
|
|
11
11
|
[--replicator <replicator-class>]
|
|
12
12
|
wal start <config-file>
|
|
13
13
|
|
|
@@ -16,7 +16,7 @@ begin
|
|
|
16
16
|
--watcher=<watcher-class> The watcher class to be used to listen for WAL changes.
|
|
17
17
|
--slot=<replication-slot> The replication slot that will be used.
|
|
18
18
|
--tmp-slot Use a temporary replication slot.
|
|
19
|
-
[--publication=<publication>...]
|
|
19
|
+
[--publication=<publication>...] Postgres publications to subscribe to.
|
|
20
20
|
[--replicator=<replicatior_class>] Change the replication driver class
|
|
21
21
|
DOCOPT
|
|
22
22
|
rescue Docopt::Exit => err
|
|
@@ -28,24 +28,18 @@ require "./config/environment"
|
|
|
28
28
|
|
|
29
29
|
db_config = ActiveRecord::Base.configurations.configs_for(name: "primary").configuration_hash
|
|
30
30
|
|
|
31
|
-
def watch(db_config, cli)
|
|
32
|
-
watcher = cli["--watcher"].constantize.new
|
|
33
|
-
use_temporary_slot = cli["--tmp-slot"] || false
|
|
34
|
-
replication_slot = cli["--slot"]
|
|
35
|
-
replication_slot = replication_slot.presence || "wal_watcher_#{SecureRandom.alphanumeric(4)}" if use_temporary_slot
|
|
36
|
-
publications = cli["--publication"] || []
|
|
37
|
-
replicator_class = cli["--replicator"].presence&.constantize || Wal::Replicator
|
|
38
|
-
|
|
39
|
-
puts "Watcher started for #{replication_slot} slot (#{publications.join(", ")})"
|
|
40
|
-
replicator_class
|
|
41
|
-
.new(replication_slot:, use_temporary_slot:, db_config:)
|
|
42
|
-
.replicate_forever(Wal::LoggingWatcher.new(replication_slot, watcher), publications:)
|
|
43
|
-
puts "Watcher finished for #{replication_slot}"
|
|
44
|
-
end
|
|
45
|
-
|
|
46
31
|
if cli["watch"]
|
|
47
|
-
|
|
32
|
+
config = {
|
|
33
|
+
"watcher" => cli["--watcher"],
|
|
34
|
+
"temporary" => cli["--tmp-slot"],
|
|
35
|
+
"publications" => cli["--publication"] || [],
|
|
36
|
+
"replicator" => cli["--replicator"],
|
|
37
|
+
}
|
|
38
|
+
slot = cli["--slot"].presence || "wal_watcher_#{SecureRandom.alphanumeric(4)}"
|
|
39
|
+
runner = Wal::Runner.new(config: { "slots" => { slot => config } }, db_config:)
|
|
40
|
+
runner.start
|
|
48
41
|
elsif cli["start"]
|
|
49
|
-
|
|
42
|
+
config = YAML.load_file(cli["<config-file>"])
|
|
43
|
+
runner = Wal::Runner.new(config:, db_config:)
|
|
50
44
|
runner.start
|
|
51
45
|
end
|
data/lib/wal/logging_watcher.rb
CHANGED
|
@@ -5,6 +5,8 @@ module Wal
|
|
|
5
5
|
def initialize(slot, watcher)
|
|
6
6
|
@slot = slot
|
|
7
7
|
@watcher = watcher
|
|
8
|
+
@actions = Set.new
|
|
9
|
+
@tables = Set.new
|
|
8
10
|
end
|
|
9
11
|
|
|
10
12
|
def should_watch_table?(table)
|
|
@@ -18,25 +20,40 @@ module Wal
|
|
|
18
20
|
def on_event(event)
|
|
19
21
|
case event
|
|
20
22
|
when Wal::BeginTransactionEvent
|
|
21
|
-
@start =
|
|
23
|
+
@start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
22
24
|
@count = 0
|
|
23
|
-
|
|
24
|
-
Wal.logger&.debug("[#{@slot}] Begin transaction=#{event.transaction_id} size=#{event.estimated_size}")
|
|
25
|
-
end
|
|
25
|
+
Wal.logger&.debug("[#{@slot}] Begin transaction=#{event.transaction_id} size=#{event.estimated_size}")
|
|
26
26
|
when Wal::CommitTransactionEvent
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
27
|
+
elapsed = ((Process.clock_gettime(Process::CLOCK_MONOTONIC) - @start) * 1000).round(3)
|
|
28
|
+
message = "[#{@slot}] Commit transaction=#{event.transaction_id} elapsed=#{elapsed} events=#{@count}"
|
|
29
|
+
message << " actions=#{@actions.sort.join(",")}" unless @actions.empty?
|
|
30
|
+
message << " tables=#{@tables.sort.join(",")}" unless @tables.empty?
|
|
31
|
+
if @count > 1
|
|
32
|
+
Wal.logger&.info(message)
|
|
33
|
+
else
|
|
34
|
+
Wal.logger&.debug(message)
|
|
30
35
|
end
|
|
36
|
+
@actions.clear
|
|
37
|
+
@tables.clear
|
|
31
38
|
when Wal::InsertEvent
|
|
32
39
|
Wal.logger&.debug("[#{@slot}] Insert transaction=#{event.transaction_id} table=#{event.table} primary_key=#{event.primary_key}")
|
|
33
40
|
@count += 1
|
|
41
|
+
@actions << :insert
|
|
42
|
+
@tables << event.table
|
|
34
43
|
when Wal::UpdateEvent
|
|
35
|
-
Wal.logger&.
|
|
44
|
+
if Wal.logger&.level >= Logger::DEBUG
|
|
45
|
+
message = "[#{@slot}] Update transaction=#{event.transaction_id} table=#{event.table} primary_key=#{event.primary_key}"
|
|
46
|
+
message << " changed=#{event.diff.keys.join(",")}" if event.diff && !event.diff.empty?
|
|
47
|
+
Wal.logger&.debug(message)
|
|
48
|
+
end
|
|
36
49
|
@count += 1
|
|
50
|
+
@actions << :update
|
|
51
|
+
@tables << event.table
|
|
37
52
|
when Wal::DeleteEvent
|
|
38
53
|
Wal.logger&.debug("[#{@slot}] Delete transaction=#{event.transaction_id} table=#{event.table} primary_key=#{event.primary_key}")
|
|
39
54
|
@count += 1
|
|
55
|
+
@actions << :delete
|
|
56
|
+
@tables << event.table
|
|
40
57
|
else
|
|
41
58
|
@count += 1
|
|
42
59
|
end
|
data/lib/wal/runner.rb
CHANGED
|
@@ -32,30 +32,38 @@ module Wal
|
|
|
32
32
|
Thread.new(slot, watcher, temporary, publications) do |replication_slot, watcher, use_temporary_slot, publications|
|
|
33
33
|
retries = 0
|
|
34
34
|
replication_slot = "#{replication_slot}_#{SecureRandom.alphanumeric(4)}" if use_temporary_slot
|
|
35
|
-
puts "Watcher started for #{replication_slot} slot (#{publications.join(", ")})"
|
|
35
|
+
puts "[#{replication_slot}] Watcher started for #{replication_slot} slot (#{publications.join(", ")})"
|
|
36
36
|
|
|
37
37
|
begin
|
|
38
|
+
Wal.hooks[:on_slot_start]&.call(slot, config)
|
|
39
|
+
|
|
38
40
|
replicator_class
|
|
39
41
|
.new(db_config:, **replicator_params, replication_slot:, use_temporary_slot:)
|
|
40
42
|
.replicate_forever(Wal::LoggingWatcher.new(replication_slot, watcher), publications:)
|
|
43
|
+
|
|
44
|
+
Wal.hooks[:on_slot_finish]&.call(slot, config)
|
|
45
|
+
|
|
41
46
|
if auto_restart
|
|
42
47
|
backoff_time = backoff_exponent ? (backoff * retries) ** backoff_exponent : backoff
|
|
43
|
-
puts "Watcher finished for #{replication_slot}, auto restarting in #{backoff_time.floor(2)}..."
|
|
48
|
+
puts "[#{replication_slot}] Watcher finished for #{replication_slot}, auto restarting in #{backoff_time.floor(2)}..."
|
|
44
49
|
sleep backoff_time
|
|
45
|
-
puts "
|
|
50
|
+
puts "[#{replication_slot}] Restarting"
|
|
46
51
|
redo
|
|
47
52
|
end
|
|
48
53
|
rescue ArgumentError
|
|
49
54
|
raise
|
|
50
55
|
rescue StandardError => err
|
|
56
|
+
Wal.hooks[:on_slot_error]&.call(err, slot, config)
|
|
57
|
+
|
|
58
|
+
Wal.logger&.error("[#{replication_slot}] Error #{err}")
|
|
59
|
+
Wal.logger&.error([err.message, *err.backtrace].join("\n"))
|
|
60
|
+
|
|
51
61
|
if retries < max_retries
|
|
52
|
-
Wal.logger&.error("[#{replication_slot}] Error #{err}")
|
|
53
|
-
Wal.logger&.error([err.message, *err.backtrace].join("\n"))
|
|
54
62
|
retries += 1
|
|
55
63
|
backoff_time = backoff_exponent ? (backoff * retries) ** backoff_exponent : backoff
|
|
56
|
-
puts "Restarting #{replication_slot} in #{backoff_time.floor(2)}s..."
|
|
64
|
+
puts "#{replication_slot}] Restarting #{replication_slot} in #{backoff_time.floor(2)}s..."
|
|
57
65
|
sleep backoff_time
|
|
58
|
-
puts "Restarting #{replication_slot}"
|
|
66
|
+
puts "#{replication_slot}] Restarting #{replication_slot}"
|
|
59
67
|
retry
|
|
60
68
|
end
|
|
61
69
|
raise
|
|
@@ -79,24 +87,38 @@ module Wal
|
|
|
79
87
|
end
|
|
80
88
|
end
|
|
81
89
|
|
|
82
|
-
attr_reader :
|
|
90
|
+
attr_reader :config, :db_config
|
|
83
91
|
|
|
84
|
-
def initialize(
|
|
85
|
-
@
|
|
92
|
+
def initialize(config:, db_config:)
|
|
93
|
+
@config = config
|
|
86
94
|
@db_config = db_config
|
|
87
95
|
@child_pids = []
|
|
88
96
|
end
|
|
89
97
|
|
|
90
98
|
def start
|
|
91
|
-
|
|
92
|
-
|
|
99
|
+
workers_slots = config["slots"].group_by { |_slot, slot_config| slot_config["worker"] || "default" }
|
|
100
|
+
|
|
101
|
+
if workers_slots.size == 1
|
|
102
|
+
run_single_worker(workers_slots.first)
|
|
103
|
+
else
|
|
104
|
+
run_forked_workers(workers_slots)
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def run_single_worker((worker_name, slot_configs))
|
|
109
|
+
@ping_thread = start_ping_thread
|
|
110
|
+
puts "[#{worker_name}] Starting worker process (PID: #{Process.pid})"
|
|
111
|
+
worker = Worker.new(name: worker_name, slot_configs: slot_configs, db_config: db_config)
|
|
112
|
+
worker.run
|
|
113
|
+
end
|
|
93
114
|
|
|
94
|
-
|
|
115
|
+
def run_forked_workers(workers_slots)
|
|
116
|
+
Wal.hooks[:before_fork]&.call(workers_slots)
|
|
95
117
|
|
|
96
118
|
workers_slots.each do |worker_name, slot_configs|
|
|
97
119
|
pid = fork_worker(worker_name, slot_configs)
|
|
98
120
|
@child_pids << pid
|
|
99
|
-
puts "Spawned worker '#{worker_name}' with PID #{pid}"
|
|
121
|
+
puts "[#{worker_name}] Spawned worker '#{worker_name}' with PID #{pid}"
|
|
100
122
|
end
|
|
101
123
|
|
|
102
124
|
@ping_thread = start_ping_thread
|
|
@@ -108,7 +130,7 @@ module Wal
|
|
|
108
130
|
|
|
109
131
|
def fork_worker(worker_name, slot_configs)
|
|
110
132
|
Process.fork do
|
|
111
|
-
Wal.
|
|
133
|
+
Wal.hooks[:after_fork]&.call(worker_name, slot_configs)
|
|
112
134
|
puts "[#{worker_name}] Starting worker process (PID: #{Process.pid})"
|
|
113
135
|
worker = Worker.new(name: worker_name, slot_configs: slot_configs, db_config: db_config)
|
|
114
136
|
worker.run
|
data/lib/wal/version.rb
CHANGED
data/lib/wal.rb
CHANGED
|
@@ -16,22 +16,30 @@ require_relative "wal/version"
|
|
|
16
16
|
module Wal
|
|
17
17
|
class << self
|
|
18
18
|
attr_accessor :logger
|
|
19
|
-
attr_accessor :
|
|
19
|
+
attr_accessor :hooks
|
|
20
20
|
|
|
21
21
|
def logger
|
|
22
22
|
@logger ||= Logger.new($stdout, level: :info)
|
|
23
23
|
end
|
|
24
24
|
|
|
25
|
-
def
|
|
26
|
-
@
|
|
25
|
+
def hooks
|
|
26
|
+
@hooks ||= {}
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def on_slot_finished(&block)
|
|
30
|
+
hooks[:on_slot_finished] = block
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def on_slot_error(&block)
|
|
34
|
+
hooks[:on_slot_error] = block
|
|
27
35
|
end
|
|
28
36
|
|
|
29
37
|
def before_fork(&block)
|
|
30
|
-
|
|
38
|
+
hooks[:before_fork] = block
|
|
31
39
|
end
|
|
32
40
|
|
|
33
41
|
def after_fork(&block)
|
|
34
|
-
|
|
42
|
+
hooks[:after_fork] = block
|
|
35
43
|
end
|
|
36
44
|
end
|
|
37
45
|
|
data/rbi/wal.rbi
CHANGED
|
@@ -7,17 +7,26 @@ module Wal
|
|
|
7
7
|
UpdateEvent,
|
|
8
8
|
DeleteEvent,
|
|
9
9
|
) }
|
|
10
|
-
VERSION = "0.0.
|
|
10
|
+
VERSION = "0.0.34"
|
|
11
11
|
|
|
12
12
|
class << self
|
|
13
13
|
sig { returns(T.class_of(Logger)) }
|
|
14
14
|
attr_accessor :logger
|
|
15
15
|
|
|
16
|
-
sig { params(block: T.proc.void).void }
|
|
16
|
+
sig { params(block: T.proc.params(worker_name: T.untyped).void).void }
|
|
17
17
|
def before_fork(&block); end
|
|
18
18
|
|
|
19
|
-
sig { params(block: T.proc.void).void }
|
|
19
|
+
sig { params(block: T.proc.params(worker_name: String, slot_configs: T.untyped).void).void }
|
|
20
20
|
def after_fork(&block); end
|
|
21
|
+
|
|
22
|
+
sig { params(block: T.proc.params(slot: String, config: T.untyped).void).void }
|
|
23
|
+
def on_slot_start(&block); end
|
|
24
|
+
|
|
25
|
+
sig { params(block: T.proc.params(error: StandardError, slot: String, config: T.untyped).void).void }
|
|
26
|
+
def on_slot_error(&block); end
|
|
27
|
+
|
|
28
|
+
sig { params(block: T.proc.params(slot: String, config: T.untyped).void).void }
|
|
29
|
+
def on_slot_finish(&block); end
|
|
21
30
|
end
|
|
22
31
|
|
|
23
32
|
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.34
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Rodrigo Navarro
|
|
8
8
|
bindir: exe
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date: 2026-
|
|
10
|
+
date: 2026-02-11 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: pg
|