Skip to content

Commit 6ee880c

Browse files
committed
feat: add objbacker prototype for tiered object storage
Add experimental objbacker integration package that enables cost-effective tiered storage for DBLab snapshots using object storage (S3/GCS/Azure). Key features: - Configuration model for object storage backends with performance tuning - VDEV manager for creating and managing objbacker-backed ZFS VDEVs - Pool helper for creating tiered (local+object) and object-only pools - Snapshot archival service with automatic tiering based on age policy - Cost estimation for storage savings vs traditional block storage - Example configuration demonstrating all available options This prototype implements the architecture discussed for integrating ZettaLane's objbacker technology which provides native ZFS VDEV support for object storage without FUSE overhead.
1 parent 583e4dd commit 6ee880c

6 files changed

Lines changed: 1877 additions & 0 deletions

File tree

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
# Copy this configuration to: ~/.dblab/engine/configs/server.yml
2+
# Configuration reference guide: https://postgres.ai/docs/reference-guides/database-lab-engine-configuration-reference
3+
#
4+
# EXPERIMENTAL: This configuration demonstrates objbacker integration for tiered storage.
5+
# Objbacker is a native ZFS VDEV that talks directly to S3/GCS/Azure blob storage,
6+
# enabling cost-effective storage for database snapshots.
7+
# See: https://www.zettalane.com/blog/openzfs-summit-2025-mayanas-objbacker.html
8+
#
9+
# REQUIREMENTS:
10+
# - objbacker kernel module loaded (/dev/zfs_objbacker must exist)
11+
# - objbacker daemon installed (zfs_objbacker_daemon)
12+
# - Fast local NVMe for cache (recommended: 10GB+ for metadata caching)
13+
# - Cloud storage credentials configured
14+
15+
server:
16+
verificationToken: "secret_token"
17+
port: 2345
18+
disableConfigModification: false
19+
20+
embeddedUI:
21+
enabled: true
22+
dockerImage: "postgresai/ce-ui:latest"
23+
host: "127.0.0.1"
24+
port: 2346
25+
26+
global:
27+
engine: postgres
28+
debug: true
29+
database:
30+
username: postgres
31+
dbname: postgres
32+
33+
poolManager:
34+
mountDir: /var/lib/dblab
35+
dataSubDir: data
36+
clonesMountSubDir: clones
37+
socketSubDir: sockets
38+
observerSubDir: observer
39+
preSnapshotSuffix: "_pre"
40+
selectedPool: ""
41+
42+
# EXPERIMENTAL: objbacker configuration for tiered/archival storage
43+
objbacker:
44+
enabled: true # Enable objbacker integration
45+
46+
# Object storage backend: s3, gcs, or azure
47+
storageType: s3
48+
49+
# Object storage configuration
50+
endpoint: "" # Leave empty for AWS S3, or set for MinIO/other S3-compatible
51+
bucket: "my-dblab-snapshots"
52+
prefix: "dblab/production" # Optional prefix within the bucket
53+
region: "us-east-1" # Required for S3
54+
55+
# Authentication (choose one method)
56+
credentials:
57+
accessKeyId: "" # Set via environment variable: DBLAB_OBJBACKER_ACCESS_KEY_ID
58+
secretAccessKey: "" # Set via environment variable: DBLAB_OBJBACKER_SECRET_ACCESS_KEY
59+
credentialsFile: "" # Alternative: path to credentials file (for GCS service account)
60+
useIamRole: true # Use IAM role on cloud VMs (recommended for AWS/GCP)
61+
62+
# Performance tuning
63+
performance:
64+
blockSize: 1048576 # 1MB - larger blocks for streaming efficiency
65+
localCacheSize: 10737418240 # 10GB - cache for metadata and hot data
66+
localCachePath: "/var/cache/objbacker" # Should be on fast NVMe storage
67+
readAheadSize: 4194304 # 4MB prefetch for sequential reads
68+
maxConcurrentOps: 32 # Concurrent object storage operations
69+
connectionTimeout: 30s
70+
requestTimeout: 5m
71+
72+
# Tiering configuration for automatic hot/cold data management
73+
tiering:
74+
enabled: true # Enable automatic tiering
75+
hotDataThreshold: 24h # Data accessed within 24h stays hot
76+
metadataLocal: true # Keep ZFS metadata on local storage
77+
promoteOnRead: true # Promote cold data to hot tier when accessed
78+
79+
# Device paths (usually don't need to change)
80+
devicePath: "/dev/zfs_objbacker"
81+
daemonSocketPath: "/var/run/zfs_objbacker.sock"
82+
83+
# Automatic snapshot archival policy
84+
snapshotArchival:
85+
enabled: true
86+
87+
# Archive snapshots older than this to object storage
88+
archiveAfter: 168h # 7 days
89+
90+
# Always keep at least this many recent snapshots locally
91+
keepLocalCount: 3
92+
93+
# Delete archived snapshots after this time (0 = keep forever)
94+
deleteArchivedAfter: 2160h # 90 days
95+
96+
# Branches to exclude from archival (e.g., production branch stays local)
97+
excludeBranches: []
98+
99+
# How often to run the archival check
100+
scheduleInterval: 6h
101+
102+
databaseContainer: &db_container
103+
dockerImage: "postgresai/extended-postgres:18-0.6.2"
104+
containerConfig:
105+
"shm-size": 1gb
106+
107+
databaseConfigs: &db_configs
108+
configs:
109+
shared_buffers: 1GB
110+
shared_preload_libraries: "pg_stat_statements, pg_stat_kcache, auto_explain, logerrors"
111+
maintenance_work_mem: "500MB"
112+
work_mem: "100MB"
113+
114+
provision:
115+
<<: *db_container
116+
portPool:
117+
from: 6000
118+
to: 6099
119+
useSudo: false
120+
keepUserPasswords: false
121+
cloneAccessAddresses: "127.0.0.1"
122+
123+
retrieval:
124+
jobs:
125+
- physicalRestore
126+
- physicalSnapshot
127+
spec:
128+
physicalRestore:
129+
options:
130+
<<: *db_container
131+
tool: walg
132+
sync:
133+
enabled: true
134+
healthCheck:
135+
interval: 5
136+
maxRetries: 200
137+
configs:
138+
shared_buffers: 2GB
139+
140+
envs:
141+
WALG_GS_PREFIX: "gs://{BUCKET}/{SCOPE}"
142+
GOOGLE_APPLICATION_CREDENTIALS: "/tmp/sa.json"
143+
144+
walg:
145+
backupName: LATEST
146+
147+
physicalSnapshot:
148+
options:
149+
skipStartSnapshot: false
150+
<<: *db_configs
151+
promotion:
152+
<<: *db_container
153+
enabled: true
154+
healthCheck:
155+
interval: 5
156+
maxRetries: 200
157+
queryPreprocessing:
158+
queryPath: ""
159+
maxParallelWorkers: 2
160+
inline: ""
161+
configs:
162+
shared_buffers: 2GB
163+
scheduler:
164+
snapshot:
165+
timetable: "0 */6 * * *" # Take snapshots every 6 hours
166+
retention:
167+
timetable: "0 * * * *"
168+
limit: 10 # Keep more snapshots locally since archival handles old ones
Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
/*
2+
2025 © Postgres.ai
3+
*/
4+
5+
// Package objbacker provides an interface to work with objbacker-backed ZFS pools.
6+
// Objbacker is a native ZFS VDEV that talks directly to S3/GCS/Azure blob storage,
7+
// enabling cost-effective tiered storage for database clones.
8+
package objbacker
9+
10+
import (
11+
"fmt"
12+
"time"
13+
)
14+
15+
// StorageType defines the type of object storage backend.
16+
type StorageType string
17+
18+
const (
19+
// StorageTypeS3 represents Amazon S3 or S3-compatible storage.
20+
StorageTypeS3 StorageType = "s3"
21+
// StorageTypeGCS represents Google Cloud Storage.
22+
StorageTypeGCS StorageType = "gcs"
23+
// StorageTypeAzure represents Azure Blob Storage.
24+
StorageTypeAzure StorageType = "azure"
25+
)
26+
27+
// Config defines configuration for objbacker-backed storage.
28+
type Config struct {
29+
// Enabled determines if objbacker integration is active.
30+
Enabled bool `yaml:"enabled"`
31+
32+
// StorageType specifies the object storage backend (s3, gcs, azure).
33+
StorageType StorageType `yaml:"storageType"`
34+
35+
// Endpoint is the object storage endpoint URL.
36+
// For S3: https://s3.amazonaws.com or custom endpoint for MinIO/etc.
37+
// For GCS: https://storage.googleapis.com
38+
// For Azure: https://<account>.blob.core.windows.net
39+
Endpoint string `yaml:"endpoint"`
40+
41+
// Bucket is the name of the bucket/container for storing data.
42+
Bucket string `yaml:"bucket"`
43+
44+
// Prefix is the optional path prefix within the bucket.
45+
Prefix string `yaml:"prefix"`
46+
47+
// Region is the cloud region (required for S3).
48+
Region string `yaml:"region"`
49+
50+
// Credentials holds authentication configuration.
51+
Credentials CredentialsConfig `yaml:"credentials"`
52+
53+
// Performance tuning options.
54+
Performance PerformanceConfig `yaml:"performance"`
55+
56+
// Tiering configuration for hot/cold data separation.
57+
Tiering TieringConfig `yaml:"tiering"`
58+
59+
// DevicePath is the path to the objbacker character device.
60+
// Defaults to /dev/zfs_objbacker.
61+
DevicePath string `yaml:"devicePath"`
62+
63+
// DaemonSocketPath is the path to the objbacker daemon socket.
64+
DaemonSocketPath string `yaml:"daemonSocketPath"`
65+
}
66+
67+
// CredentialsConfig holds authentication settings for object storage.
68+
type CredentialsConfig struct {
69+
// AccessKeyID is the access key for S3/GCS.
70+
AccessKeyID string `yaml:"accessKeyId"`
71+
72+
// SecretAccessKey is the secret key for S3/GCS.
73+
SecretAccessKey string `yaml:"secretAccessKey"`
74+
75+
// CredentialsFile is the path to credentials file (for GCS service account).
76+
CredentialsFile string `yaml:"credentialsFile"`
77+
78+
// UseIAMRole enables IAM role-based authentication (for cloud VMs).
79+
UseIAMRole bool `yaml:"useIamRole"`
80+
}
81+
82+
// PerformanceConfig defines performance tuning parameters.
83+
type PerformanceConfig struct {
84+
// BlockSize is the block size for object storage operations.
85+
// Larger blocks (1MB+) are more efficient for streaming.
86+
// Smaller blocks (<128KB) are better for random access.
87+
// Default: 1MB
88+
BlockSize int64 `yaml:"blockSize"`
89+
90+
// LocalCacheSize is the size of the local NVMe cache in bytes.
91+
// Used for metadata and frequently accessed small blocks.
92+
// Default: 10GB
93+
LocalCacheSize int64 `yaml:"localCacheSize"`
94+
95+
// LocalCachePath is the path to the local cache directory.
96+
// Should be on fast storage (NVMe).
97+
LocalCachePath string `yaml:"localCachePath"`
98+
99+
// ReadAheadSize is the prefetch size for sequential reads.
100+
// Default: 4MB
101+
ReadAheadSize int64 `yaml:"readAheadSize"`
102+
103+
// MaxConcurrentOps is the maximum number of concurrent object operations.
104+
// Default: 32
105+
MaxConcurrentOps int `yaml:"maxConcurrentOps"`
106+
107+
// ConnectionTimeout is the timeout for establishing connections.
108+
ConnectionTimeout time.Duration `yaml:"connectionTimeout"`
109+
110+
// RequestTimeout is the timeout for individual requests.
111+
RequestTimeout time.Duration `yaml:"requestTimeout"`
112+
}
113+
114+
// TieringConfig defines data tiering behavior.
115+
type TieringConfig struct {
116+
// Enabled determines if tiered storage is active.
117+
// When enabled, hot data stays on local storage while cold data
118+
// is automatically moved to object storage.
119+
Enabled bool `yaml:"enabled"`
120+
121+
// HotDataThreshold is the age threshold for hot data in hours.
122+
// Data accessed more recently than this stays on local storage.
123+
// Default: 24 hours
124+
HotDataThreshold time.Duration `yaml:"hotDataThreshold"`
125+
126+
// MetadataLocal keeps all metadata on local storage for faster access.
127+
// Default: true
128+
MetadataLocal bool `yaml:"metadataLocal"`
129+
130+
// PromoteOnRead automatically promotes cold data to hot tier on read.
131+
// Default: true
132+
PromoteOnRead bool `yaml:"promoteOnRead"`
133+
}
134+
135+
// DefaultConfig returns a Config with sensible defaults.
136+
func DefaultConfig() Config {
137+
return Config{
138+
Enabled: false,
139+
StorageType: StorageTypeS3,
140+
DevicePath: "/dev/zfs_objbacker",
141+
DaemonSocketPath: "/var/run/zfs_objbacker.sock",
142+
Performance: PerformanceConfig{
143+
BlockSize: 1024 * 1024, // 1MB
144+
LocalCacheSize: 10 * 1024 * 1024 * 1024, // 10GB
145+
LocalCachePath: "/var/cache/objbacker",
146+
ReadAheadSize: 4 * 1024 * 1024, // 4MB
147+
MaxConcurrentOps: 32,
148+
ConnectionTimeout: 30 * time.Second,
149+
RequestTimeout: 5 * time.Minute,
150+
},
151+
Tiering: TieringConfig{
152+
Enabled: true,
153+
HotDataThreshold: 24 * time.Hour,
154+
MetadataLocal: true,
155+
PromoteOnRead: true,
156+
},
157+
}
158+
}
159+
160+
// Validate checks if the configuration is valid.
161+
func (c *Config) Validate() error {
162+
if !c.Enabled {
163+
return nil
164+
}
165+
166+
if c.Bucket == "" {
167+
return fmt.Errorf("objbacker: bucket is required")
168+
}
169+
170+
switch c.StorageType {
171+
case StorageTypeS3, StorageTypeGCS, StorageTypeAzure:
172+
// valid
173+
default:
174+
return fmt.Errorf("objbacker: invalid storage type: %s", c.StorageType)
175+
}
176+
177+
if c.StorageType == StorageTypeS3 && c.Region == "" && !c.Credentials.UseIAMRole {
178+
return fmt.Errorf("objbacker: region is required for S3")
179+
}
180+
181+
if !c.Credentials.UseIAMRole {
182+
if c.Credentials.AccessKeyID == "" && c.Credentials.CredentialsFile == "" {
183+
return fmt.Errorf("objbacker: credentials are required (accessKeyId or credentialsFile)")
184+
}
185+
}
186+
187+
if c.Performance.LocalCachePath == "" {
188+
return fmt.Errorf("objbacker: localCachePath is required")
189+
}
190+
191+
return nil
192+
}
193+
194+
// ObjectPath returns the full object path for a given key.
195+
func (c *Config) ObjectPath(key string) string {
196+
if c.Prefix != "" {
197+
return fmt.Sprintf("%s/%s", c.Prefix, key)
198+
}
199+
return key
200+
}

0 commit comments

Comments
 (0)