Skip to content

Commit 15dca45

Browse files
Make PRAGMA synchronous configurable for a Namespace (#1533)
* Make PRAGMA synchronous configurable for a Namespace SQLite allows setting synchronous values to any four levels. This patch provides an option to set the level for a namespace which will be used for every connection. It defaults to `NORMAL`. allowed values: https://www.sqlite.org/pragma.html#pragma_synchronous e.g. to enable: curl -X POST http://sqld-admin-endpoint/v1/namespaces/<namespace>/config -H "Content-Type: application/json" -d '{"durability_mode": "strong"}' -v * Make `durability_mode` field optional in proto * Set `default` in the enum Co-authored-by: ad hoc <[email protected]> --------- Co-authored-by: ad hoc <[email protected]>
1 parent ebf9e0f commit 15dca45

File tree

5 files changed

+143
-4
lines changed

5 files changed

+143
-4
lines changed

libsql-replication/proto/metadata.proto

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,13 @@ syntax = "proto3";
22

33
package metadata;
44

5+
enum DurabilityMode {
6+
RELAXED = 0;
7+
STRONG = 1;
8+
EXTRA = 2;
9+
OFF = 3;
10+
}
11+
512
// Database config used to send db configs over the wire and stored
613
// in the meta store.
714
message DatabaseConfig {
@@ -12,11 +19,12 @@ message DatabaseConfig {
1219
// maximum db size (in pages)
1320
uint64 max_db_pages = 4;
1421
optional string heartbeat_url = 5;
15-
optional string bottomless_db_id = 6;
22+
optional string bottomless_db_id = 6;
1623
optional string jwt_key = 7;
1724
optional uint64 txn_timeout_s = 8;
1825
bool allow_attach = 9;
1926
optional uint64 max_row_size = 10;
2027
optional bool shared_schema = 11;
2128
optional string shared_schema_name = 12;
29+
optional DurabilityMode durability_mode = 13;
2230
}

libsql-replication/src/generated/metadata.rs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,4 +30,38 @@ pub struct DatabaseConfig {
3030
pub shared_schema: ::core::option::Option<bool>,
3131
#[prost(string, optional, tag = "12")]
3232
pub shared_schema_name: ::core::option::Option<::prost::alloc::string::String>,
33+
#[prost(enumeration = "DurabilityMode", optional, tag = "13")]
34+
pub durability_mode: ::core::option::Option<i32>,
35+
}
36+
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)]
37+
#[repr(i32)]
38+
pub enum DurabilityMode {
39+
Relaxed = 0,
40+
Strong = 1,
41+
Extra = 2,
42+
Off = 3,
43+
}
44+
impl DurabilityMode {
45+
/// String value of the enum field names used in the ProtoBuf definition.
46+
///
47+
/// The values are not transformed in any way and thus are considered stable
48+
/// (if the ProtoBuf definition does not change) and safe for programmatic use.
49+
pub fn as_str_name(&self) -> &'static str {
50+
match self {
51+
DurabilityMode::Relaxed => "RELAXED",
52+
DurabilityMode::Strong => "STRONG",
53+
DurabilityMode::Extra => "EXTRA",
54+
DurabilityMode::Off => "OFF",
55+
}
56+
}
57+
/// Creates an enum from field names used in the ProtoBuf definition.
58+
pub fn from_str_name(value: &str) -> ::core::option::Option<Self> {
59+
match value {
60+
"RELAXED" => Some(Self::Relaxed),
61+
"STRONG" => Some(Self::Strong),
62+
"EXTRA" => Some(Self::Extra),
63+
"OFF" => Some(Self::Off),
64+
_ => None,
65+
}
66+
}
3367
}

libsql-server/src/connection/config.rs

Lines changed: 87 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
11
use crate::{namespace::NamespaceName, LIBSQL_PAGE_SIZE};
22
use bytesize::mb;
3+
use rusqlite::types::ToSqlOutput;
4+
use rusqlite::ToSql;
5+
use serde::{Deserialize, Serialize};
6+
use std::fmt::Display;
7+
use std::str::FromStr;
38
use url::Url;
49

