pg-replication-protocol 0.0.2 → 0.0.4
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/pg_output.rb +17 -8
- data/lib/pg/replication/version.rb +1 -1
- data/lib/pg/replication.rb +40 -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: 7eb14ee3cd0484e8160ed1bc27a841178bb5bb19a35304d46992c8d7accad5be
|
4
|
+
data.tar.gz: 51e43b8bcba5ee2db08b08962418ae0ff6ab6657076ca586984c279fe6603f6e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0e86a9fcaa6a3497d6b211004551219fd9080adbca09977de836c53766780088fcb6c42966e461b8517026ef310ab67134c52d20fef396adf1807c06bf004ffe
|
7
|
+
data.tar.gz: 7e21681ae16444dc1a43450074a574ea81edfd554ce6362c13befad60f651c637c887416fd58befdf1bb8e5f604efd946a6e253486c584b562bb790a1e644864
|
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
|
@@ -11,7 +11,7 @@ module PG
|
|
11
11
|
Type = Data.define(:oid, :namespace, :name)
|
12
12
|
Insert = Data.define(:oid, :new)
|
13
13
|
Update = Data.define(:oid, :key, :old, :new)
|
14
|
-
Delete = Data.define(:oid, :old)
|
14
|
+
Delete = Data.define(:oid, :key, :old)
|
15
15
|
Truncate = Data.define(:oid)
|
16
16
|
Tuple = Data.define(:type, :data)
|
17
17
|
Column = Data.define(:flags, :name, :oid, :modifier) do
|
@@ -115,14 +115,23 @@ module PG
|
|
115
115
|
)
|
116
116
|
|
117
117
|
in "D"
|
118
|
+
oid = buffer.read_int32
|
119
|
+
key = []
|
120
|
+
old = []
|
121
|
+
|
122
|
+
until buffer.eof?
|
123
|
+
case buffer.read_char
|
124
|
+
when "K"
|
125
|
+
key = PGOutput.read_tuples(buffer)
|
126
|
+
when "O"
|
127
|
+
old = PGOutput.read_tuples(buffer)
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
118
131
|
PGOutput::Delete.new(
|
119
|
-
oid
|
120
|
-
|
121
|
-
|
122
|
-
PGOutput.read_tuples(buffer)
|
123
|
-
else
|
124
|
-
[]
|
125
|
-
end,
|
132
|
+
oid:,
|
133
|
+
key:,
|
134
|
+
old:,
|
126
135
|
)
|
127
136
|
|
128
137
|
in "T"
|
data/lib/pg/replication.rb
CHANGED
@@ -8,10 +8,8 @@ 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
|
-
SELECT setting FROM pg_catalog.pg_settings WHERE name = 'wal_receiver_status_interval'
|
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
|
15
13
|
|
16
14
|
start_query = "START_REPLICATION SLOT #{slot} #{logical ? "LOGICAL" : "PHYSICAL"} #{location}"
|
17
15
|
unless params.empty?
|
@@ -23,14 +21,14 @@ module PG
|
|
23
21
|
end
|
24
22
|
query(start_query)
|
25
23
|
|
26
|
-
|
24
|
+
@last_confirmed_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
|
+
standby_status_update(write_lsn: @last_confirmed_lsn)
|
51
|
+
last_keep_alive = Time.now
|
52
|
+
y << msg
|
53
|
+
@last_confirmed_lsn = lsn
|
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,17 @@ 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
|
101
113
|
end
|
102
114
|
end
|
103
115
|
|
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.4
|
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: []
|