Compare commits
1 commit
main
...
feat/lmdb-
Author | SHA1 | Date | |
---|---|---|---|
45425a5637 |
11 changed files with 70 additions and 71 deletions
|
@ -23,7 +23,7 @@ client = minio.Minio(
|
||||||
"GKyourapikey",
|
"GKyourapikey",
|
||||||
"abcd[...]1234",
|
"abcd[...]1234",
|
||||||
# Force the region, this is specific to garage
|
# Force the region, this is specific to garage
|
||||||
region="garage",
|
region="region",
|
||||||
)
|
)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
@ -335,7 +335,6 @@ From the [official Mastodon documentation](https://docs.joinmastodon.org/admin/t
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ RAILS_ENV=production bin/tootctl media remove --days 3
|
$ RAILS_ENV=production bin/tootctl media remove --days 3
|
||||||
$ RAILS_ENV=production bin/tootctl media remove --days 15 --prune-profiles
|
|
||||||
$ RAILS_ENV=production bin/tootctl media remove-orphans
|
$ RAILS_ENV=production bin/tootctl media remove-orphans
|
||||||
$ RAILS_ENV=production bin/tootctl preview_cards remove --days 15
|
$ RAILS_ENV=production bin/tootctl preview_cards remove --days 15
|
||||||
```
|
```
|
||||||
|
@ -354,6 +353,8 @@ Imports: 1.7 KB
|
||||||
Settings: 0 Bytes
|
Settings: 0 Bytes
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Unfortunately, [old avatars and headers cannot currently be cleaned up](https://github.com/mastodon/mastodon/issues/9567).
|
||||||
|
|
||||||
### Migrating your data
|
### Migrating your data
|
||||||
|
|
||||||
Data migration should be done with an efficient S3 client.
|
Data migration should be done with an efficient S3 client.
|
||||||
|
|
|
@ -50,20 +50,3 @@ locations. They use Garage themselves for the following tasks:
|
||||||
|
|
||||||
The Deuxfleurs Garage cluster is a multi-site cluster currently composed of
|
The Deuxfleurs Garage cluster is a multi-site cluster currently composed of
|
||||||
9 nodes in 3 physical locations.
|
9 nodes in 3 physical locations.
|
||||||
|
|
||||||
### Triplebit
|
|
||||||
|
|
||||||
[Triplebit](https://www.triplebit.org) is a non-profit hosting provider and
|
|
||||||
ISP focused on improving access to privacy-related services. They use
|
|
||||||
Garage themselves for the following tasks:
|
|
||||||
|
|
||||||
- Hosting of their homepage, [privacyguides.org](https://www.privacyguides.org/), and various other static sites
|
|
||||||
|
|
||||||
- As a Mastodon object storage backend for [mstdn.party](https://mstdn.party/) and [mstdn.plus](https://mstdn.plus/)
|
|
||||||
|
|
||||||
- As a PeerTube storage backend for [neat.tube](https://neat.tube/)
|
|
||||||
|
|
||||||
- As a [Matrix media backend](https://github.com/matrix-org/synapse-s3-storage-provider)
|
|
||||||
|
|
||||||
Triplebit's Garage cluster is a multi-site cluster currently composed of
|
|
||||||
10 nodes in 3 physical locations.
|
|
||||||
|
|
|
@ -16,7 +16,6 @@ data_dir = "/var/lib/garage/data"
|
||||||
metadata_fsync = true
|
metadata_fsync = true
|
||||||
data_fsync = false
|
data_fsync = false
|
||||||
disable_scrub = false
|
disable_scrub = false
|
||||||
use_local_tz = false
|
|
||||||
metadata_auto_snapshot_interval = "6h"
|
metadata_auto_snapshot_interval = "6h"
|
||||||
|
|
||||||
db_engine = "lmdb"
|
db_engine = "lmdb"
|
||||||
|
@ -100,7 +99,6 @@ Top-level configuration options:
|
||||||
[`data_fsync`](#data_fsync),
|
[`data_fsync`](#data_fsync),
|
||||||
[`db_engine`](#db_engine),
|
[`db_engine`](#db_engine),
|
||||||
[`disable_scrub`](#disable_scrub),
|
[`disable_scrub`](#disable_scrub),
|
||||||
[`use_local_tz`](#use_local_tz),
|
|
||||||
[`lmdb_map_size`](#lmdb_map_size),
|
[`lmdb_map_size`](#lmdb_map_size),
|
||||||
[`metadata_auto_snapshot_interval`](#metadata_auto_snapshot_interval),
|
[`metadata_auto_snapshot_interval`](#metadata_auto_snapshot_interval),
|
||||||
[`metadata_dir`](#metadata_dir),
|
[`metadata_dir`](#metadata_dir),
|
||||||
|
@ -429,13 +427,6 @@ you should delete it from the data directory and then call `garage repair
|
||||||
blocks` on the node to ensure that it re-obtains a copy from another node on
|
blocks` on the node to ensure that it re-obtains a copy from another node on
|
||||||
the network.
|
the network.
|
||||||
|
|
||||||
#### `use_local_tz` {#use_local_tz}
|
|
||||||
|
|
||||||
By default, Garage runs the lifecycle worker every day at midnight in UTC. Set the
|
|
||||||
`use_local_tz` configuration value to `true` if you want Garage to run the
|
|
||||||
lifecycle worker at midnight in your local timezone. If you have multiple nodes,
|
|
||||||
you should also ensure that each node has the same timezone configuration.
|
|
||||||
|
|
||||||
#### `block_size` {#block_size}
|
#### `block_size` {#block_size}
|
||||||
|
|
||||||
Garage splits stored objects in consecutive chunks of size `block_size`
|
Garage splits stored objects in consecutive chunks of size `block_size`
|
||||||
|
|
|
@ -76,9 +76,6 @@ spec:
|
||||||
- name: etc
|
- name: etc
|
||||||
mountPath: /etc/garage.toml
|
mountPath: /etc/garage.toml
|
||||||
subPath: garage.toml
|
subPath: garage.toml
|
||||||
{{- with .Values.extraVolumeMounts }}
|
|
||||||
{{- toYaml . | nindent 12 }}
|
|
||||||
{{- end }}
|
|
||||||
# TODO
|
# TODO
|
||||||
# livenessProbe:
|
# livenessProbe:
|
||||||
# httpGet:
|
# httpGet:
|
||||||
|
@ -113,9 +110,6 @@ spec:
|
||||||
- name: data
|
- name: data
|
||||||
emptyDir: {}
|
emptyDir: {}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
{{- with .Values.extraVolumes }}
|
|
||||||
{{- toYaml . | nindent 8 }}
|
|
||||||
{{- end }}
|
|
||||||
{{- with .Values.nodeSelector }}
|
{{- with .Values.nodeSelector }}
|
||||||
nodeSelector:
|
nodeSelector:
|
||||||
{{- toYaml . | nindent 8 }}
|
{{- toYaml . | nindent 8 }}
|
||||||
|
|
|
@ -218,10 +218,6 @@ affinity: {}
|
||||||
|
|
||||||
environment: {}
|
environment: {}
|
||||||
|
|
||||||
extraVolumes: {}
|
|
||||||
|
|
||||||
extraVolumeMounts: {}
|
|
||||||
|
|
||||||
monitoring:
|
monitoring:
|
||||||
metrics:
|
metrics:
|
||||||
# If true, a service for monitoring is created with a prometheus.io/scrape annotation
|
# If true, a service for monitoring is created with a prometheus.io/scrape annotation
|
||||||
|
|
|
@ -15,6 +15,9 @@ path = "lib.rs"
|
||||||
err-derive.workspace = true
|
err-derive.workspace = true
|
||||||
hexdump.workspace = true
|
hexdump.workspace = true
|
||||||
tracing.workspace = true
|
tracing.workspace = true
|
||||||
|
opentelemetry.workspace = true
|
||||||
|
opentelemetry.workspace = true
|
||||||
|
xxhash-rust.workspace = true
|
||||||
|
|
||||||
heed = { workspace = true, optional = true }
|
heed = { workspace = true, optional = true }
|
||||||
rusqlite = { workspace = true, optional = true, features = ["backup"] }
|
rusqlite = { workspace = true, optional = true, features = ["backup"] }
|
||||||
|
|
|
@ -10,6 +10,8 @@ use std::sync::{Arc, RwLock};
|
||||||
use heed::types::ByteSlice;
|
use heed::types::ByteSlice;
|
||||||
use heed::{BytesDecode, Env, RoTxn, RwTxn, UntypedDatabase as Database};
|
use heed::{BytesDecode, Env, RoTxn, RwTxn, UntypedDatabase as Database};
|
||||||
|
|
||||||
|
use xxhash_rust::xxh3::xxh3_128;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
Db, Error, IDb, ITx, ITxFn, OnCommit, Result, TxError, TxFnResult, TxOpError, TxOpResult,
|
Db, Error, IDb, ITx, ITxFn, OnCommit, Result, TxError, TxFnResult, TxOpError, TxOpResult,
|
||||||
TxResult, TxValueIter, Value, ValueIter,
|
TxResult, TxValueIter, Value, ValueIter,
|
||||||
|
@ -58,6 +60,40 @@ impl LmdbDb {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn key_hash(key: &[u8]) -> [u8; 16] {
|
||||||
|
xxh3_128(key).to_ne_bytes()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn kv_to_value(key: &[u8], value: &[u8]) -> Vec<u8> {
|
||||||
|
[&key.len().to_ne_bytes(), key, value].concat()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn value_to_kv(value: &[u8]) -> (Vec<u8>, Vec<u8>) {
|
||||||
|
const USIZE_LEN: usize = std::mem::size_of::<usize>();
|
||||||
|
let klen = usize::from_ne_bytes(value[0..USIZE_LEN].try_into().unwrap());
|
||||||
|
(
|
||||||
|
value[USIZE_LEN..klen+USIZE_LEN].to_vec(),
|
||||||
|
value[USIZE_LEN+klen..].to_vec()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn key_hash(key: &[u8]) -> [u8; 16] {
|
||||||
|
xxh3_128(key).to_ne_bytes()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn kv_to_value(key: &[u8], value: &[u8]) -> Vec<u8> {
|
||||||
|
[&key.len().to_ne_bytes(), key, value].concat()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn value_to_kv(value: &[u8]) -> (Vec<u8>, Vec<u8>) {
|
||||||
|
const USIZE_LEN: usize = std::mem::size_of::<usize>();
|
||||||
|
let klen = usize::from_ne_bytes(value[0..USIZE_LEN].try_into().unwrap());
|
||||||
|
(
|
||||||
|
value[USIZE_LEN..klen+USIZE_LEN].to_vec(),
|
||||||
|
value[USIZE_LEN+klen..].to_vec()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
impl IDb for LmdbDb {
|
impl IDb for LmdbDb {
|
||||||
fn engine(&self) -> String {
|
fn engine(&self) -> String {
|
||||||
"LMDB (using Heed crate)".into()
|
"LMDB (using Heed crate)".into()
|
||||||
|
@ -119,10 +155,11 @@ impl IDb for LmdbDb {
|
||||||
let tree = self.get_tree(tree)?;
|
let tree = self.get_tree(tree)?;
|
||||||
|
|
||||||
let tx = self.db.read_txn()?;
|
let tx = self.db.read_txn()?;
|
||||||
let val = tree.get(&tx, key)?;
|
let kh = key_hash(key);
|
||||||
|
let val = tree.get(&tx, &kh)?;
|
||||||
match val {
|
match val {
|
||||||
None => Ok(None),
|
None => Ok(None),
|
||||||
Some(v) => Ok(Some(v.to_vec())),
|
Some(v) => Ok(Some(value_to_kv(v).1))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -135,7 +172,9 @@ impl IDb for LmdbDb {
|
||||||
fn insert(&self, tree: usize, key: &[u8], value: &[u8]) -> Result<()> {
|
fn insert(&self, tree: usize, key: &[u8], value: &[u8]) -> Result<()> {
|
||||||
let tree = self.get_tree(tree)?;
|
let tree = self.get_tree(tree)?;
|
||||||
let mut tx = self.db.write_txn()?;
|
let mut tx = self.db.write_txn()?;
|
||||||
tree.put(&mut tx, key, value)?;
|
let kh = key_hash(key);
|
||||||
|
let value = kv_to_value(key, value);
|
||||||
|
tree.put(&mut tx, &kh, &value)?;
|
||||||
tx.commit()?;
|
tx.commit()?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -143,7 +182,8 @@ impl IDb for LmdbDb {
|
||||||
fn remove(&self, tree: usize, key: &[u8]) -> Result<()> {
|
fn remove(&self, tree: usize, key: &[u8]) -> Result<()> {
|
||||||
let tree = self.get_tree(tree)?;
|
let tree = self.get_tree(tree)?;
|
||||||
let mut tx = self.db.write_txn()?;
|
let mut tx = self.db.write_txn()?;
|
||||||
tree.delete(&mut tx, key)?;
|
let kh = key_hash(key);
|
||||||
|
tree.delete(&mut tx, &kh)?;
|
||||||
tx.commit()?;
|
tx.commit()?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -242,8 +282,9 @@ impl<'a> LmdbTx<'a> {
|
||||||
impl<'a> ITx for LmdbTx<'a> {
|
impl<'a> ITx for LmdbTx<'a> {
|
||||||
fn get(&self, tree: usize, key: &[u8]) -> TxOpResult<Option<Value>> {
|
fn get(&self, tree: usize, key: &[u8]) -> TxOpResult<Option<Value>> {
|
||||||
let tree = self.get_tree(tree)?;
|
let tree = self.get_tree(tree)?;
|
||||||
match tree.get(&self.tx, key)? {
|
let kh = key_hash(key);
|
||||||
Some(v) => Ok(Some(v.to_vec())),
|
match tree.get(&self.tx, &kh)? {
|
||||||
|
Some(v) => Ok(Some(value_to_kv(v).1)),
|
||||||
None => Ok(None),
|
None => Ok(None),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -254,14 +295,18 @@ impl<'a> ITx for LmdbTx<'a> {
|
||||||
|
|
||||||
fn insert(&mut self, tree: usize, key: &[u8], value: &[u8]) -> TxOpResult<()> {
|
fn insert(&mut self, tree: usize, key: &[u8], value: &[u8]) -> TxOpResult<()> {
|
||||||
let tree = *self.get_tree(tree)?;
|
let tree = *self.get_tree(tree)?;
|
||||||
tree.put(&mut self.tx, key, value)?;
|
let kh = key_hash(key);
|
||||||
|
let value = kv_to_value(key, value);
|
||||||
|
tree.put(&mut self.tx, &kh, &value)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
fn remove(&mut self, tree: usize, key: &[u8]) -> TxOpResult<()> {
|
fn remove(&mut self, tree: usize, key: &[u8]) -> TxOpResult<()> {
|
||||||
let tree = *self.get_tree(tree)?;
|
let tree = *self.get_tree(tree)?;
|
||||||
tree.delete(&mut self.tx, key)?;
|
let kh = key_hash(key);
|
||||||
|
tree.delete(&mut self.tx, &kh)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn clear(&mut self, tree: usize) -> TxOpResult<()> {
|
fn clear(&mut self, tree: usize) -> TxOpResult<()> {
|
||||||
let tree = *self.get_tree(tree)?;
|
let tree = *self.get_tree(tree)?;
|
||||||
tree.clear(&mut self.tx)?;
|
tree.clear(&mut self.tx)?;
|
||||||
|
@ -370,7 +415,7 @@ where
|
||||||
match next {
|
match next {
|
||||||
None => None,
|
None => None,
|
||||||
Some(Err(e)) => Some(Err(e.into())),
|
Some(Err(e)) => Some(Err(e.into())),
|
||||||
Some(Ok((k, v))) => Some(Ok((k.to_vec(), v.to_vec()))),
|
Some(Ok((_k, v))) => Some(Ok(value_to_kv(v))),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -380,7 +425,7 @@ where
|
||||||
fn tx_iter_item<'a>(
|
fn tx_iter_item<'a>(
|
||||||
item: std::result::Result<(&'a [u8], &'a [u8]), heed::Error>,
|
item: std::result::Result<(&'a [u8], &'a [u8]), heed::Error>,
|
||||||
) -> TxOpResult<(Vec<u8>, Vec<u8>)> {
|
) -> TxOpResult<(Vec<u8>, Vec<u8>)> {
|
||||||
item.map(|(k, v)| (k.to_vec(), v.to_vec()))
|
item.map(|(_k, v)| value_to_kv(v))
|
||||||
.map_err(|e| TxOpError(Error::from(e)))
|
.map_err(|e| TxOpError(Error::from(e)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -141,7 +141,7 @@ impl Garage {
|
||||||
)?)
|
)?)
|
||||||
.ok()
|
.ok()
|
||||||
.and_then(|x| NetworkKey::from_slice(&x))
|
.and_then(|x| NetworkKey::from_slice(&x))
|
||||||
.ok_or_message("Invalid RPC secret key: expected 32 bytes of random hex, please check the documentation for requirements")?;
|
.ok_or_message("Invalid RPC secret key: expected 32 bits of entropy, please check the documentation for requirements")?;
|
||||||
|
|
||||||
let (replication_factor, consistency_mode) = parse_replication_mode(&config)?;
|
let (replication_factor, consistency_mode) = parse_replication_mode(&config)?;
|
||||||
|
|
||||||
|
|
|
@ -70,7 +70,7 @@ pub fn register_bg_vars(
|
||||||
|
|
||||||
impl LifecycleWorker {
|
impl LifecycleWorker {
|
||||||
pub fn new(garage: Arc<Garage>, persister: PersisterShared<LifecycleWorkerPersisted>) -> Self {
|
pub fn new(garage: Arc<Garage>, persister: PersisterShared<LifecycleWorkerPersisted>) -> Self {
|
||||||
let today = today(garage.config.use_local_tz);
|
let today = today();
|
||||||
let last_completed = persister.get_with(|x| {
|
let last_completed = persister.get_with(|x| {
|
||||||
x.last_completed
|
x.last_completed
|
||||||
.as_deref()
|
.as_deref()
|
||||||
|
@ -205,9 +205,8 @@ impl Worker for LifecycleWorker {
|
||||||
async fn wait_for_work(&mut self) -> WorkerState {
|
async fn wait_for_work(&mut self) -> WorkerState {
|
||||||
match &self.state {
|
match &self.state {
|
||||||
State::Completed(d) => {
|
State::Completed(d) => {
|
||||||
let use_local_tz = self.garage.config.use_local_tz;
|
|
||||||
let next_day = d.succ_opt().expect("no next day");
|
let next_day = d.succ_opt().expect("no next day");
|
||||||
let next_start = midnight_ts(next_day, use_local_tz);
|
let next_start = midnight_ts(next_day);
|
||||||
loop {
|
loop {
|
||||||
let now = now_msec();
|
let now = now_msec();
|
||||||
if now < next_start {
|
if now < next_start {
|
||||||
|
@ -219,7 +218,7 @@ impl Worker for LifecycleWorker {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.state = State::start(std::cmp::max(next_day, today(use_local_tz)));
|
self.state = State::start(std::cmp::max(next_day, today()));
|
||||||
}
|
}
|
||||||
State::Running { .. } => (),
|
State::Running { .. } => (),
|
||||||
}
|
}
|
||||||
|
@ -386,16 +385,10 @@ fn check_size_filter(version_data: &ObjectVersionData, filter: &LifecycleFilter)
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
fn midnight_ts(date: NaiveDate, use_local_tz: bool) -> u64 {
|
fn midnight_ts(date: NaiveDate) -> u64 {
|
||||||
let midnight = date.and_hms_opt(0, 0, 0).expect("midnight does not exist");
|
date.and_hms_opt(0, 0, 0)
|
||||||
if use_local_tz {
|
.expect("midnight does not exist")
|
||||||
return midnight
|
.timestamp_millis() as u64
|
||||||
.and_local_timezone(Local)
|
|
||||||
.single()
|
|
||||||
.expect("bad local midnight")
|
|
||||||
.timestamp_millis() as u64;
|
|
||||||
}
|
|
||||||
midnight.timestamp_millis() as u64
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn next_date(ts: u64) -> NaiveDate {
|
fn next_date(ts: u64) -> NaiveDate {
|
||||||
|
@ -406,9 +399,6 @@ fn next_date(ts: u64) -> NaiveDate {
|
||||||
.expect("no next day")
|
.expect("no next day")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn today(use_local_tz: bool) -> NaiveDate {
|
fn today() -> NaiveDate {
|
||||||
if use_local_tz {
|
|
||||||
return Local::now().naive_local().date();
|
|
||||||
}
|
|
||||||
Utc::now().naive_utc().date()
|
Utc::now().naive_utc().date()
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,10 +27,6 @@ pub struct Config {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub disable_scrub: bool,
|
pub disable_scrub: bool,
|
||||||
|
|
||||||
/// Use local timezone
|
|
||||||
#[serde(default)]
|
|
||||||
pub use_local_tz: bool,
|
|
||||||
|
|
||||||
/// Automatic snapshot interval for metadata
|
/// Automatic snapshot interval for metadata
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub metadata_auto_snapshot_interval: Option<String>,
|
pub metadata_auto_snapshot_interval: Option<String>,
|
||||||
|
|
Loading…
Reference in a new issue