10+
use super::TXN_TIMEOUT;
511
use libsql_replication::rpc::metadata;
612
use tokio::time::Duration;
713

8-
use super::TXN_TIMEOUT;
9-
1014
#[derive(Debug, Clone, serde::Deserialize)]
1115
pub struct DatabaseConfig {
1216
pub block_reads: bool,
@@ -29,6 +33,8 @@ pub struct DatabaseConfig {
2933
pub is_shared_schema: bool,
3034
#[serde(default)]
3135
pub shared_schema_name: Option<NamespaceName>,
36+
#[serde(default)]
37+
pub durability_mode: DurabilityMode,
3238
}
3339

3440
const fn default_max_size() -> u64 {
@@ -54,6 +60,7 @@ impl Default for DatabaseConfig {
5460
max_row_size: default_max_row_size(),
5561
is_shared_schema: false,
5662
shared_schema_name: None,
63+
durability_mode: DurabilityMode::default(),
5764
}
5865
}
5966
}
@@ -77,6 +84,10 @@ impl From<&metadata::DatabaseConfig> for DatabaseConfig {
7784
.shared_schema_name
7885
.clone()
7986
.map(NamespaceName::new_unchecked),
87+
durability_mode: match value.durability_mode {
88+
None => DurabilityMode::default(),
89+
Some(m) => DurabilityMode::from(metadata::DurabilityMode::try_from(m)),
90+
},
8091
}
8192
}
8293
}
@@ -96,6 +107,80 @@ impl From<&DatabaseConfig> for metadata::DatabaseConfig {
96107
max_row_size: Some(value.max_row_size),
97108
shared_schema: Some(value.is_shared_schema),
98109
shared_schema_name: value.shared_schema_name.as_ref().map(|s| s.to_string()),
110+
durability_mode: Some(metadata::DurabilityMode::from(value.durability_mode).into()),
111+
}
112+
}
113+
}
114+
115+
/// Durability mode specifies the `PRAGMA SYNCHRONOUS` setting for the connection
116+
#[derive(PartialEq, Clone, Copy, Debug, Deserialize, Serialize, Default)]
117+
#[serde(rename_all = "lowercase")]
118+
pub enum DurabilityMode {
119+
Extra,
120+
Strong,
121+
#[default]
122+
Relaxed,
123+
Off,
124+
}
125+
126+
impl ToSql for DurabilityMode {
127+
fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
128+
match self {
129+
DurabilityMode::Extra => Ok(ToSqlOutput::from("extra")),
130+
DurabilityMode::Strong => Ok(ToSqlOutput::from("full")),
131+
DurabilityMode::Relaxed => Ok(ToSqlOutput::from("normal")),
132+
DurabilityMode::Off => Ok(ToSqlOutput::from("off")),
133+
}
134+
}
135+
}
136+
137+
impl FromStr for DurabilityMode {
138+
type Err = ();
139+
140+
fn from_str(input: &str) -> Result<DurabilityMode, Self::Err> {
141+
match input {
142+
"extra" => Ok(DurabilityMode::Extra),
143+
"strong" => Ok(DurabilityMode::Strong),
144+
"relaxed" => Ok(DurabilityMode::Relaxed),
145+
"off" => Ok(DurabilityMode::Off),
146+
_ => Err(()),
147+
}
148+
}
149+
}
150+
151+
impl Display for DurabilityMode {
152+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
153+
let m = match self {
154+
DurabilityMode::Extra => "extra",
155+
DurabilityMode::Strong => "strong",
156+
DurabilityMode::Relaxed => "relaxed",
157+
DurabilityMode::Off => "off",
158+
};
159+
write!(f, "{m}")
160+
}
161+
}
162+
163+
impl From<DurabilityMode> for metadata::DurabilityMode {
164+
fn from(value: DurabilityMode) -> Self {
165+
match value {
166+
DurabilityMode::Relaxed => metadata::DurabilityMode::Relaxed,
167+
DurabilityMode::Strong => metadata::DurabilityMode::Strong,
168+
DurabilityMode::Extra => metadata::DurabilityMode::Extra,
169+
DurabilityMode::Off => metadata::DurabilityMode::Off,
170+
}
171+
}
172+
}
173+
174+
impl From<Result<metadata::DurabilityMode, prost::DecodeError>> for DurabilityMode {
175+
fn from(value: Result<metadata::DurabilityMode, prost::DecodeError>) -> Self {
176+
match value {
177+
Ok(mode) => match mode {
178+
metadata::DurabilityMode::Relaxed => DurabilityMode::Relaxed,
179+
metadata::DurabilityMode::Strong => DurabilityMode::Strong,
180+
metadata::DurabilityMode::Extra => DurabilityMode::Extra,
181+
metadata::DurabilityMode::Off => DurabilityMode::Off,
182+
},
183+
Err(_) => DurabilityMode::default(),
99184
}
100185
}
101186
}

