Skip to content

Commit 8b4fd9e

Browse files
committed
Parse the pgcontrol file on boot to ensure compatibility with our internal defaults.
1 parent bb46120 commit 8b4fd9e

File tree

5 files changed

+167
-21
lines changed

5 files changed

+167
-21
lines changed

internal/flypg/node.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,7 @@ func (n *Node) Init(ctx context.Context) error {
218218
return fmt.Errorf("failed to initialize fly config: %s", err)
219219
}
220220

221-
if err := n.PGConfig.initialize(store); err != nil {
221+
if err := n.PGConfig.initialize(ctx, store); err != nil {
222222
return fmt.Errorf("failed to initialize pg config: %s", err)
223223
}
224224

@@ -527,7 +527,7 @@ func (n *Node) handleRemoteRestore(ctx context.Context, store *state.Store) erro
527527
}
528528

529529
// Set restore configuration
530-
if err := n.PGConfig.initialize(store); err != nil {
530+
if err := n.PGConfig.initialize(ctx, store); err != nil {
531531
return fmt.Errorf("failed to initialize pg config: %s", err)
532532
}
533533

internal/flypg/pg.go

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ func (c *PGConfig) Print(w io.Writer) error {
112112
return e.Encode(cfg)
113113
}
114114

115-
func (c *PGConfig) SetDefaults(store *state.Store) error {
115+
func (c *PGConfig) SetDefaults(ctx context.Context, store *state.Store) error {
116116
// The default wal_segment_size in mb
117117
const walSegmentSize = 16
118118

@@ -184,6 +184,27 @@ func (c *PGConfig) SetDefaults(store *state.Store) error {
184184
return fmt.Errorf("failed to set recovery target config: %s", err)
185185
}
186186

187+
// Override any default settings that may conflict with pg_control.
188+
pgControlMap, err := pgControlSettings(ctx)
189+
if err != nil {
190+
return fmt.Errorf("failed to fetch pg_control settings: %s", err)
191+
}
192+
193+
if pgControlMap != nil {
194+
for k, v := range pgControlMap {
195+
// Skip any settings that are not already specified by the internal config
196+
if _, ok := c.internalConfig[k]; !ok {
197+
continue
198+
}
199+
200+
// Check for value discrepancies and log a warning if found.
201+
if c.internalConfig[k] != v {
202+
log.Printf("[WARN] Overriding internal config setting %s: %s -> %s", k, c.internalConfig[k], v)
203+
c.internalConfig[k] = v
204+
}
205+
}
206+
}
207+
187208
return nil
188209
}
189210

@@ -269,7 +290,7 @@ func (c *PGConfig) isInitialized() bool {
269290

270291
// initialize will ensure the required configuration files are stubbed and the parent
271292
// postgresql.conf file includes them.
272-
func (c *PGConfig) initialize(store *state.Store) error {
293+
func (c *PGConfig) initialize(ctx context.Context, store *state.Store) error {
273294
if err := c.setDefaultHBA(); err != nil {
274295
return fmt.Errorf("failed updating pg_hba.conf: %s", err)
275296
}
@@ -294,7 +315,7 @@ func (c *PGConfig) initialize(store *state.Store) error {
294315
}
295316
}
296317

297-
if err := c.SetDefaults(store); err != nil {
318+
if err := c.SetDefaults(context.TODO(), store); err != nil {
298319
return fmt.Errorf("failed to set pg defaults: %s", err)
299320
}
300321

internal/flypg/pg_control.go

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package flypg
2+
3+
import (
4+
"bufio"
5+
"context"
6+
"fmt"
7+
"strings"
8+
9+
"log"
10+
11+
"github.com/fly-apps/postgres-flex/internal/utils"
12+
)
13+
14+
const (
15+
pathToPGControl = "/data/postgresql/global/pg_control"
16+
)
17+
18+
func pgControlSettings(ctx context.Context) (map[string]string, error) {
19+
// Short-circuit if the pg_control file doesn't exist.
20+
if !utils.FileExists(pathToPGControl) {
21+
log.Println("[WARN] pg_control file does not exist. Skipping evaluation.")
22+
return nil, nil
23+
}
24+
25+
result, err := utils.RunCmd(ctx, "root", "pg_controldata")
26+
if err != nil {
27+
return nil, fmt.Errorf("failed to run pg_controldata: %s", err)
28+
}
29+
30+
return parsePGControlData(string(result))
31+
}
32+
33+
func parsePGControlData(pgControlData string) (map[string]string, error) {
34+
settings := make(map[string]string)
35+
36+
scanner := bufio.NewScanner(strings.NewReader(pgControlData))
37+
for scanner.Scan() {
38+
line := scanner.Text()
39+
40+
// Filter out lines that don't contain the word "setting".
41+
if !strings.Contains(line, "setting:") {
42+
continue
43+
}
44+
45+
parts := strings.SplitN(line, "setting:", 2)
46+
if len(parts) != 2 {
47+
continue
48+
}
49+
50+
key := strings.TrimSpace(parts[0])
51+
value := strings.TrimSpace(parts[1])
52+
53+
settings[key] = value
54+
}
55+
56+
// Check for any scanner errors.
57+
if err := scanner.Err(); err != nil {
58+
return nil, err
59+
}
60+
61+
return settings, nil
62+
}

internal/flypg/pg_control_test.go

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package flypg
2+
3+
import (
4+
"testing"
5+
)
6+
7+
func TestParseSettingsFromFile(t *testing.T) {
8+
// Sample input that includes some lines with "setting:" and some without.
9+
input := `pg_control version number: 1300
10+
Catalog version number: 202307071
11+
Database system identifier: 7420479024646529412
12+
Database cluster state: in archive recovery
13+
pg_control last modified: Tue 04 Feb 2025 10:04:52 PM UTC
14+
Latest checkpoint location: 2/40000060
15+
Latest checkpoint's REDO location: 2/40000028
16+
Latest checkpoint's REDO WAL file: 000000020000000200000040
17+
Latest checkpoint's TimeLineID: 2
18+
Latest checkpoint's PrevTimeLineID: 2
19+
Latest checkpoint's full_page_writes: on
20+
Latest checkpoint's NextXID: 0:34
21+
wal_level setting: replica
22+
wal_log_hints setting: on
23+
max_connections setting: 500
24+
max_worker_processes setting: 8
25+
Some other line without the keyword
26+
Blocks per segment of large relation: 131072
27+
WAL block size: 8192
28+
Bytes per WAL segment: 16777216
29+
Maximum length of identifiers: 64
30+
Maximum columns in an index: 32
31+
Maximum size of a TOAST chunk: 1996
32+
Size of a large-object chunk: 2048`
33+
34+
settings, err := parsePGControlData(input)
35+
if err != nil {
36+
t.Fatalf("parsePGControlData returned an error: %v", err)
37+
}
38+
39+
// Define the expected key/value pairs.
40+
expected := map[string]string{
41+
"wal_level": "replica",
42+
"wal_log_hints": "on",
43+
"max_connections": "500",
44+
"max_worker_processes": "8",
45+
}
46+
47+
if len(settings) != len(expected) {
48+
t.Errorf("expected %d settings, got %d", len(expected), len(settings))
49+
}
50+
51+
// Verify that the expected key/value pairs are present in the settings map.
52+
for key, want := range expected {
53+
got, ok := settings[key]
54+
if !ok {
55+
t.Errorf("expected key %q not found in settings", key)
56+
} else if got != want {
57+
t.Errorf("for key %q, expected value %q, got %q", key, want, got)
58+
}
59+
}
60+
}

internal/flypg/pg_test.go

Lines changed: 19 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package flypg
22

33
import (
4+
"context"
45
"fmt"
56
"os"
67
"strings"
@@ -25,6 +26,8 @@ func TestPGConfigInitialization(t *testing.T) {
2526
}
2627
defer cleanup()
2728

29+
ctx := context.TODO()
30+
2831
pgConf := &PGConfig{
2932
DataDir: pgTestDirectory,
3033
Port: 5433,
@@ -41,7 +44,7 @@ func TestPGConfigInitialization(t *testing.T) {
4144

4245
t.Run("initialize", func(t *testing.T) {
4346
store, _ := state.NewStore()
44-
if err := pgConf.initialize(store); err != nil {
47+
if err := pgConf.initialize(ctx, store); err != nil {
4548
t.Fatal(err)
4649
}
4750
})
@@ -98,7 +101,7 @@ func TestPGConfigInitialization(t *testing.T) {
98101
t.Setenv("TIMESCALEDB_ENABLED", "true")
99102
store, _ := state.NewStore()
100103

101-
if err := pgConf.initialize(store); err != nil {
104+
if err := pgConf.initialize(ctx, store); err != nil {
102105
t.Fatal(err)
103106
}
104107

@@ -129,7 +132,7 @@ func TestPGConfigInitialization(t *testing.T) {
129132
}
130133

131134
t.Run("defaults", func(t *testing.T) {
132-
if err := pgConf.initialize(store); err != nil {
135+
if err := pgConf.initialize(ctx, store); err != nil {
133136
t.Fatal(err)
134137
}
135138

@@ -160,7 +163,7 @@ func TestPGConfigInitialization(t *testing.T) {
160163
t.Fatal(err)
161164
}
162165

163-
if err := pgConf.initialize(store); err != nil {
166+
if err := pgConf.initialize(ctx, store); err != nil {
164167
t.Fatal(err)
165168
}
166169

@@ -181,7 +184,7 @@ func TestPGConfigInitialization(t *testing.T) {
181184
t.Fatal(err)
182185
}
183186

184-
if err := pgConf.initialize(store); err != nil {
187+
if err := pgConf.initialize(ctx, store); err != nil {
185188
t.Fatal(err)
186189
}
187190

@@ -202,7 +205,7 @@ func TestPGConfigInitialization(t *testing.T) {
202205
t.Fatal(err)
203206
}
204207

205-
if err := pgConf.initialize(store); err != nil {
208+
if err := pgConf.initialize(ctx, store); err != nil {
206209
t.Fatal(err)
207210
}
208211

@@ -223,7 +226,7 @@ func TestPGConfigInitialization(t *testing.T) {
223226
t.Fatal(err)
224227
}
225228

226-
if err := pgConf.initialize(store); err != nil {
229+
if err := pgConf.initialize(ctx, store); err != nil {
227230
t.Fatal(err)
228231
}
229232

@@ -242,7 +245,7 @@ func TestPGConfigInitialization(t *testing.T) {
242245
t.Setenv("S3_ARCHIVE_CONFIG", "https://my-key:[email protected]/my-bucket/my-directory")
243246
store, _ := state.NewStore()
244247

245-
if err := pgConf.initialize(store); err != nil {
248+
if err := pgConf.initialize(ctx, store); err != nil {
246249
t.Fatal(err)
247250
}
248251

@@ -257,7 +260,7 @@ func TestPGConfigInitialization(t *testing.T) {
257260

258261
t.Setenv("S3_ARCHIVE_CONFIG", "")
259262

260-
if err := pgConf.initialize(store); err != nil {
263+
if err := pgConf.initialize(ctx, store); err != nil {
261264
t.Fatal(err)
262265
}
263266

@@ -275,7 +278,7 @@ func TestPGConfigInitialization(t *testing.T) {
275278
t.Setenv("S3_ARCHIVE_REMOTE_RESTORE_CONFIG", "https://my-key:[email protected]/my-bucket/my-directory?targetTime=2024-06-30T11:15:00-06:00")
276279
store, _ := state.NewStore()
277280

278-
if err := pgConf.initialize(store); err != nil {
281+
if err := pgConf.initialize(ctx, store); err != nil {
279282
t.Fatal(err)
280283
}
281284

@@ -293,7 +296,7 @@ func TestPGConfigInitialization(t *testing.T) {
293296
t.Setenv("S3_ARCHIVE_REMOTE_RESTORE_CONFIG", "https://my-key:[email protected]/my-bucket/my-directory?targetName=20240626T172443")
294297
store, _ := state.NewStore()
295298

296-
if err := pgConf.initialize(store); err != nil {
299+
if err := pgConf.initialize(ctx, store); err != nil {
297300
t.Fatal(err)
298301
}
299302

@@ -311,7 +314,7 @@ func TestPGConfigInitialization(t *testing.T) {
311314
t.Setenv("S3_ARCHIVE_REMOTE_RESTORE_CONFIG", "https://my-key:[email protected]/my-bucket/my-directory?target=immediate")
312315
store, _ := state.NewStore()
313316

314-
if err := pgConf.initialize(store); err != nil {
317+
if err := pgConf.initialize(ctx, store); err != nil {
315318
t.Fatal(err)
316319
}
317320

@@ -329,7 +332,7 @@ func TestPGConfigInitialization(t *testing.T) {
329332
t.Setenv("S3_ARCHIVE_REMOTE_RESTORE_CONFIG", "https://my-key:[email protected]/my-bucket/my-directory?targetTime=2024-06-30T11:15:00Z&targetInclusive=false")
330333
store, _ := state.NewStore()
331334

332-
if err := pgConf.initialize(store); err != nil {
335+
if err := pgConf.initialize(ctx, store); err != nil {
333336
t.Fatal(err)
334337
}
335338

@@ -351,7 +354,7 @@ func TestPGConfigInitialization(t *testing.T) {
351354
t.Setenv("S3_ARCHIVE_REMOTE_RESTORE_CONFIG", "https://my-key:[email protected]/my-bucket/my-directory?targetTime=2024-06-30T11:15:00-06:00&targetTimeline=2")
352355
store, _ := state.NewStore()
353356

354-
if err := pgConf.initialize(store); err != nil {
357+
if err := pgConf.initialize(ctx, store); err != nil {
355358
t.Fatal(err)
356359
}
357360

@@ -391,7 +394,7 @@ func TestPGUserConfigOverride(t *testing.T) {
391394
}
392395

393396
store, _ := state.NewStore()
394-
if err := pgConf.initialize(store); err != nil {
397+
if err := pgConf.initialize(context.TODO(), store); err != nil {
395398
t.Error(err)
396399
}
397400

@@ -542,7 +545,7 @@ func TestValidateCompatibility(t *testing.T) {
542545
}
543546

544547
store, _ := state.NewStore()
545-
if err := pgConf.initialize(store); err != nil {
548+
if err := pgConf.initialize(context.TODO(), store); err != nil {
546549
t.Fatal(err)
547550
}
548551
t.Run("SharedPreloadLibraries", func(t *testing.T) {

0 commit comments

Comments
 (0)