Skip to content

Commit 5148338

Browse files
committed
Add options to run the cluster in a faster but less safe mode
These are `--faster-but-less-safe` and `--safer-but-slower` flags, available at present only to the `shell` subcommand.
1 parent aa3d1ef commit 5148338

File tree

3 files changed

+150
-35
lines changed

3 files changed

+150
-35
lines changed

src/cli.rs

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ pub enum Commands {
1616
/// Start a psql shell, creating and starting the cluster as necessary.
1717
#[clap(display_order = 1)]
1818
Shell {
19+
#[clap(flatten)]
20+
cluster: ClusterArgs,
21+
1922
#[clap(flatten)]
2023
database: DatabaseArgs,
2124

@@ -27,6 +30,9 @@ pub enum Commands {
2730
/// necessary.
2831
#[clap(display_order = 2)]
2932
Exec {
33+
#[clap(flatten)]
34+
cluster: ClusterArgs,
35+
3036
#[clap(flatten)]
3137
database: DatabaseArgs,
3238

@@ -50,7 +56,7 @@ pub enum Commands {
5056
}
5157

5258
#[derive(Args)]
53-
pub struct DatabaseArgs {
59+
pub struct ClusterArgs {
5460
/// The directory in which to place, or find, the cluster.
5561
#[clap(
5662
short = 'D',
@@ -62,6 +68,43 @@ pub struct DatabaseArgs {
6268
)]
6369
pub dir: PathBuf,
6470

71+
/// Run the cluster in a "faster" mode.
72+
///
73+
/// This disables `fsync` and `full_page_writes` in the cluster. This can
74+
/// make the cluster faster but it can also lead to unrecoverable data
75+
/// corruption in the event of a power failure or system crash. Useful for
76+
/// tests, for example, but probably not production.
77+
///
78+
/// In the future this may make additional or different changes. See
79+
/// https://www.postgresql.org/docs/16/runtime-config-wal.html for more
80+
/// information.
81+
///
82+
/// This option is STICKY. Once you've used it, the cluster will be
83+
/// configured to be "faster but less safe" and you do not need to specify
84+
/// it again. To find out if the cluster is running in this mode, open a
85+
/// `psql` shell (e.g. `postgresfixture shell`) and run `SHOW fsync;` and
86+
/// `SHOW full_page_writes;`.
87+
#[clap(long = "faster-but-less-safe", action = clap::ArgAction::SetTrue, default_value_t = false, display_order = 2)]
88+
pub faster: bool,
89+
90+
/// Run the cluster in a "safer" mode.
91+
///
92+
/// This is the opposite of `--faster-but-less-safe`, i.e. it runs with
93+
/// `fsync` and `full_page_writes` enabled in the cluster. This is the
94+
/// default when neither `--faster-but-less-safe` nor `--safe-but-slower`
95+
/// have been specified.
96+
///
97+
/// This option is STICKY. Once you've used it, the cluster will be
98+
/// configured to be "slower and safer" and you do not need to specify it
99+
/// again. To find out if the cluster is running in this mode, open a `psql`
100+
/// shell (e.g. `postgresfixture shell`) and run `SHOW fsync;` and `SHOW
101+
/// full_page_writes;`.
102+
#[clap(long = "slower-but-safer", action = clap::ArgAction::SetTrue, default_value_t = false, display_order = 3, conflicts_with = "faster")]
103+
pub slower: bool,
104+
}
105+
106+
#[derive(Args)]
107+
pub struct DatabaseArgs {
65108
/// The database to connect to.
66109
#[clap(
67110
short = 'd',
@@ -73,6 +116,7 @@ pub struct DatabaseArgs {
73116
)]
74117
pub name: String,
75118
}
119+
76120
#[derive(Args)]
77121
pub struct LifecycleArgs {
78122
/// Destroy the cluster after use. WARNING: This will DELETE THE DATA

src/coordinate.rs

Lines changed: 29 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
//! let cluster = cluster::Cluster::new(&data_dir, runtime);
1313
//! let lock_file = cluster_dir.path().join("lock");
1414
//! let lock = lock::UnlockedFile::try_from(lock_file.as_path()).unwrap();
15-
//! assert!(coordinate::run_and_stop(&cluster, lock, || cluster.exists()).unwrap())
15+
//! assert!(coordinate::run_and_stop(&cluster, lock, |_| Ok(()), |cluster| cluster.exists()).unwrap())
1616
//! ```
1717
1818
use std::time::Duration;
@@ -30,16 +30,18 @@ use crate::lock;
3030
/// (maybe) stops the cluster again, and finally returns the result of `action`.
3131
/// If there are other users of the cluster – i.e. if an exclusive lock cannot
3232
/// be acquired during the shutdown phase – then the cluster is left running.
33-
pub fn run_and_stop<F, T>(
34-
cluster: &Cluster,
33+
pub fn run_and_stop<'a, INIT, ACTION, T>(
34+
cluster: &'a Cluster,
3535
lock: lock::UnlockedFile,
36-
action: F,
36+
initialise: INIT,
37+
action: ACTION,
3738
) -> Result<T, ClusterError>
3839
where
39-
F: std::panic::UnwindSafe + FnOnce() -> T,
40+
INIT: std::panic::UnwindSafe + FnOnce(&'a Cluster) -> Result<(), ClusterError>,
41+
ACTION: std::panic::UnwindSafe + FnOnce(&'a Cluster) -> T,
4042
{
41-
let lock = startup(cluster, lock)?;
42-
let action_res = std::panic::catch_unwind(action);
43+
let lock = startup(cluster, lock, initialise)?;
44+
let action_res = std::panic::catch_unwind(|| action(cluster));
4345
let _: Option<bool> = shutdown(cluster, lock, |cluster| cluster.stop())?;
4446
match action_res {
4547
Ok(result) => Ok(result),
@@ -54,27 +56,33 @@ where
5456
/// returning. If there are other users of the cluster – i.e. if an exclusive
5557
/// lock cannot be acquired during the shutdown phase – then the cluster is left
5658
/// running and is **not** destroyed.
57-
pub fn run_and_destroy<F, T>(
58-
cluster: &Cluster,
59+
pub fn run_and_destroy<'a, INIT, ACTION, T>(
60+
cluster: &'a Cluster,
5961
lock: lock::UnlockedFile,
60-
action: F,
62+
initialise: INIT,
63+
action: ACTION,
6164
) -> Result<T, ClusterError>
6265
where
63-
F: std::panic::UnwindSafe + FnOnce() -> T,
66+
INIT: std::panic::UnwindSafe + FnOnce(&'a Cluster) -> Result<(), ClusterError>,
67+
ACTION: std::panic::UnwindSafe + FnOnce(&'a Cluster) -> T,
6468
{
65-
let lock = startup(cluster, lock)?;
66-
let action_res = std::panic::catch_unwind(action);
69+
let lock = startup(cluster, lock, initialise)?;
70+
let action_res = std::panic::catch_unwind(|| action(cluster));
6771
let shutdown_res = shutdown(cluster, lock, |cluster| cluster.destroy());
6872
match action_res {
6973
Ok(result) => shutdown_res.map(|_| result),
7074
Err(err) => std::panic::resume_unwind(err),
7175
}
7276
}
7377

74-
fn startup(
75-
cluster: &Cluster,
78+
fn startup<'a, INIT>(
79+
cluster: &'a Cluster,
7680
mut lock: lock::UnlockedFile,
77-
) -> Result<lock::LockedFileShared, ClusterError> {
81+
initialise: INIT,
82+
) -> Result<lock::LockedFileShared, ClusterError>
83+
where
84+
INIT: std::panic::UnwindSafe + FnOnce(&'a Cluster) -> Result<(), ClusterError>,
85+
{
7886
loop {
7987
lock = match lock.try_lock_exclusive() {
8088
Ok(Left(lock)) => {
@@ -85,6 +93,8 @@ fn startup(
8593
// exclusive lock, so we must check if the cluster is
8694
// running _now_, else loop back to the top again.
8795
if cluster.running()? {
96+
// Perform post-start initialisation.
97+
initialise(cluster)?;
8898
return Ok(lock);
8999
} else {
90100
// Release all locks then sleep for a random time between
@@ -101,7 +111,9 @@ fn startup(
101111
}
102112
}
103113
Ok(Right(lock)) => {
104-
// We have an exclusive lock, so try to start the cluster.
114+
// We have an exclusive lock. Perform pre-start initialisation.
115+
initialise(cluster)?;
116+
// Now try to start the cluster.
105117
cluster.start()?;
106118
// Once started, downgrade to a shared log.
107119
return Ok(lock.lock_shared()?);

src/main.rs

Lines changed: 76 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -18,27 +18,79 @@ fn main() -> Result<()> {
1818
let cli = cli::Cli::parse();
1919
let result = match cli.command {
2020
cli::Commands::Shell {
21+
cluster:
22+
cli::ClusterArgs {
23+
dir,
24+
faster,
25+
slower,
26+
},
2127
database,
2228
lifecycle,
23-
} => run(database.dir, &database.name, lifecycle.destroy, |cluster| {
24-
check_exit(
25-
cluster
26-
.shell(&database.name)
27-
.wrap_err("Starting PostgreSQL shell in cluster failed")?,
29+
} => {
30+
run(
31+
dir,
32+
&database.name,
33+
lifecycle.destroy,
34+
|cluster| match (cluster.running()?, faster, slower) {
35+
// No change to go faster or slower, so do nothing.
36+
(_, false, false) => Ok(()),
37+
// Request to go faster; cluster is running.
38+
(true, true, _) => {
39+
let mut conn = cluster.connect("template1")?;
40+
conn.execute("ALTER SYSTEM SET fsync = 'off'", &[])?;
41+
conn.execute("ALTER SYSTEM SET full_page_writes = 'off'", &[])?;
42+
Ok(())
43+
}
44+
// Request to go slower; cluster is running.
45+
(true, _, true) => {
46+
let mut conn = cluster.connect("template1")?;
47+
conn.execute("ALTER SYSTEM SET fsync = 'on'", &[])?;
48+
conn.execute("ALTER SYSTEM SET full_page_writes = 'on'", &[])?;
49+
Ok(())
50+
}
51+
// Request to go faster; cluster is NOT running.
52+
(false, true, _) => cluster
53+
.single(&[
54+
"ALTER SYSTEM SET fsync = 'off'",
55+
"ALTER SYSTEM SET full_page_writes = 'off'",
56+
])
57+
.map(|_output| ()),
58+
// Request to go slower; cluster is NOT running.
59+
(false, _, true) => cluster
60+
.single(&[
61+
"ALTER SYSTEM SET fsync = 'on'",
62+
"ALTER SYSTEM SET full_page_writes = 'on'",
63+
])
64+
.map(|_output| ()),
65+
},
66+
|cluster| {
67+
check_exit(
68+
cluster
69+
.shell(&database.name)
70+
.wrap_err("Starting PostgreSQL shell in cluster failed")?,
71+
)
72+
},
2873
)
29-
}),
74+
}
3075
cli::Commands::Exec {
76+
cluster,
3177
database,
3278
command,
3379
args,
3480
lifecycle,
35-
} => run(database.dir, &database.name, lifecycle.destroy, |cluster| {
36-
check_exit(
37-
cluster
38-
.exec(&database.name, command, &args)
39-
.wrap_err("Executing command in cluster failed")?,
40-
)
41-
}),
81+
} => run(
82+
cluster.dir,
83+
&database.name,
84+
lifecycle.destroy,
85+
|_cluster| Ok(()),
86+
|cluster| {
87+
check_exit(
88+
cluster
89+
.exec(&database.name, command, &args)
90+
.wrap_err("Executing command in cluster failed")?,
91+
)
92+
},
93+
),
4294
cli::Commands::Runtimes => {
4395
let runtimes_on_path = runtime::Runtime::find_on_path();
4496

@@ -83,9 +135,16 @@ fn check_exit(status: ExitStatus) -> Result<i32> {
83135

84136
const UUID_NS: uuid::Uuid = uuid::Uuid::from_u128(93875103436633470414348750305797058811);
85137

86-
fn run<F>(database_dir: PathBuf, database_name: &str, destroy: bool, action: F) -> Result<i32>
138+
fn run<INIT, ACTION>(
139+
database_dir: PathBuf,
140+
database_name: &str,
141+
destroy: bool,
142+
initialise: INIT,
143+
action: ACTION,
144+
) -> Result<i32>
87145
where
88-
F: FnOnce(&cluster::Cluster) -> Result<i32> + std::panic::UnwindSafe,
146+
INIT: std::panic::UnwindSafe + FnOnce(&cluster::Cluster) -> Result<(), cluster::ClusterError>,
147+
ACTION: FnOnce(&cluster::Cluster) -> Result<i32> + std::panic::UnwindSafe,
89148
{
90149
// Create the cluster directory first.
91150
match fs::create_dir(&database_dir) {
@@ -119,7 +178,7 @@ where
119178
coordinate::run_and_stop
120179
};
121180

122-
runner(&cluster, lock, || {
181+
runner(&cluster, lock, initialise, |cluster: &cluster::Cluster| {
123182
if !cluster
124183
.databases()
125184
.wrap_err("Could not list databases")?
@@ -137,6 +196,6 @@ where
137196
ctrlc::set_handler(|| ()).wrap_err("Could not set signal handler")?;
138197

139198
// Finally, run the given action.
140-
action(&cluster)
199+
action(cluster)
141200
})?
142201
}

0 commit comments

Comments
 (0)