libsql-server/src/connection/libsql.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -455,6 +455,9 @@ impl<W: Wal> Connection<W> {
455455

456456
let config = config_store.get();
457457
conn.pragma_update(None, "max_page_count", config.max_db_pages)?;
458+
tracing::info!("setting PRAGMA synchronous to {}", config.durability_mode);
459+
conn.pragma_update(None, "synchronous", config.durability_mode)?;
460+
458461
conn.set_limit(
459462
rusqlite::limits::Limit::SQLITE_LIMIT_LENGTH,
460463
config.max_row_size as i32,

libsql-server/src/http/admin/mod.rs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ use tower_http::trace::DefaultOnResponse;
2222
use url::Url;
2323

2424
use crate::auth::parse_jwt_keys;
25-
use crate::connection::config::DatabaseConfig;
25+
use crate::connection::config::{DatabaseConfig, DurabilityMode};
2626
use crate::error::{Error, LoadDumpError};
2727
use crate::hrana;
2828
use crate::namespace::{DumpStream, NamespaceName, NamespaceStore, RestoreOption};
@@ -197,6 +197,7 @@ async fn handle_get_config<C: Connector>(
197197
jwt_key: config.jwt_key.clone(),
198198
allow_attach: config.allow_attach,
199199
txn_timeout_s: config.txn_timeout.map(|d| d.as_secs() as u64),
200+
durability_mode: Some(config.durability_mode),
200201
};
201202
Ok(Json(resp))
202203
}
@@ -244,6 +245,8 @@ struct HttpDatabaseConfig {
244245
allow_attach: bool,
245246
#[serde(default)]
246247
txn_timeout_s: Option<u64>,
248+
#[serde(default)]
249+
durability_mode: Option<DurabilityMode>,
247250
}
248251

249252
async fn handle_post_config<C>(
@@ -272,6 +275,9 @@ async fn handle_post_config<C>(
272275
config.heartbeat_url = Some(Url::parse(&url)?);
273276
}
274277
config.jwt_key = req.jwt_key;
278+
if let Some(mode) = req.durability_mode {
279+
config.durability_mode = mode;
280+
}
275281

276282
store.store(config).await?;
277283

@@ -295,6 +301,8 @@ struct CreateNamespaceReq {
295301
shared_schema_name: Option<NamespaceName>,
296302
#[serde(default)]
297303
allow_attach: bool,
304+
#[serde(default)]
305+
durability_mode: Option<DurabilityMode>,
298306
}
299307

300308
async fn handle_create_namespace<C: Connector>(
@@ -346,6 +354,7 @@ async fn handle_create_namespace<C: Connector>(
346354
if let Some(max_db_size) = req.max_db_size {
347355
config.max_db_pages = max_db_size.as_u64() / LIBSQL_PAGE_SIZE;
348356
}
357+
config.durability_mode = req.durability_mode.unwrap_or(DurabilityMode::default());
349358

350359
app_state.namespaces.create(namespace, dump, config).await?;
351360

0 commit comments

Comments
 (0)