diff --git a/Cargo.toml b/Cargo.toml index aede93e..2621ae8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,7 @@ members = ["hiqlite"] exclude = ["examples"] [workspace.package] -version = "0.3.0-20241124" +version = "0.3.0" edition = "2021" license = "Apache-2.0" authors = ["Sebastian Dobe =8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.26.0", - "@rollup/rollup-android-arm64": "4.26.0", - "@rollup/rollup-darwin-arm64": "4.26.0", - "@rollup/rollup-darwin-x64": "4.26.0", - "@rollup/rollup-freebsd-arm64": "4.26.0", - "@rollup/rollup-freebsd-x64": "4.26.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.26.0", - "@rollup/rollup-linux-arm-musleabihf": "4.26.0", - "@rollup/rollup-linux-arm64-gnu": "4.26.0", - "@rollup/rollup-linux-arm64-musl": "4.26.0", - "@rollup/rollup-linux-powerpc64le-gnu": "4.26.0", - "@rollup/rollup-linux-riscv64-gnu": "4.26.0", - "@rollup/rollup-linux-s390x-gnu": "4.26.0", - "@rollup/rollup-linux-x64-gnu": "4.26.0", - "@rollup/rollup-linux-x64-musl": "4.26.0", - "@rollup/rollup-win32-arm64-msvc": "4.26.0", - "@rollup/rollup-win32-ia32-msvc": "4.26.0", - "@rollup/rollup-win32-x64-msvc": "4.26.0", + "@rollup/rollup-android-arm-eabi": "4.27.4", + "@rollup/rollup-android-arm64": "4.27.4", + "@rollup/rollup-darwin-arm64": "4.27.4", + "@rollup/rollup-darwin-x64": "4.27.4", + "@rollup/rollup-freebsd-arm64": "4.27.4", + "@rollup/rollup-freebsd-x64": "4.27.4", + "@rollup/rollup-linux-arm-gnueabihf": "4.27.4", + "@rollup/rollup-linux-arm-musleabihf": "4.27.4", + "@rollup/rollup-linux-arm64-gnu": "4.27.4", + "@rollup/rollup-linux-arm64-musl": "4.27.4", + "@rollup/rollup-linux-powerpc64le-gnu": "4.27.4", + "@rollup/rollup-linux-riscv64-gnu": "4.27.4", + "@rollup/rollup-linux-s390x-gnu": "4.27.4", + "@rollup/rollup-linux-x64-gnu": "4.27.4", + "@rollup/rollup-linux-x64-musl": "4.27.4", + "@rollup/rollup-win32-arm64-msvc": "4.27.4", + "@rollup/rollup-win32-ia32-msvc": "4.27.4", + "@rollup/rollup-win32-x64-msvc": "4.27.4", "fsevents": "~2.3.2" } }, @@ -1511,9 +1511,9 @@ } }, "node_modules/svelte": { - "version": "5.1.16", - "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.1.16.tgz", - "integrity": "sha512-QcY+om9r8+uTcSfeFuv8++ExdfwVCKeT+Y7GPSZ6rQPczvy62BMtvMoi0rScabgv+upGE5jxKjd7M4u23+AjGA==", + "version": "5.2.10", + "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.2.10.tgz", + "integrity": "sha512-ON0OyO7vOmSjTc9mLjusu3vf1I7BvjovbiRB7j84F1WZMXV6dR+Tj4btIzxQxMHfzbGskaFmRa7qjgmBSVBnhQ==", "dev": true, "license": "MIT", "dependencies": { @@ -1524,9 +1524,9 @@ "acorn-typescript": "^1.4.13", "aria-query": "^5.3.1", "axobject-query": "^4.1.0", - "esm-env": "^1.0.0", + "esm-env": "^1.2.0", "esrap": "^1.2.2", - "is-reference": "^3.0.2", + "is-reference": "^3.0.3", "locate-character": "^3.0.0", "magic-string": "^0.30.11", "zimmerframe": "^1.1.2" @@ -1536,9 +1536,9 @@ } }, "node_modules/svelte-check": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-4.0.7.tgz", - "integrity": "sha512-24hwo+D5L35HOXsh3Z2sU4WhdDLavlHquYaJhrEqAt+mV1xOVzoMVYThW80n99osDJxyuH+vxjNFkNRL4EvwTg==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-4.1.0.tgz", + "integrity": "sha512-AflEZYqI578KuDZcpcorPSf597LStxlkN7XqXi38u09zlHODVKd7c+7OuubGzbhgGRUqNTdQCZ+Ga96iRXEf2g==", "dev": true, "license": "MIT", "dependencies": { @@ -1581,9 +1581,9 @@ } }, "node_modules/typescript": { - "version": "5.6.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz", - "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==", + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.2.tgz", + "integrity": "sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==", "dev": true, "license": "Apache-2.0", "bin": { @@ -1694,9 +1694,9 @@ } }, "node_modules/vitefu": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-1.0.3.tgz", - "integrity": "sha512-iKKfOMBHob2WxEJbqbJjHAkmYgvFDPhuqrO82om83S8RLk+17FtyMBfcyeH8GqD0ihShtkMW/zzJgiA51hCNCQ==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-1.0.4.tgz", + "integrity": "sha512-y6zEE3PQf6uu/Mt6DTJ9ih+kyJLr4XcSgHR2zUkM8SWDhuixEJxfJ6CZGMHh1Ec3vPLoEA0IHU5oWzVqw8ulow==", "dev": true, "license": "MIT", "workspaces": [ @@ -1704,7 +1704,7 @@ "tests/projects/*" ], "peerDependencies": { - "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0-beta.0" + "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0" }, "peerDependenciesMeta": { "vite": { diff --git a/examples/bench/Cargo.lock b/examples/bench/Cargo.lock index 05e5ba1..0ce046d 100644 --- a/examples/bench/Cargo.lock +++ b/examples/bench/Cargo.lock @@ -1240,7 +1240,7 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "hiqlite" -version = "0.3.0-20241115" +version = "0.3.0" dependencies = [ "axum", "axum-server", diff --git a/examples/cache-only/Cargo.lock b/examples/cache-only/Cargo.lock index d36dc1c..7e42023 100644 --- a/examples/cache-only/Cargo.lock +++ b/examples/cache-only/Cargo.lock @@ -876,7 +876,7 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "hiqlite" -version = "0.3.0-20241115" +version = "0.3.0" dependencies = [ "axum", "axum-server", diff --git a/examples/sqlite-only/Cargo.lock b/examples/sqlite-only/Cargo.lock index 25fab0f..d67c3eb 100644 --- a/examples/sqlite-only/Cargo.lock +++ b/examples/sqlite-only/Cargo.lock @@ -1179,7 +1179,7 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "hiqlite" -version = "0.3.0-20241115" +version = "0.3.0" dependencies = [ "axum", "axum-server", diff --git a/examples/walkthrough/Cargo.lock b/examples/walkthrough/Cargo.lock index d961ff5..1a26a1e 100644 --- a/examples/walkthrough/Cargo.lock +++ b/examples/walkthrough/Cargo.lock @@ -1224,7 +1224,7 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "hiqlite" -version = "0.3.0-20241115" +version = "0.3.0" dependencies = [ "axum", "axum-server", diff --git a/hiqlite/README.md b/hiqlite/README.md index 4b72663..9bd8d03 100644 --- a/hiqlite/README.md +++ b/hiqlite/README.md @@ -7,31 +7,31 @@ Hiqlite is an embeddable SQLite database that can form a Raft cluster to provide Why another SQLite replication solution? Other projects exist already that can do this. The problem is that none of them checks all boxes. They either require an additional independent process running on the side which can do async -replication, need a special file system, or are running as a server. +replication, need a special file system, have bad throughput / latency, or are running as a server. I don't think that running SQLite as a server is a good solution. Yes, it is very resource friendly, and it may be a -good solution when you are heavily resource constrained, but you lose its biggest strength when doing this: having +good choice when you are heavily resource constrained, but you lose its biggest strength when doing this: having all your data local, which makes reads superfast without network latency. -Hiqlite builds on top of `rusqlite` and provides an async wrapper around it to make it easy usable with `tokio`. For the -Raft logic, it builds on top of`openraft` while providing its own storage and network implementations. +Hiqlite builds on top of `rusqlite` and provides an async wrapper around it. For the Raft logic, it builds on top of +`openraft` while providing its own storage and network implementations. ## Goal -Rust is such an efficient language that you usually only need one process to achieve whatever you need, for most -applications at least. An embedded SQLite makes the whole process very convenient. You get very fast local reads and at -the same time, it comes with the benefit that you don't have to manage an additional database, which you need to set up, +Rust is such an efficient language that you most often only need a single process to achieve whatever you need, for most +applications at least. An embedded SQLite makes everything very convenient. You get very fast local reads and at the +same time, it comes with the benefit that you don't have to manage an additional database, which you need to set up, configure and more importantly maintain. And embedded SQLite will bring database updates basically for free when you -build a new version. +build a new application version. When configured correctly, SQLite offers very good performance and can handle most workloads these days. In very -first benchmarks that I did to find out if the project makes sense in the first place, I got up to 24.5k single -inserts / s on a cheap consumer grade M2 SSD. These tests were done on localhost with 3 different processes, but still -with real networking in between them. On another machine with older SATA SSDs it reached up to 16.5k inserts / s. +first benchmarks that I did to find out if the project makes sense at all, I got up to 24.5k single inserts / s on a +cheap consumer grade M2 SSD. These tests were done on localhost with 3 different processes, but still with real +networking in between them. On another machine with older SATA SSDs it reached up to 16.5k inserts / s. At the end, the goal is that you can have the simplicity and all the advantages of an embedded SQLite while still being able to run your application highly available (which is almost always mandatory for me) and having automatic fail-over -in case of any errors or problems. +and self-healing capabilities in case of any errors or problems. ## Currently implemented and working features @@ -40,10 +40,7 @@ in case of any errors or problems. - persistent storage for Raft logs (with [rocksdb](https://github.com/rust-rocksdb/rust-rocksdb)) and SQLite state machine - "magic" auto setup, no need to do any manual init or management for the Raft -- self-healing - each node can automatically recover from: - - lost cached WAL buffer for the state machine - - complete loss of the state machine DB (SQLite) - - complete loss of the whole volume itself +- self-healing - each node can automatically recover from un-graceful shutdowns and even full data volume loss - automatic database migrations - fully authenticated networking - optional TLS everywhere for a zero-trust philosophy @@ -73,8 +70,8 @@ in case of any errors or problems. ## Performance I added a [bench example](https://github.com/sebadob/hiqlite/tree/main/examples/bench) for easy testing on different -hardware and setups. This example is very simple and it mostly cares about `INSERT` performance, since this is usually -the bottleneck with Raft, because of 2 RTTs for each write by design. +hardware and setups. This example is very simple and it mostly cares about `INSERT` performance, which is usually the +bottleneck when using Raft, because of 2 network round-trips for each write by design. The performance can vary quite a bit, depending on your setup and hardware, of course. Even though the project is in an early state, I already put quite a bit of work in optimizing latency and throughput and I would say, it will be able @@ -84,17 +81,17 @@ SSDs and fast memory make quite a big difference of course. Regarding the CPU, t more from fewer cores with higher single core speed like Workstation CPU's or AMD Epyc 4004 series. The reason is the single writer at a time limitation from SQLite. -Just to give you some raw numbers so you can get an idea how fast it currently is, some numbers below. -These values were taken using the [bench example](https://github.com/sebadob/hiqlite/tree/main/examples/bench). +Just to give you some raw numbers so you can get an idea how fast it currently is, some numbers below. These values were +taken using the [bench example](https://github.com/sebadob/hiqlite/tree/main/examples/bench). -Hiqlite can run as a single instance as well, which will have lower latency and higher throughput of course, but I did -not include this in the tests, because you usually want a HA Raft cluster of course. With higher concurrency (`-c`), -only write / second will change, reads will always be local anyway. +Hiqlite can run as a single instance as well, which will have even lower latency and higher throughput of course, but I +did not include this in the tests, because you usually want a HA Raft cluster. With higher concurrency (`-c`), only +write / second will change, reads will always be local anyway. Test command (`-c` adjusted each time for different concurrency): ``` -cargo run --release -- cluster -c 4 -r 100000 +cargo run --release -- cluster -c 4 -r 10000 ``` ### Beefy Workstation @@ -507,7 +504,7 @@ spec: spec: containers: - name: hiqlite - image: ghcr.io/sebadob/hiqlite:0.2.1 + image: ghcr.io/sebadob/hiqlite:0.3.0 imagePullPolicy: Always securityContext: allowPrivilegeEscalation: false @@ -642,7 +639,7 @@ spec: spec: containers: - name: hiqlite-proxy - image: ghcr.io/sebadob/hiqlite:0.2.1 + image: ghcr.io/sebadob/hiqlite:0.3.0 command: [ "/app/hiqlite", "proxy" ] imagePullPolicy: Always securityContext: @@ -708,16 +705,12 @@ like shown in the There are currently some known issues: -1. Sometimes a node can hang on shutdown. In this case it needs to be killed manually. As mentioned already, I was not - able to reproduce this consistently so far. This could be solved by simply adding a timeout to the shutdown handler, - but I did not do that on purpose at the current stage. I would rather find the issue and fix it, even if it takes - time because of not being easily reproducible than ignoring the issue with a timeout. -2. When creating synthetic benchmarks for testing write throughput at the absolute max, you will see error logs because +1. When creating synthetic benchmarks for testing write throughput at the absolute max, you will see error logs because of missed Raft heartbeats and leader switches, even though the network and everything else is fine. The reason is - simply that the Raft heartbeats in the current implementation come in-order with the Raft data replication. So, if - you generate an insane amount of Raft data which takes time to replicate, because you end up being effectively I/O - bound by your physical disk, these heartbeats can get lost, because they won't happen in-time. - This issue will be resolved with the next major release of `openraft`, where heartbeats will be sent separately from - the main data replication. -3. In the current version, the logging output is very verbose on the `info` level. This is on purpose until everything + that the Raft heartbeats in the current implementation come in-order with the Raft data replication. So, if you + generate an insane amount of Raft data which takes time to replicate, because you end up being effectively I/O + bound by your physical disk, these heartbeats can get lost, because they won't happen in-time. This issue will be + resolved with the next major release of `openraft`, where heartbeats will be sent separately from the main data + replication. +2. In the current version, the logging output is very verbose on the `info` level. This is on purpose until everything has been stabilized. In future versions, this will be reduced quite a bit. diff --git a/hiqlite/src/client/stream.rs b/hiqlite/src/client/stream.rs index 89fc23f..00fd52b 100644 --- a/hiqlite/src/client/stream.rs +++ b/hiqlite/src/client/stream.rs @@ -164,7 +164,7 @@ async fn client_stream( raft_type: RaftType, ) { let mut in_flight: HashMap>> = - HashMap::with_capacity(32); + HashMap::with_capacity(8); let mut in_flight_buf: HashMap< usize, oneshot::Sender>, @@ -592,7 +592,7 @@ async fn client_stream( } assert!(in_flight.is_empty()); // reset to a reasonable size for the next start to keep memory usage under control - in_flight = HashMap::with_capacity(32); + in_flight = HashMap::with_capacity(8); info!("client stream tasks killed - re-connecting now"); } diff --git a/hiqlite/src/helpers.rs b/hiqlite/src/helpers.rs index 2b79de3..831e9fd 100644 --- a/hiqlite/src/helpers.rs +++ b/hiqlite/src/helpers.rs @@ -119,3 +119,15 @@ pub async fn fn_access(path: &str, mode: u32) -> Result<(), Error> { } Ok(()) } + +/// Reads a single line from stdin and returns it `trim`ed. +#[cfg(feature = "server")] +pub async fn read_line_stdin() -> Result { + let line = tokio::task::spawn_blocking(|| { + let mut buf = String::with_capacity(4); + std::io::stdin().read_line(&mut buf)?; + Ok::(buf.trim().to_string()) + }) + .await??; + Ok(line) +} diff --git a/hiqlite/src/network/raft_client_split.rs b/hiqlite/src/network/raft_client_split.rs index f17fbeb..03669a3 100644 --- a/hiqlite/src/network/raft_client_split.rs +++ b/hiqlite/src/network/raft_client_split.rs @@ -201,12 +201,17 @@ impl NetworkStreaming { heartbeat_interval: u64, ) { let mut request_id = 0usize; - // TODO probably a Vec<_> is faster here since we would never have too many in flight reqs + // TODO probably, a Vec<_> is faster here since we would never have too many in flight reqs // for raft internal replication and voting? -> check + // maybe feature-gate an alternative impl, even though it might not make the biggest difference let mut in_flight: HashMap< usize, oneshot::Sender>, - > = HashMap::with_capacity(32); + > = HashMap::with_capacity(8); + // let mut in_flight: Vec<( + // usize, + // oneshot::Sender>, + // )> = Vec::with_capacity(8); let mut shutdown = false; loop { @@ -321,6 +326,17 @@ impl NetworkStreaming { } RaftRequest::StreamResponse(resp) => { + // match in_flight.iter().position(|(id, _)| &resp.request_id == id) { + // None => { + // error!("client ack for RaftStreamResponse missing"); + // } + // Some(idx) => { + // let (_id, ack) = in_flight.swap_remove(idx); + // if ack.send(Ok(resp.payload)).is_err() { + // error!("sending back stream response from raft server"); + // } + // } + // } match in_flight.remove(&resp.request_id) { None => { error!("client ack for RaftStreamResponse missing"); @@ -351,6 +367,7 @@ impl NetworkStreaming { break; } + // in_flight.push((request_id, ack)); in_flight.insert(request_id, ack); request_id += 1; } @@ -359,10 +376,12 @@ impl NetworkStreaming { handle_write.abort(); handle_read.abort(); + // for (_, ack) in in_flight.drain(..) { for (_, ack) in in_flight.drain() { let _ = ack.send(Err(Error::Connect("Raft WebSocket stream ended".into()))); } - in_flight = HashMap::with_capacity(32); + // reset to a reasonable size for the next start to keep memory usage under control + in_flight = HashMap::with_capacity(8); if shutdown { break; diff --git a/hiqlite/src/server/args.rs b/hiqlite/src/server/args.rs index 163a33c..81d9e62 100644 --- a/hiqlite/src/server/args.rs +++ b/hiqlite/src/server/args.rs @@ -46,9 +46,9 @@ pub struct ArgsProxy { #[derive(Debug, Clone, Parser)] pub struct ArgsGenerate { - /// Set the password for the dashboard - #[clap(short, long)] - pub password: Option, + /// Set a custom password for the dashboard. If `false`, a random one will be generated. + #[clap(short, long, default_value = "false")] + pub password: bool, /// Issue insecure authn cookies for the dashboard. Do NOT use in production! #[clap(long, default_value = "false")] diff --git a/hiqlite/src/server/config.rs b/hiqlite/src/server/config.rs index 01bd0bf..915cdd2 100644 --- a/hiqlite/src/server/config.rs +++ b/hiqlite/src/server/config.rs @@ -1,9 +1,9 @@ -use crate::helpers::fn_access; +use crate::helpers::{fn_access, read_line_stdin}; use crate::server::args::{ArgsConfig, ArgsGenerate}; use crate::server::password; use crate::{Error, NodeConfig}; use cryptr::{utils, EncKeys}; -use tokio::fs; +use tokio::{fs, task}; pub fn build_node_config(args: ArgsConfig) -> Result { let config_path = if args.config_file == "$HOME/.hiqlite/config" { @@ -26,22 +26,37 @@ pub fn build_node_config(args: ArgsConfig) -> Result { pub async fn generate(args: ArgsGenerate) -> Result<(), Error> { let path = default_config_dir(); fs::create_dir_all(&path).await?; - fn_access(&path, 0o600).await?; + fn_access(&path, 0o700).await?; let path_file = default_config_file_path(); if fs::File::open(&path_file).await.is_ok() { - return Err(Error::Error( - format!("Config file {} exists already", path_file).into(), - )); + eprint!( + "Config file {} exists already. Overwrite? (yes): ", + path_file + ); + let line = read_line_stdin().await?; + if line != "yes" { + return Ok(()); + } } - let password_dashboard = if let Some(password) = args.password { - password::hash_password_b64(password).await? + let pwd_plain = if args.password { + let plain; + loop { + println!("Provide a password with at least 16 characters: "); + let line = read_line_stdin().await?; + if line.len() > 16 { + plain = line; + break; + } + } + plain } else { - let plain = utils::secure_random_alnum(16); - println!("New password for the dashboard: {}", plain); - password::hash_password_b64(plain).await? + utils::secure_random_alnum(24) }; + println!("New password for the dashboard: {}", pwd_plain); + let password_dashboard = password::hash_password_b64(pwd_plain).await?; + let default_config = default_config(&password_dashboard, args.insecure_cookie)?; fs::write(&path_file, default_config).await?; println!("New default config file created: {}", path_file); @@ -75,6 +90,7 @@ fn default_config(password_dashboard_b64: &str, insecure_cookie: bool) -> Result let secret_api = utils::secure_random_alnum(32); let enc_keys = EncKeys::generate()?; let enc_keys_b64 = enc_keys.keys_as_b64()?; + let enc_keys_trimmed = enc_keys_b64.trim(); let enc_key_active = enc_keys.enc_key_active; Ok(format!( @@ -107,7 +123,7 @@ HQL_NODES=" # The data dir hiqlite will store raft logs and state machine data in. # default: hiqlite_data -HQL_DATA_DIR={} +HQL_DATA_DIR={data_dir} # The file name of the SQLite database in the state machine folder. # default: hiqlite.db @@ -118,6 +134,42 @@ HQL_DATA_DIR={} # default: false #HQL_LOG_STATEMENTS=false +# The size of the pooled connections for local database reads. +# +# Do not confuse this with a pool size for network databases, as it +# is much more efficient. You can't really translate between them, +# because it depends on many things, but assuming a factor of 10 is +# a good start. This means, if you needed a (read) pool size of 40 +# connections for something like a postgres before, you should start +# at a `read_pool_size` of 4. +# +# Keep in mind that this pool is only used for reads and writes will +# travel through the Raft and have their own dedicated connection. +# +# default: 4 +#HQL_READ_POOL_SIZE=4 + +# Enables immediate flush + sync to disk after each Log Store Batch. +# The situations where you would need this are very rare, and you +# should use it with care. +# +# The default is `false`, and a flush + sync will be done in 200ms +# intervals. Even if the application should crash, the OS will take +# care of flushing left-over buffers to disk and no data will get +# lost. If something worse happens, you might lose the last 200ms +# of commits (on that node, not the whole cluster). This is only +# important to know for single instance deployments. HA nodes will +# sync data from other cluster members after a restart anyway. +# +# The only situation where you might want to enable this option is +# when you are on a host that might lose power out of nowhere, and +# it has no backup battery, or when your OS / disk itself is unstable. +# +# `sync_immediate` will greatly reduce the write throughput and put +# a lot more pressure on the disk. If you have lots of writes, it +# can pretty quickly kill your SSD for instance. +#HQL_SYNC_IMMEDIATE=false + # Sets the limit when the Raft will trigger the creation of a new # state machine snapshot and purge all logs that are included in # the snapshot. @@ -140,8 +192,8 @@ HQL_LOGS_UNTIL_SNAPSHOT=10000 # Secrets for Raft internal authentication as well as for the API. # These must be at least 16 characters long and you should provide # different ones for both variables. -HQL_SECRET_RAFT={} -HQL_SECRET_API={} +HQL_SECRET_RAFT={secret_raft} +HQL_SECRET_API={secret_api} # You can either parse `ENC_KEYS` and `ENC_KEY_ACTIVE` from the # environment with setting this value to `env`, or parse them from @@ -155,10 +207,15 @@ HQL_SECRET_API={} # default: "0 30 2 * * * *" #HQL_BACKUP_CRON="0 30 2 * * * *" -# Backups older than the configured days will be cleaned up after -# the backup cron job. +# Backups older than the configured days will be cleaned up on S3 +# after the backup cron job `HQL_BACKUP_CRON`. # default: 30 -#HQL_BACKUP_KEEP_DAYS=30 +HQL_BACKUP_KEEP_DAYS=30 + +# Backups older than the configured days will be cleaned up locally +# after each `Client::backup()` and the cron job `HQL_BACKUP_CRON`. +# default: 3 +HQL_BACKUP_KEEP_DAYS_LOCAL=3 # Access values for the S3 bucket where backups will be pushed to. #HQL_S3_URL=https://s3.example.com @@ -193,27 +250,20 @@ HQL_SECRET_API={} # You can find a utility in the Admin UI to do this for you. # ENC_KEYS=" -{} +{enc_keys_trimmed} " # This identifies the key ID from the `ENC_KEYS` list, that # should actively be used for new encryptions. -ENC_KEY_ACTIVE={} +ENC_KEY_ACTIVE={enc_key_active} # The password for the dashboard as b64 encoded Argon2ID hash -HQL_PASSWORD_DASHBOARD={} +HQL_PASSWORD_DASHBOARD={password_dashboard_b64} # Can be set to `true` during local dev and testing to issue # insecure cookies # default: false -HQL_INSECURE_COOKIE={} +HQL_INSECURE_COOKIE={insecure_cookie} "#, - data_dir, - secret_raft, - secret_api, - enc_keys_b64.trim(), - enc_key_active, - password_dashboard_b64, - insecure_cookie, )) }