Skip to content

Commit 7676a7a

Browse files
committed
fix: Data restore fails on managed PostgreSQL services
Remove --disable-triggers and --jobs=N from pg_restore. The v7.0.8 fix for FK constraint violations required superuser privileges that managed PostgreSQL services (SerenDB, AWS RDS, Neon) don't grant. Single-threaded restore naturally respects FK dependency order, avoiding the need for elevated privileges while ensuring data integrity. Closes #73
1 parent e0b5d14 commit 7676a7a

4 files changed

Lines changed: 25 additions & 21 deletions

File tree

CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,16 @@
22

33
All notable changes to this project will be documented in this file.
44

5+
## [7.0.9] - 2025-12-10
6+
7+
### Fixed
8+
9+
- **Data restore fails on managed PostgreSQL services**: Fixed bug where `pg_restore` would fail with "permission denied: system trigger" on managed PostgreSQL services (SerenDB, AWS RDS, Neon, etc.). The v7.0.8 fix for FK constraint violations used `--disable-triggers`, which requires superuser privileges that managed services don't grant. Replaced parallel restore with single-threaded restore that naturally respects FK dependency order, eliminating the need for elevated privileges.
10+
11+
### Changed
12+
13+
- **Removed parallel restore**: Data restoration now uses single-threaded `pg_restore` instead of parallel (`--jobs=N`). This is slower but works on all PostgreSQL deployments without requiring superuser privileges. The trade-off is acceptable since correctness and compatibility are prioritized over speed.
14+
515
## [7.0.8] - 2025-12-09
616

717
### Fixed

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "database-replicator"
3-
version = "7.0.8"
3+
version = "7.0.9"
44
edition = "2021"
55
license = "Apache-2.0"
66
description = "Universal database-to-PostgreSQL replication CLI. Supports PostgreSQL, SQLite, MongoDB, and MySQL."

src/migration/restore.rs

Lines changed: 13 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -171,14 +171,22 @@ pub async fn restore_schema(target_url: &str, input_path: &str) -> Result<()> {
171171
Ok(())
172172
}
173173

174-
/// Restore data using pg_restore with parallel jobs
174+
/// Restore data using pg_restore
175175
///
176176
/// Uses PostgreSQL directory format restore with:
177-
/// - Parallel restore for faster performance
177+
/// - Single-threaded restore to respect FK dependency order
178178
/// - Automatic decompression of compressed dump files
179179
/// - Optimized for directory format dumps created by dump_data()
180180
///
181-
/// The number of parallel jobs is automatically determined based on available CPU cores.
181+
/// # Why Single-Threaded?
182+
///
183+
/// Parallel restore (`--jobs=N`) requires `--disable-triggers` to avoid FK
184+
/// constraint violations when tables are restored out of dependency order.
185+
/// However, `--disable-triggers` requires superuser privileges, which managed
186+
/// PostgreSQL services (SerenDB, AWS RDS, Neon, etc.) do not grant.
187+
///
188+
/// Single-threaded restore naturally processes tables in FK dependency order,
189+
/// avoiding the need for elevated privileges while ensuring data integrity.
182190
///
183191
/// # Note on Retry Behavior
184192
///
@@ -189,16 +197,7 @@ pub async fn restore_schema(target_url: &str, input_path: &str) -> Result<()> {
189197
/// If data restoration fails due to connection issues, the user should re-run the
190198
/// command with --drop-existing to ensure a clean slate.
191199
pub async fn restore_data(target_url: &str, input_path: &str) -> Result<()> {
192-
// Determine optimal number of parallel jobs (number of CPUs, capped at 8)
193-
let num_cpus = std::thread::available_parallelism()
194-
.map(|n| n.get().min(8))
195-
.unwrap_or(4);
196-
197-
tracing::info!(
198-
"Restoring data from {} (parallel={}, format=directory)",
199-
input_path,
200-
num_cpus
201-
);
200+
tracing::info!("Restoring data from {} (format=directory)", input_path);
202201

203202
// Parse URL and create .pgpass file for secure authentication
204203
let parts = crate::utils::parse_postgres_url(target_url)
@@ -218,8 +217,6 @@ pub async fn restore_data(target_url: &str, input_path: &str) -> Result<()> {
218217
let mut cmd = Command::new("pg_restore");
219218
cmd.arg("--data-only")
220219
.arg("--no-owner")
221-
.arg("--disable-triggers") // Disable FK constraints during parallel restore
222-
.arg(format!("--jobs={}", num_cpus)) // Parallel restore jobs
223220
.arg("--host")
224221
.arg(&parts.host)
225222
.arg("--port")
@@ -277,10 +274,7 @@ pub async fn restore_data(target_url: &str, input_path: &str) -> Result<()> {
277274
);
278275
}
279276

280-
tracing::info!(
281-
"✓ Data restored successfully using {} parallel jobs",
282-
num_cpus
283-
);
277+
tracing::info!("✓ Data restored successfully");
284278
Ok(())
285279
}
286280

0 commit comments

Comments
 (0)