pg-replication-protocol 0.0.3 → 0.0.5
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/Gemfile.lock +2 -1
- data/README.md +15 -9
- data/lib/pg/replication/version.rb +1 -1
- data/lib/pg/replication.rb +50 -28
- metadata +3 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 07bd494baf2df079a175855749b79dc41460c5903a58e3cef89f7ac1304e6e9b
|
4
|
+
data.tar.gz: 28ad5dced1a6a73c84a62ce6eae434dbde34883378555c8bac7674d477928627
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7bd50c203eb711090bdbb0b16d745e46472d936191ac3c3653138bc9db42cecd6b1ed51a625eeae5194be3ca17c66e6f8a05dd4a860979a791d4fa54eee74139
|
7
|
+
data.tar.gz: 3ddaf038c9cdfab3c05c4ac2e6eac8c1799c7a0b1d334a4657b19a9251feeac835784bbdec5a94fba43260f50b932441cd49786f1f573c7a607687a742ef8eb4
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
# PG::Replication
|
2
2
|
|
3
|
+
Adds support to `pg` for listening to replication slots
|
4
|
+
|
3
5
|
## Usage
|
4
6
|
|
5
7
|
Add to your Gemfile:
|
@@ -14,7 +16,7 @@ gem "pg-replication-protocol", require: "pg/replication"
|
|
14
16
|
require "pg"
|
15
17
|
require "pg/replication"
|
16
18
|
|
17
|
-
#
|
19
|
+
# It is important to create a connection with the `replication: "database"` option
|
18
20
|
connection = PG.connect(..., replication: "database")
|
19
21
|
|
20
22
|
# Create a publication and a slot (in a real use case the slot will not be temporary)
|
@@ -25,25 +27,26 @@ connection.query('CREATE_REPLICATION_SLOT some_slot TEMPORARY LOGICAL "pgoutput"
|
|
25
27
|
tables = {}
|
26
28
|
|
27
29
|
# Start a pgoutput plugin replication slot message stream
|
28
|
-
|
30
|
+
# The `messages: true` option is required to be able to decode `PG::Replication::PGOutput::Message`
|
31
|
+
connection.start_pgoutput_replication_slot(slot, publications, messages: true).each do |msg|
|
29
32
|
case msg
|
30
|
-
in PG::Replication::PGOutput::Relation(oid:, name:, columns:)
|
31
|
-
#
|
33
|
+
in PG::Replication::Protocol::XLogData(data: PG::Replication::PGOutput::Relation(oid:, name:, columns:))
|
34
|
+
# This message is received on the first row of each table, or when there are schema changes
|
32
35
|
tables[oid] = { name:, columns: }
|
33
36
|
|
34
|
-
in PG::Replication::PGOutput::Begin
|
37
|
+
in PG::Replication::Protocol::XLogData(data: PG::Replication::PGOutput::Begin)
|
35
38
|
puts "Transaction start"
|
36
39
|
|
37
|
-
in PG::Replication::PGOutput::Commit
|
40
|
+
in PG::Replication::Protocol::XLogData(data: PG::Replication::PGOutput::Commit)
|
38
41
|
puts "Transaction end"
|
39
42
|
|
40
|
-
in PG::Replication::PGOutput::Insert(oid:, new:)
|
43
|
+
in PG::Replication::Protocol::XLogData(data: PG::Replication::PGOutput::Insert(oid:, new:))
|
41
44
|
puts "Insert #{tables[oid][:name]}"
|
42
45
|
new.zip(tables[oid][:columns]).each do |tuple, col|
|
43
46
|
puts "#{col.name}: #{tuple.data || "NULL"}"
|
44
47
|
end
|
45
48
|
|
46
|
-
in PG::Replication::PGOutput::Update(oid:, new:, old:)
|
49
|
+
in PG::Replication::Protocol::XLogData(data: PG::Replication::PGOutput::Update(oid:, new:, old:))
|
47
50
|
puts "Update #{tables[oid][:name]}"
|
48
51
|
if !old.empty? && new != old
|
49
52
|
new.zip(old, tables[oid][:columns]).each do |new, old, col|
|
@@ -53,9 +56,12 @@ connection.start_pgoutput_replication_slot(slot, publications).each do |msg|
|
|
53
56
|
end
|
54
57
|
end
|
55
58
|
|
56
|
-
in PG::Replication::PGOutput::Delete(oid:)
|
59
|
+
in PG::Replication::Protocol::XLogData(data: PG::Replication::PGOutput::Delete(oid:))
|
57
60
|
puts "Delete #{tables[oid][:name]}"
|
58
61
|
|
62
|
+
in PG::Replication::Protocol::XLogData(data: PG::Replication::PGOutput::Message(prefix:, content:))
|
63
|
+
puts "Message #{prefix}: #{content}"
|
64
|
+
|
59
65
|
else
|
60
66
|
nil
|
61
67
|
end
|
data/lib/pg/replication.rb
CHANGED
@@ -8,10 +8,9 @@ require_relative "replication/protocol"
|
|
8
8
|
|
9
9
|
module PG
|
10
10
|
module Replication
|
11
|
-
def start_replication_slot(slot, logical: true, location: "0/0", **params)
|
12
|
-
keep_alive_secs =
|
13
|
-
|
14
|
-
SQL
|
11
|
+
def start_replication_slot(slot, logical: true, auto_keep_alive: true, location: "0/0", **params)
|
12
|
+
keep_alive_secs = wal_receiver_status_interval
|
13
|
+
@last_confirmed_lsn = confirmed_slot_lsn(slot) || 0
|
15
14
|
|
16
15
|
start_query = "START_REPLICATION SLOT #{slot} #{logical ? "LOGICAL" : "PHYSICAL"} #{location}"
|
17
16
|
unless params.empty?
|
@@ -23,14 +22,13 @@ module PG
|
|
23
22
|
end
|
24
23
|
query(start_query)
|
25
24
|
|
26
|
-
last_processed_lsn = 0
|
27
25
|
last_keep_alive = Time.now
|
28
26
|
|
29
27
|
Enumerator
|
30
28
|
.new do |y|
|
31
29
|
loop do
|
32
|
-
if Time.now - last_keep_alive > keep_alive_secs
|
33
|
-
standby_status_update(write_lsn:
|
30
|
+
if auto_keep_alive && Time.now - last_keep_alive > keep_alive_secs
|
31
|
+
standby_status_update(write_lsn: @last_confirmed_lsn)
|
34
32
|
last_keep_alive = Time.now
|
35
33
|
end
|
36
34
|
|
@@ -47,37 +45,40 @@ module PG
|
|
47
45
|
next
|
48
46
|
|
49
47
|
in data
|
50
|
-
|
51
|
-
|
48
|
+
case (msg = Protocol.read_message(Buffer.new(StringIO.new(data))))
|
49
|
+
in Protocol::XLogData(lsn:, data:) if auto_keep_alive
|
50
|
+
y << msg
|
51
|
+
standby_status_update(write_lsn: lsn)
|
52
|
+
@last_confirmed_lsn = lsn
|
53
|
+
last_keep_alive = Time.now
|
54
|
+
|
55
|
+
in Protocol::PrimaryKeepalive(server_time:, asap: true) if auto_keep_alive
|
56
|
+
standby_status_update(write_lsn: @last_confirmed_lsn)
|
57
|
+
last_keep_alive = Time.now
|
58
|
+
y << msg
|
59
|
+
|
60
|
+
else
|
61
|
+
y << msg
|
62
|
+
end
|
52
63
|
end
|
53
64
|
end
|
54
65
|
end
|
55
66
|
.lazy
|
56
|
-
.filter_map do |msg|
|
57
|
-
case msg
|
58
|
-
in Protocol::XLogData(lsn:, data:)
|
59
|
-
last_processed_lsn = lsn
|
60
|
-
standby_status_update(write_lsn: last_processed_lsn)
|
61
|
-
last_keep_alive = Time.now
|
62
|
-
data
|
63
|
-
|
64
|
-
in Protocol::PrimaryKeepalive(server_time:, asap: true)
|
65
|
-
standby_status_update(write_lsn: last_processed_lsn)
|
66
|
-
last_keep_alive = Time.now
|
67
|
-
next
|
68
|
-
|
69
|
-
else
|
70
|
-
next
|
71
|
-
end
|
72
|
-
end
|
73
67
|
end
|
74
68
|
|
75
69
|
def start_pgoutput_replication_slot(slot, publication_names, **kwargs)
|
76
70
|
publication_names = publication_names.join(",")
|
77
71
|
|
78
72
|
start_replication_slot(slot, **kwargs.merge(proto_version: "1", publication_names:))
|
79
|
-
.map
|
80
|
-
|
73
|
+
.map do |msg|
|
74
|
+
case msg
|
75
|
+
in Protocol::XLogData(data:, lsn:)
|
76
|
+
data = data.force_encoding(internal_encoding)
|
77
|
+
msg.with(data: PGOutput.read_message(Buffer.from_string(data)))
|
78
|
+
else
|
79
|
+
msg
|
80
|
+
end
|
81
|
+
end
|
81
82
|
end
|
82
83
|
|
83
84
|
def standby_status_update(
|
@@ -98,6 +99,27 @@ module PG
|
|
98
99
|
|
99
100
|
put_copy_data(msg)
|
100
101
|
flush
|
102
|
+
@last_confirmed_lsn = [@last_confirmed_lsn, write_lsn].compact.max
|
103
|
+
end
|
104
|
+
|
105
|
+
def last_confirmed_lsn
|
106
|
+
@last_confirmed_lsn
|
107
|
+
end
|
108
|
+
|
109
|
+
def wal_receiver_status_interval
|
110
|
+
query(<<~SQL).getvalue(0, 0)&.to_i || 10
|
111
|
+
SELECT setting FROM pg_catalog.pg_settings WHERE name = 'wal_receiver_status_interval'
|
112
|
+
SQL
|
113
|
+
end
|
114
|
+
|
115
|
+
def confirmed_slot_lsn(slot)
|
116
|
+
lsn = query(<<~SQL).getvalue(0, 0)
|
117
|
+
SELECT confirmed_flush_lsn FROM pg_replication_slots WHERE slot_name = '#{slot}'
|
118
|
+
SQL
|
119
|
+
high, low = lsn.split("/")
|
120
|
+
(high.to_i(16) << 32) + low.to_i(16)
|
121
|
+
rescue StandardError
|
122
|
+
nil
|
101
123
|
end
|
102
124
|
end
|
103
125
|
|
metadata
CHANGED
@@ -1,14 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: pg-replication-protocol
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Rodrigo Navarro
|
8
|
-
autorequire:
|
9
8
|
bindir: bin
|
10
9
|
cert_chain: []
|
11
|
-
date:
|
10
|
+
date: 2025-06-04 00:00:00.000000000 Z
|
12
11
|
dependencies:
|
13
12
|
- !ruby/object:Gem::Dependency
|
14
13
|
name: pg
|
@@ -24,7 +23,6 @@ dependencies:
|
|
24
23
|
- - "~>"
|
25
24
|
- !ruby/object:Gem::Version
|
26
25
|
version: '1.0'
|
27
|
-
description:
|
28
26
|
email:
|
29
27
|
- rnavarro@rnavarro.com.br
|
30
28
|
executables: []
|
@@ -43,13 +41,11 @@ files:
|
|
43
41
|
- lib/pg/replication/protocol.rb
|
44
42
|
- lib/pg/replication/version.rb
|
45
43
|
- pg-replication-protocol.gemspec
|
46
|
-
homepage:
|
47
44
|
licenses:
|
48
45
|
- MIT
|
49
46
|
metadata:
|
50
47
|
homepage_uri: https://github.com/reu/pg-replication-protocol-rb
|
51
48
|
source_code_uri: https://github.com/reu/pg-replication-protocol-rb
|
52
|
-
post_install_message:
|
53
49
|
rdoc_options: []
|
54
50
|
require_paths:
|
55
51
|
- lib
|
@@ -64,8 +60,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
64
60
|
- !ruby/object:Gem::Version
|
65
61
|
version: '0'
|
66
62
|
requirements: []
|
67
|
-
rubygems_version: 3.
|
68
|
-
signing_key:
|
63
|
+
rubygems_version: 3.6.2
|
69
64
|
specification_version: 4
|
70
65
|
summary: Postgres replication protocol
|
71
66
|
test_files: []
|