fix: handle pg_replication_slots on pg<13 (#1098)

* fix: handle pg_replication_slots on pg<13

Signed-off-by: Michael Todorovic <michael.todorovic@outlook.com>

* fix: tests

Signed-off-by: Michael Todorovic <michael.todorovic@outlook.com>

---------

Signed-off-by: Michael Todorovic <michael.todorovic@outlook.com>
This commit is contained in:
Michael Todorovic 2025-02-15 15:00:48 +01:00 committed by GitHub
parent 072864d179
commit 9e42fc0145
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 44 additions and 11 deletions

View File

@ -18,6 +18,7 @@ import (
"database/sql" "database/sql"
"log/slog" "log/slog"
"github.com/blang/semver/v4"
"github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus"
) )
@ -81,8 +82,18 @@ var (
"availability of WAL files claimed by this slot", "availability of WAL files claimed by this slot",
[]string{"slot_name", "slot_type", "wal_status"}, nil, []string{"slot_name", "slot_type", "wal_status"}, nil,
) )
pgReplicationSlotQuery = `SELECT pgReplicationSlotQuery = `SELECT
slot_name,
slot_type,
CASE WHEN pg_is_in_recovery() THEN
pg_last_wal_receive_lsn() - '0/0'
ELSE
pg_current_wal_lsn() - '0/0'
END AS current_wal_lsn,
COALESCE(confirmed_flush_lsn, '0/0') - '0/0' AS confirmed_flush_lsn,
active
FROM pg_replication_slots;`
pgReplicationSlotNewQuery = `SELECT
slot_name, slot_name,
slot_type, slot_type,
CASE WHEN pg_is_in_recovery() THEN CASE WHEN pg_is_in_recovery() THEN
@ -98,9 +109,15 @@ var (
) )
func (PGReplicationSlotCollector) Update(ctx context.Context, instance *instance, ch chan<- prometheus.Metric) error { func (PGReplicationSlotCollector) Update(ctx context.Context, instance *instance, ch chan<- prometheus.Metric) error {
query := pgReplicationSlotQuery
abovePG13 := instance.version.GTE(semver.MustParse("13.0.0"))
if abovePG13 {
query = pgReplicationSlotNewQuery
}
db := instance.getDB() db := instance.getDB()
rows, err := db.QueryContext(ctx, rows, err := db.QueryContext(ctx,
pgReplicationSlotQuery) query)
if err != nil { if err != nil {
return err return err
} }
@ -114,7 +131,22 @@ func (PGReplicationSlotCollector) Update(ctx context.Context, instance *instance
var isActive sql.NullBool var isActive sql.NullBool
var safeWalSize sql.NullInt64 var safeWalSize sql.NullInt64
var walStatus sql.NullString var walStatus sql.NullString
if err := rows.Scan(&slotName, &slotType, &walLSN, &flushLSN, &isActive, &safeWalSize, &walStatus); err != nil {
r := []any{
&slotName,
&slotType,
&walLSN,
&flushLSN,
&isActive,
}
if abovePG13 {
r = append(r, &safeWalSize)
r = append(r, &walStatus)
}
err := rows.Scan(r...)
if err != nil {
return err return err
} }

View File

@ -17,6 +17,7 @@ import (
"testing" "testing"
"github.com/DATA-DOG/go-sqlmock" "github.com/DATA-DOG/go-sqlmock"
"github.com/blang/semver/v4"
"github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus"
dto "github.com/prometheus/client_model/go" dto "github.com/prometheus/client_model/go"
"github.com/smartystreets/goconvey/convey" "github.com/smartystreets/goconvey/convey"
@ -29,12 +30,12 @@ func TestPgReplicationSlotCollectorActive(t *testing.T) {
} }
defer db.Close() defer db.Close()
inst := &instance{db: db} inst := &instance{db: db, version: semver.MustParse("13.3.7")}
columns := []string{"slot_name", "slot_type", "current_wal_lsn", "confirmed_flush_lsn", "active", "safe_wal_size", "wal_status"} columns := []string{"slot_name", "slot_type", "current_wal_lsn", "confirmed_flush_lsn", "active", "safe_wal_size", "wal_status"}
rows := sqlmock.NewRows(columns). rows := sqlmock.NewRows(columns).
AddRow("test_slot", "physical", 5, 3, true, 323906992, "reserved") AddRow("test_slot", "physical", 5, 3, true, 323906992, "reserved")
mock.ExpectQuery(sanitizeQuery(pgReplicationSlotQuery)).WillReturnRows(rows) mock.ExpectQuery(sanitizeQuery(pgReplicationSlotNewQuery)).WillReturnRows(rows)
ch := make(chan prometheus.Metric) ch := make(chan prometheus.Metric)
go func() { go func() {
@ -72,12 +73,12 @@ func TestPgReplicationSlotCollectorInActive(t *testing.T) {
} }
defer db.Close() defer db.Close()
inst := &instance{db: db} inst := &instance{db: db, version: semver.MustParse("13.3.7")}
columns := []string{"slot_name", "slot_type", "current_wal_lsn", "confirmed_flush_lsn", "active", "safe_wal_size", "wal_status"} columns := []string{"slot_name", "slot_type", "current_wal_lsn", "confirmed_flush_lsn", "active", "safe_wal_size", "wal_status"}
rows := sqlmock.NewRows(columns). rows := sqlmock.NewRows(columns).
AddRow("test_slot", "physical", 6, 12, false, -4000, "extended") AddRow("test_slot", "physical", 6, 12, false, -4000, "extended")
mock.ExpectQuery(sanitizeQuery(pgReplicationSlotQuery)).WillReturnRows(rows) mock.ExpectQuery(sanitizeQuery(pgReplicationSlotNewQuery)).WillReturnRows(rows)
ch := make(chan prometheus.Metric) ch := make(chan prometheus.Metric)
go func() { go func() {
@ -115,12 +116,12 @@ func TestPgReplicationSlotCollectorActiveNil(t *testing.T) {
} }
defer db.Close() defer db.Close()
inst := &instance{db: db} inst := &instance{db: db, version: semver.MustParse("13.3.7")}
columns := []string{"slot_name", "slot_type", "current_wal_lsn", "confirmed_flush_lsn", "active", "safe_wal_size", "wal_status"} columns := []string{"slot_name", "slot_type", "current_wal_lsn", "confirmed_flush_lsn", "active", "safe_wal_size", "wal_status"}
rows := sqlmock.NewRows(columns). rows := sqlmock.NewRows(columns).
AddRow("test_slot", "physical", 6, 12, nil, nil, "lost") AddRow("test_slot", "physical", 6, 12, nil, nil, "lost")
mock.ExpectQuery(sanitizeQuery(pgReplicationSlotQuery)).WillReturnRows(rows) mock.ExpectQuery(sanitizeQuery(pgReplicationSlotNewQuery)).WillReturnRows(rows)
ch := make(chan prometheus.Metric) ch := make(chan prometheus.Metric)
go func() { go func() {
@ -156,12 +157,12 @@ func TestPgReplicationSlotCollectorTestNilValues(t *testing.T) {
} }
defer db.Close() defer db.Close()
inst := &instance{db: db} inst := &instance{db: db, version: semver.MustParse("13.3.7")}
columns := []string{"slot_name", "slot_type", "current_wal_lsn", "confirmed_flush_lsn", "active", "safe_wal_size", "wal_status"} columns := []string{"slot_name", "slot_type", "current_wal_lsn", "confirmed_flush_lsn", "active", "safe_wal_size", "wal_status"}
rows := sqlmock.NewRows(columns). rows := sqlmock.NewRows(columns).
AddRow(nil, nil, nil, nil, true, nil, nil) AddRow(nil, nil, nil, nil, true, nil, nil)
mock.ExpectQuery(sanitizeQuery(pgReplicationSlotQuery)).WillReturnRows(rows) mock.ExpectQuery(sanitizeQuery(pgReplicationSlotNewQuery)).WillReturnRows(rows)
ch := make(chan prometheus.Metric) ch := make(chan prometheus.Metric)
go func() { go func() {