Compare commits

..

1 Commits

Author SHA1 Message Date
Alex Auvolat 8bccddb504 add garage node dump-table command for debugging purpose 2025-08-29 16:35:47 +02:00
37 changed files with 240 additions and 241 deletions
-3
View File
@@ -1,6 +1,3 @@
labels:
nix: "enabled"
when: when:
event: event:
- push - push
-3
View File
@@ -1,6 +1,3 @@
labels:
nix: "enabled"
when: when:
event: event:
- deployment - deployment
-3
View File
@@ -1,6 +1,3 @@
labels:
nix: "enabled"
when: when:
event: event:
- deployment - deployment
Generated
+23 -22
View File
@@ -1280,7 +1280,7 @@ dependencies = [
[[package]] [[package]]
name = "garage" name = "garage"
version = "1.3.0" version = "1.2.0"
dependencies = [ dependencies = [
"assert-json-diff", "assert-json-diff",
"async-trait", "async-trait",
@@ -1321,6 +1321,7 @@ dependencies = [
"opentelemetry-prometheus", "opentelemetry-prometheus",
"parse_duration", "parse_duration",
"serde", "serde",
"serde_bytes",
"serde_json", "serde_json",
"sha1", "sha1",
"sha2", "sha2",
@@ -1336,7 +1337,7 @@ dependencies = [
[[package]] [[package]]
name = "garage_api_admin" name = "garage_api_admin"
version = "1.3.0" version = "1.2.0"
dependencies = [ dependencies = [
"argon2", "argon2",
"async-trait", "async-trait",
@@ -1362,7 +1363,7 @@ dependencies = [
[[package]] [[package]]
name = "garage_api_common" name = "garage_api_common"
version = "1.3.0" version = "1.2.0"
dependencies = [ dependencies = [
"base64 0.21.7", "base64 0.21.7",
"bytes", "bytes",
@@ -1396,7 +1397,7 @@ dependencies = [
[[package]] [[package]]
name = "garage_api_k2v" name = "garage_api_k2v"
version = "1.3.0" version = "1.2.0"
dependencies = [ dependencies = [
"base64 0.21.7", "base64 0.21.7",
"err-derive", "err-derive",
@@ -1419,7 +1420,7 @@ dependencies = [
[[package]] [[package]]
name = "garage_api_s3" name = "garage_api_s3"
version = "1.3.0" version = "1.2.0"
dependencies = [ dependencies = [
"aes-gcm", "aes-gcm",
"async-compression", "async-compression",
@@ -1464,7 +1465,7 @@ dependencies = [
[[package]] [[package]]
name = "garage_block" name = "garage_block"
version = "1.3.0" version = "1.2.0"
dependencies = [ dependencies = [
"arc-swap", "arc-swap",
"async-compression", "async-compression",
@@ -1489,7 +1490,7 @@ dependencies = [
[[package]] [[package]]
name = "garage_db" name = "garage_db"
version = "1.3.0" version = "1.2.0"
dependencies = [ dependencies = [
"err-derive", "err-derive",
"fjall", "fjall",
@@ -1504,7 +1505,7 @@ dependencies = [
[[package]] [[package]]
name = "garage_model" name = "garage_model"
version = "1.3.0" version = "1.2.0"
dependencies = [ dependencies = [
"async-trait", "async-trait",
"base64 0.21.7", "base64 0.21.7",
@@ -1531,7 +1532,7 @@ dependencies = [
[[package]] [[package]]
name = "garage_net" name = "garage_net"
version = "1.3.0" version = "1.2.0"
dependencies = [ dependencies = [
"arc-swap", "arc-swap",
"bytes", "bytes",
@@ -1556,7 +1557,7 @@ dependencies = [
[[package]] [[package]]
name = "garage_rpc" name = "garage_rpc"
version = "1.3.0" version = "1.2.0"
dependencies = [ dependencies = [
"arc-swap", "arc-swap",
"async-trait", "async-trait",
@@ -1588,7 +1589,7 @@ dependencies = [
[[package]] [[package]]
name = "garage_table" name = "garage_table"
version = "1.3.0" version = "1.2.0"
dependencies = [ dependencies = [
"arc-swap", "arc-swap",
"async-trait", "async-trait",
@@ -1609,7 +1610,7 @@ dependencies = [
[[package]] [[package]]
name = "garage_util" name = "garage_util"
version = "1.3.0" version = "1.2.0"
dependencies = [ dependencies = [
"arc-swap", "arc-swap",
"async-trait", "async-trait",
@@ -1641,7 +1642,7 @@ dependencies = [
[[package]] [[package]]
name = "garage_web" name = "garage_web"
version = "1.3.0" version = "1.2.0"
dependencies = [ dependencies = [
"err-derive", "err-derive",
"garage_api_common", "garage_api_common",
@@ -1810,11 +1811,11 @@ dependencies = [
[[package]] [[package]]
name = "hashlink" name = "hashlink"
version = "0.10.0" version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" checksum = "6ba4ff7128dee98c7dc9794b6a411377e1404dba1c97deb8d1a55297bd25d8af"
dependencies = [ dependencies = [
"hashbrown 0.15.2", "hashbrown 0.14.5",
] ]
[[package]] [[package]]
@@ -2619,9 +2620,9 @@ dependencies = [
[[package]] [[package]]
name = "libsqlite3-sys" name = "libsqlite3-sys"
version = "0.35.0" version = "0.28.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "133c182a6a2c87864fe97778797e46c7e999672690dc9fa3ee8e241aa4a9c13f" checksum = "0c10584274047cb335c23d3e61bcef8e323adae7c5c8c760540f73610177fc3f"
dependencies = [ dependencies = [
"cc", "cc",
"pkg-config", "pkg-config",
@@ -3478,9 +3479,9 @@ dependencies = [
[[package]] [[package]]
name = "r2d2_sqlite" name = "r2d2_sqlite"
version = "0.31.0" version = "0.24.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "63417e83dc891797eea3ad379f52a5986da4bca0d6ef28baf4d14034dd111b0c" checksum = "6a982edf65c129796dba72f8775b292ef482b40d035e827a9825b3bc07ccc5f2"
dependencies = [ dependencies = [
"r2d2", "r2d2",
"rusqlite", "rusqlite",
@@ -3669,9 +3670,9 @@ checksum = "3cd14fd5e3b777a7422cca79358c57a8f6e3a703d9ac187448d0daf220c2407f"
[[package]] [[package]]
name = "rusqlite" name = "rusqlite"
version = "0.37.0" version = "0.31.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "165ca6e57b20e1351573e3729b958bc62f0e48025386970b6e4d29e7a7e71f3f" checksum = "b838eba278d213a8beaf485bd313fd580ca4505a00d5871caeb1457c55322cae"
dependencies = [ dependencies = [
"bitflags 2.9.0", "bitflags 2.9.0",
"fallible-iterator", "fallible-iterator",
+14 -14
View File
@@ -24,18 +24,18 @@ default-members = ["src/garage"]
# Internal Garage crates # Internal Garage crates
format_table = { version = "0.1.1", path = "src/format-table" } format_table = { version = "0.1.1", path = "src/format-table" }
garage_api_common = { version = "1.3.0", path = "src/api/common" } garage_api_common = { version = "1.2.0", path = "src/api/common" }
garage_api_admin = { version = "1.3.0", path = "src/api/admin" } garage_api_admin = { version = "1.2.0", path = "src/api/admin" }
garage_api_s3 = { version = "1.3.0", path = "src/api/s3" } garage_api_s3 = { version = "1.2.0", path = "src/api/s3" }
garage_api_k2v = { version = "1.3.0", path = "src/api/k2v" } garage_api_k2v = { version = "1.2.0", path = "src/api/k2v" }
garage_block = { version = "1.3.0", path = "src/block" } garage_block = { version = "1.2.0", path = "src/block" }
garage_db = { version = "1.3.0", path = "src/db", default-features = false } garage_db = { version = "1.2.0", path = "src/db", default-features = false }
garage_model = { version = "1.3.0", path = "src/model", default-features = false } garage_model = { version = "1.2.0", path = "src/model", default-features = false }
garage_net = { version = "1.3.0", path = "src/net" } garage_net = { version = "1.2.0", path = "src/net" }
garage_rpc = { version = "1.3.0", path = "src/rpc" } garage_rpc = { version = "1.2.0", path = "src/rpc" }
garage_table = { version = "1.3.0", path = "src/table" } garage_table = { version = "1.2.0", path = "src/table" }
garage_util = { version = "1.3.0", path = "src/util" } garage_util = { version = "1.2.0", path = "src/util" }
garage_web = { version = "1.3.0", path = "src/web" } garage_web = { version = "1.2.0", path = "src/web" }
k2v-client = { version = "0.0.4", path = "src/k2v-client" } k2v-client = { version = "0.0.4", path = "src/k2v-client" }
# External crates from crates.io # External crates from crates.io
@@ -88,9 +88,9 @@ tracing-journald = "0.3.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] } tracing-subscriber = { version = "0.3", features = ["env-filter"] }
heed = { version = "0.11", default-features = false, features = ["lmdb"] } heed = { version = "0.11", default-features = false, features = ["lmdb"] }
rusqlite = "0.37" rusqlite = "0.31.0"
r2d2 = "0.8" r2d2 = "0.8"
r2d2_sqlite = "0.31" r2d2_sqlite = "0.24"
fjall = "2.4" fjall = "2.4"
async-compression = { version = "0.4", features = ["tokio", "zstd"] } async-compression = { version = "0.4", features = ["tokio", "zstd"] }
-46
View File
@@ -161,49 +161,3 @@ kopia repository validate-provider
You can then run all the standard kopia commands: `kopia snapshot create`, `kopia mount`... You can then run all the standard kopia commands: `kopia snapshot create`, `kopia mount`...
Everything should work out-of-the-box. Everything should work out-of-the-box.
## Plakar
Create your key and bucket on Garage server:
```bash
garage key create my-plakar-key
garage bucket create plakar-backups
garage bucket allow plakar-backups --read --write --key my-plakar-key
```
On Plakar server, add your Garage as a storage location:
```bash
plakar store add garageS3 s3://my-garage.tld/plakar-backups \
region=garage # Or as you've specified in garage.toml \
access_key=<Key ID from "garage key info my-plakar-key"> \
secret_access_key=<Secret key from "garage key info my-plakar-key">
```
Then create the repository.
```bash
plakar at @garageS3 create -plaintext # Unencrypted
# or
plakar at @garageS3 create #encrypted
```
If you encrypt your backups (Plakar default), you will need to define a strong passphrase. Do not forget to save your password safely. It will be needed to decrypt your backups.
After the repository has been created, check that everything works as expected (that might give an empty result as no file has been added yet, but no error message):
```bash
plakar at @garageS3 check
```
Now that everything is configure, you can use Garage as your backups storage. For instance sync it with a local backup storage:
```bash
$ plakar at ~/backups sync to @garageS3
```
Or list the S3 storage content:
```bash
$ plakar at @garageS3 ls
```
More information in Plakar documentation: https://www.plakar.io/docs/main/quickstart/
+5 -5
View File
@@ -96,14 +96,14 @@ to store 2 TB of data in total.
## Get a Docker image ## Get a Docker image
Our docker image is currently named `dxflrs/garage` and is stored on the [Docker Hub](https://hub.docker.com/r/dxflrs/garage/tags?page=1&ordering=last_updated). Our docker image is currently named `dxflrs/garage` and is stored on the [Docker Hub](https://hub.docker.com/r/dxflrs/garage/tags?page=1&ordering=last_updated).
We encourage you to use a fixed tag (eg. `v1.3.0`) and not the `latest` tag. We encourage you to use a fixed tag (eg. `v1.2.0`) and not the `latest` tag.
For this example, we will use the latest published version at the time of the writing which is `v1.3.0` but it's up to you For this example, we will use the latest published version at the time of the writing which is `v1.2.0` but it's up to you
to check [the most recent versions on the Docker Hub](https://hub.docker.com/r/dxflrs/garage/tags?page=1&ordering=last_updated). to check [the most recent versions on the Docker Hub](https://hub.docker.com/r/dxflrs/garage/tags?page=1&ordering=last_updated).
For example: For example:
``` ```
sudo docker pull dxflrs/garage:v1.3.0 sudo docker pull dxflrs/garage:v1.2.0
``` ```
## Deploying and configuring Garage ## Deploying and configuring Garage
@@ -171,7 +171,7 @@ docker run \
-v /etc/garage.toml:/etc/garage.toml \ -v /etc/garage.toml:/etc/garage.toml \
-v /var/lib/garage/meta:/var/lib/garage/meta \ -v /var/lib/garage/meta:/var/lib/garage/meta \
-v /var/lib/garage/data:/var/lib/garage/data \ -v /var/lib/garage/data:/var/lib/garage/data \
dxflrs/garage:v1.3.0 dxflrs/garage:v1.2.0
``` ```
With this command line, Garage should be started automatically at each boot. With this command line, Garage should be started automatically at each boot.
@@ -185,7 +185,7 @@ If you want to use `docker-compose`, you may use the following `docker-compose.y
version: "3" version: "3"
services: services:
garage: garage:
image: dxflrs/garage:v1.3.0 image: dxflrs/garage:v1.2.0
network_mode: "host" network_mode: "host"
restart: unless-stopped restart: unless-stopped
volumes: volumes:
+1 -1
View File
@@ -132,7 +132,7 @@ docker run \
-v /path/to/garage.toml:/etc/garage.toml \ -v /path/to/garage.toml:/etc/garage.toml \
-v /path/to/garage/meta:/var/lib/garage/meta \ -v /path/to/garage/meta:/var/lib/garage/meta \
-v /path/to/garage/data:/var/lib/garage/data \ -v /path/to/garage/data:/var/lib/garage/data \
dxflrs/garage:v1.3.0 dxflrs/garage:v1.2.0
``` ```
Under Linux, you can substitute `--network host` for `-p 3900:3900 -p 3901:3901 -p 3902:3902 -p 3903:3903` Under Linux, you can substitute `--network host` for `-p 3900:3900 -p 3901:3901 -p 3902:3902 -p 3903:3903`
@@ -24,7 +24,6 @@ db_engine = "lmdb"
block_size = "1M" block_size = "1M"
block_ram_buffer_max = "256MiB" block_ram_buffer_max = "256MiB"
block_max_concurrent_reads = 16
lmdb_map_size = "1T" lmdb_map_size = "1T"
@@ -97,7 +96,6 @@ The following gives details about each available configuration option.
Top-level configuration options, in alphabetical order: Top-level configuration options, in alphabetical order:
[`allow_punycode`](#allow_punycode), [`allow_punycode`](#allow_punycode),
[`allow_world_readable_secrets`](#allow_world_readable_secrets), [`allow_world_readable_secrets`](#allow_world_readable_secrets),
[`block_max_concurrent_reads`](`block_max_concurrent_reads),
[`block_ram_buffer_max`](#block_ram_buffer_max), [`block_ram_buffer_max`](#block_ram_buffer_max),
[`block_size`](#block_size), [`block_size`](#block_size),
[`bootstrap_peers`](#bootstrap_peers), [`bootstrap_peers`](#bootstrap_peers),
@@ -524,29 +522,6 @@ node.
The default value is 256MiB. The default value is 256MiB.
#### `block_max_concurrent_reads` (since `v1.3.0` / `v2.1.0`) {#block_max_concurrent_reads}
The maximum number of blocks (individual files in the data directory) open
simultaneously for reading.
Reducing this number does not limit the number of data blocks that can be
transferred through the network simultaneously. This mechanism was just added
as a backpressure mechanism for HDD read speed: it helps avoid a situation
where too many requests are coming in and Garage is reading too many block
files simultaneously, thus not making timely progress on any of the reads.
When a request to read a data block comes in through the network, the requests
awaits for one of the `block_max_concurrent_reads` slots to be available
(internally implemented using a Semaphore object). Once it acquired a read
slot, it reads the entire block file to RAM and frees the slot as soon as the
block file is finished reading. Only after the slot is released will the
block's data start being transferred over the network. If the request fails to
acquire a reading slot wihtin 15 seconds, it fails with a timeout error.
Timeout events can be monitored through the `block_read_semaphore_timeouts`
metric in Prometheus: a non-zero number of such events indicates an I/O
bottleneck on HDD read speed.
#### `lmdb_map_size` {#lmdb_map_size} #### `lmdb_map_size` {#lmdb_map_size}
This parameters can be used to set the map size used by LMDB, This parameters can be used to set the map size used by LMDB,
+1 -1
View File
@@ -70,7 +70,7 @@ Example response body:
```json ```json
{ {
"node": "b10c110e4e854e5aa3f4637681befac755154b20059ec163254ddbfae86b09df", "node": "b10c110e4e854e5aa3f4637681befac755154b20059ec163254ddbfae86b09df",
"garageVersion": "v1.3.0", "garageVersion": "v1.2.0",
"garageFeatures": [ "garageFeatures": [
"k2v", "k2v",
"lmdb", "lmdb",
+2 -2
View File
@@ -2,8 +2,8 @@ apiVersion: v2
name: garage name: garage
description: S3-compatible object store for small self-hosted geo-distributed deployments description: S3-compatible object store for small self-hosted geo-distributed deployments
type: application type: application
version: 0.7.2 version: 0.7.1
appVersion: "v1.3.0" appVersion: "v1.2.0"
home: https://garagehq.deuxfleurs.fr/ home: https://garagehq.deuxfleurs.fr/
icon: https://garagehq.deuxfleurs.fr/images/garage-logo.svg icon: https://garagehq.deuxfleurs.fr/images/garage-logo.svg
+1 -1
View File
@@ -1,6 +1,6 @@
# garage # garage
![Version: 0.7.2](https://img.shields.io/badge/Version-0.7.2-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: v1.3.0](https://img.shields.io/badge/AppVersion-v1.3.0-informational?style=flat-square) ![Version: 0.7.1](https://img.shields.io/badge/Version-0.7.1-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: v1.2.0](https://img.shields.io/badge/AppVersion-v1.2.0-informational?style=flat-square)
S3-compatible object store for small self-hosted geo-distributed deployments S3-compatible object store for small self-hosted geo-distributed deployments
+1 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "garage_api_admin" name = "garage_api_admin"
version = "1.3.0" version = "1.2.0"
authors = ["Alex Auvolat <alex@adnab.me>"] authors = ["Alex Auvolat <alex@adnab.me>"]
edition = "2018" edition = "2018"
license = "AGPL-3.0" license = "AGPL-3.0"
+1 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "garage_api_common" name = "garage_api_common"
version = "1.3.0" version = "1.2.0"
authors = ["Alex Auvolat <alex@adnab.me>"] authors = ["Alex Auvolat <alex@adnab.me>"]
edition = "2018" edition = "2018"
license = "AGPL-3.0" license = "AGPL-3.0"
+2 -1
View File
@@ -33,6 +33,7 @@ use garage_util::metrics::{gen_trace_id, RecordDuration};
use garage_util::socket_address::UnixOrTCPSocketAddress; use garage_util::socket_address::UnixOrTCPSocketAddress;
use crate::helpers::{BoxBody, ErrorBody}; use crate::helpers::{BoxBody, ErrorBody};
use crate::signature::payload::Authorization;
pub trait ApiEndpoint: Send + Sync + 'static { pub trait ApiEndpoint: Send + Sync + 'static {
fn name(&self) -> &'static str; fn name(&self) -> &'static str;
@@ -61,7 +62,7 @@ pub trait ApiHandler: Send + Sync + 'static {
/// Returns the key id used to authenticate this request. The ID returned must be safe to /// Returns the key id used to authenticate this request. The ID returned must be safe to
/// log. /// log.
fn key_id_from_request(&self, _req: &Request<IncomingBody>) -> Option<String> { fn key_id_from_request(&self, req: &Request<IncomingBody>) -> Option<String> {
None None
} }
} }
+1 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "garage_api_k2v" name = "garage_api_k2v"
version = "1.3.0" version = "1.2.0"
authors = ["Alex Auvolat <alex@adnab.me>"] authors = ["Alex Auvolat <alex@adnab.me>"]
edition = "2018" edition = "2018"
license = "AGPL-3.0" license = "AGPL-3.0"
+1 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "garage_api_s3" name = "garage_api_s3"
version = "1.3.0" version = "1.2.0"
authors = ["Alex Auvolat <alex@adnab.me>"] authors = ["Alex Auvolat <alex@adnab.me>"]
edition = "2018" edition = "2018"
license = "AGPL-3.0" license = "AGPL-3.0"
+1 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "garage_block" name = "garage_block"
version = "1.3.0" version = "1.2.0"
authors = ["Alex Auvolat <alex@adnab.me>"] authors = ["Alex Auvolat <alex@adnab.me>"]
edition = "2018" edition = "2018"
license = "AGPL-3.0" license = "AGPL-3.0"
+3 -16
View File
@@ -50,8 +50,6 @@ pub const INLINE_THRESHOLD: usize = 3072;
// to delete the block locally. // to delete the block locally.
pub(crate) const BLOCK_GC_DELAY: Duration = Duration::from_secs(600); pub(crate) const BLOCK_GC_DELAY: Duration = Duration::from_secs(600);
const BLOCK_READ_SEMAPHORE_TIMEOUT: Duration = Duration::from_secs(15);
/// RPC messages used to share blocks of data between nodes /// RPC messages used to share blocks of data between nodes
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
pub enum BlockRpc { pub enum BlockRpc {
@@ -89,7 +87,6 @@ pub struct BlockManager {
disable_scrub: bool, disable_scrub: bool,
mutation_lock: Vec<Mutex<BlockManagerLocked>>, mutation_lock: Vec<Mutex<BlockManagerLocked>>,
read_semaphore: Semaphore,
pub rc: BlockRc, pub rc: BlockRc,
pub resync: BlockResyncManager, pub resync: BlockResyncManager,
@@ -179,8 +176,6 @@ impl BlockManager {
.iter() .iter()
.map(|_| Mutex::new(BlockManagerLocked())) .map(|_| Mutex::new(BlockManagerLocked()))
.collect::<Vec<_>>(), .collect::<Vec<_>>(),
read_semaphore: Semaphore::new(config.block_max_concurrent_reads),
rc, rc,
resync, resync,
system, system,
@@ -562,6 +557,9 @@ impl BlockManager {
match self.find_block(hash).await { match self.find_block(hash).await {
Some(p) => self.read_block_from(hash, &p).await, Some(p) => self.read_block_from(hash, &p).await,
None => { None => {
// Not found but maybe we should have had it ??
self.resync
.put_to_resync(hash, 2 * self.system.rpc_helper().rpc_timeout())?;
return Err(Error::Message(format!( return Err(Error::Message(format!(
"block {:?} not found on node", "block {:?} not found on node",
hash hash
@@ -583,15 +581,6 @@ impl BlockManager {
) -> Result<DataBlock, Error> { ) -> Result<DataBlock, Error> {
let (header, path) = block_path.as_parts_ref(); let (header, path) = block_path.as_parts_ref();
let permit = tokio::select! {
sem = self.read_semaphore.acquire() => sem.ok_or_message("acquire read semaphore")?,
_ = tokio::time::sleep(BLOCK_READ_SEMAPHORE_TIMEOUT) => {
self.metrics.block_read_semaphore_timeouts.add(1);
debug!("read block {:?}: read_semaphore acquire timeout", hash);
return Err(Error::Message("read block: read_semaphore acquire timeout".into()));
}
};
let mut f = fs::File::open(&path).await?; let mut f = fs::File::open(&path).await?;
let mut data = vec![]; let mut data = vec![];
f.read_to_end(&mut data).await?; f.read_to_end(&mut data).await?;
@@ -616,8 +605,6 @@ impl BlockManager {
return Err(Error::CorruptData(*hash)); return Err(Error::CorruptData(*hash));
} }
drop(permit);
Ok(data) Ok(data)
} }
-6
View File
@@ -22,7 +22,6 @@ pub struct BlockManagerMetrics {
pub(crate) bytes_read: BoundCounter<u64>, pub(crate) bytes_read: BoundCounter<u64>,
pub(crate) block_read_duration: BoundValueRecorder<f64>, pub(crate) block_read_duration: BoundValueRecorder<f64>,
pub(crate) block_read_semaphore_timeouts: BoundCounter<u64>,
pub(crate) bytes_written: BoundCounter<u64>, pub(crate) bytes_written: BoundCounter<u64>,
pub(crate) block_write_duration: BoundValueRecorder<f64>, pub(crate) block_write_duration: BoundValueRecorder<f64>,
pub(crate) delete_counter: BoundCounter<u64>, pub(crate) delete_counter: BoundCounter<u64>,
@@ -120,11 +119,6 @@ impl BlockManagerMetrics {
.with_description("Duration of block read operations") .with_description("Duration of block read operations")
.init() .init()
.bind(&[]), .bind(&[]),
block_read_semaphore_timeouts: meter
.u64_counter("block.read_semaphore_timeouts")
.with_description("Number of block reads that failed due to semaphore acquire timeout")
.init()
.bind(&[]),
bytes_written: meter bytes_written: meter
.u64_counter("block.bytes_written") .u64_counter("block.bytes_written")
.with_description("Number of bytes written to disk") .with_description("Number of bytes written to disk")
-8
View File
@@ -133,14 +133,6 @@ impl BlockResyncManager {
))) )))
} }
/// Clear the entire resync queue and list of errored blocks
/// Corresponds to `garage repair clear-resync-queue`
pub fn clear_resync_queue(&self) -> Result<(), Error> {
self.queue.clear()?;
self.errors.clear()?;
Ok(())
}
pub fn register_bg_vars(&self, vars: &mut vars::BgVars) { pub fn register_bg_vars(&self, vars: &mut vars::BgVars) {
let notify = self.notify.clone(); let notify = self.notify.clone();
vars.register_rw( vars.register_rw(
+1 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "garage_db" name = "garage_db"
version = "1.3.0" version = "1.2.0"
authors = ["Alex Auvolat <alex@adnab.me>"] authors = ["Alex Auvolat <alex@adnab.me>"]
edition = "2018" edition = "2018"
license = "AGPL-3.0" license = "AGPL-3.0"
+25 -37
View File
@@ -106,44 +106,32 @@ impl Db {
result: Cell::new(None), result: Cell::new(None),
}; };
let tx_res = self.0.transaction(&f); let tx_res = self.0.transaction(&f);
let fn_res = f.result.into_inner(); let ret = f
.result
.into_inner()
.expect("Transaction did not store result");
match (tx_res, fn_res) { match tx_res {
(Ok(on_commit), Some(Ok(value))) => { Ok(on_commit) => match ret {
// Transaction succeeded Ok(value) => {
// TxFn stored the value to return to the user in fn_res on_commit.into_iter().for_each(|f| f());
// tx_res contains the on_commit list of callbacks, run them now Ok(value)
on_commit.into_iter().for_each(|f| f()); }
Ok(value) _ => unreachable!(),
} },
(Err(TxError::Abort(())), Some(Err(TxError::Abort(e)))) => { Err(TxError::Abort(())) => match ret {
// Transaction was aborted by user code Err(TxError::Abort(e)) => Err(TxError::Abort(e)),
// The abort error value is stored in fn_res _ => unreachable!(),
Err(TxError::Abort(e)) },
} Err(TxError::Db(e2)) => match ret {
(Err(TxError::Db(_tx_e)), Some(Err(TxError::Db(fn_e)))) => { // Ok was stored -> the error occurred when finalizing
// Transaction encountered a DB error in user code // transaction
// The error value encountered is the one in fn_res, Ok(_) => Err(TxError::Db(e2)),
// tx_res contains only a dummy error message // An error was already stored: that's the one we want to
Err(TxError::Db(fn_e)) // return
} Err(TxError::Db(e)) => Err(TxError::Db(e)),
(Err(TxError::Db(tx_e)), None) => { _ => unreachable!(),
// Transaction encounterred a DB error when initializing the transaction, },
// before user code was called
Err(TxError::Db(tx_e))
}
(Err(TxError::Db(tx_e)), Some(Ok(_))) => {
// Transaction encounterred a DB error when commiting the transaction,
// after user code was called
Err(TxError::Db(tx_e))
}
(tx_res, fn_res) => {
panic!(
"unexpected error case: tx_res={:?}, fn_res={:?}",
tx_res.map(|_| "..."),
fn_res.map(|x| x.map(|_| "...").map_err(|_| "..."))
);
}
} }
} }
+23 -9
View File
@@ -151,16 +151,30 @@ impl IDb for SqliteDb {
} }
fn snapshot(&self, base_path: &PathBuf) -> Result<()> { fn snapshot(&self, base_path: &PathBuf) -> Result<()> {
std::fs::create_dir_all(base_path)?; fn progress(p: rusqlite::backup::Progress) {
let path = Engine::Sqlite use std::sync::atomic::{AtomicU64, Ordering};
.db_path(&base_path) use std::time::{SystemTime, UNIX_EPOCH};
.into_os_string()
.into_string()
.map_err(|_| Error("invalid sqlite path string".into()))?;
info!("Start sqlite VACUUM INTO `{}`", path); static LAST_LOG_TIME: AtomicU64 = AtomicU64::new(0);
self.db.get()?.execute("VACUUM INTO ?1", params![path])?;
info!("Finished sqlite VACUUM INTO `{}`", path); let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("Fix your clock :o")
.as_millis() as u64;
if now >= LAST_LOG_TIME.load(Ordering::Relaxed) + 10 * 1000 {
let percent = (p.pagecount - p.remaining) * 100 / p.pagecount;
info!("Sqlite snapshot progress: {}%", percent);
LAST_LOG_TIME.fetch_max(now, Ordering::Relaxed);
}
}
std::fs::create_dir_all(base_path)?;
let path = Engine::Sqlite.db_path(&base_path);
self.db
.get()?
.backup(rusqlite::DatabaseName::Main, path, Some(progress))?;
Ok(()) Ok(())
} }
+3 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "garage" name = "garage"
version = "1.3.0" version = "1.2.0"
authors = ["Alex Auvolat <alex@adnab.me>"] authors = ["Alex Auvolat <alex@adnab.me>"]
edition = "2018" edition = "2018"
license = "AGPL-3.0" license = "AGPL-3.0"
@@ -49,6 +49,8 @@ structopt.workspace = true
git-version.workspace = true git-version.workspace = true
serde.workspace = true serde.workspace = true
serde_json.workspace = true
serde_bytes.workspace = true
futures.workspace = true futures.workspace = true
tokio.workspace = true tokio.workspace = true
+10 -4
View File
@@ -71,6 +71,10 @@ pub enum NodeOperation {
/// Connect to Garage node that is currently isolated from the system /// Connect to Garage node that is currently isolated from the system
#[structopt(name = "connect", version = garage_version())] #[structopt(name = "connect", version = garage_version())]
Connect(ConnectNodeOpt), Connect(ConnectNodeOpt),
/// Dump the content of a metadata table as JSON lines
#[structopt(name = "dump", version = garage_version())]
Dump(DumpNodeOpt),
} }
#[derive(StructOpt, Debug)] #[derive(StructOpt, Debug)]
@@ -88,6 +92,12 @@ pub struct ConnectNodeOpt {
pub(crate) node: String, pub(crate) node: String,
} }
#[derive(StructOpt, Debug)]
pub struct DumpNodeOpt {
/// Name of the data table to dump
pub(crate) what: String,
}
#[derive(StructOpt, Debug)] #[derive(StructOpt, Debug)]
pub enum LayoutOperation { pub enum LayoutOperation {
/// Assign role to Garage node /// Assign role to Garage node
@@ -466,10 +476,6 @@ pub enum RepairWhat {
/// Repair (resync/rebalance) the set of stored blocks in the cluster /// Repair (resync/rebalance) the set of stored blocks in the cluster
#[structopt(name = "blocks", version = garage_version())] #[structopt(name = "blocks", version = garage_version())]
Blocks, Blocks,
/// Clear the block resync queue. The list of blocks in errored state
/// is cleared as well. You MUST run `garage repair blocks` after invoking this.
#[structopt(name = "clear-resync-queue", version = garage_version())]
ClearResyncQueue,
/// Repropagate object deletions to the version table /// Repropagate object deletions to the version table
#[structopt(name = "versions", version = garage_version())] #[structopt(name = "versions", version = garage_version())]
Versions, Versions,
+4 -1
View File
@@ -145,11 +145,14 @@ async fn main() {
let res = match opt.cmd { let res = match opt.cmd {
Command::Server => server::run_server(opt.config_file, opt.secrets).await, Command::Server => server::run_server(opt.config_file, opt.secrets).await,
Command::OfflineRepair(repair_opt) => { Command::OfflineRepair(repair_opt) => {
repair::offline::offline_repair(opt.config_file, opt.secrets, repair_opt).await repair::offline::offline_repair(opt.config_file, opt.secrets, repair_opt)
} }
Command::ConvertDb(conv_opt) => { Command::ConvertDb(conv_opt) => {
cli::convert_db::do_conversion(conv_opt).map_err(From::from) cli::convert_db::do_conversion(conv_opt).map_err(From::from)
} }
Command::Node(NodeOperation::Dump(dump_opt)) => {
repair::offline::dump(opt.config_file, opt.secrets, dump_opt)
}
Command::Node(NodeOperation::NodeId(node_id_opt)) => { Command::Node(NodeOperation::NodeId(node_id_opt)) => {
node_id_command(opt.config_file, node_id_opt.quiet) node_id_command(opt.config_file, node_id_opt.quiet)
} }
+102 -1
View File
@@ -1,14 +1,18 @@
use std::io::Write;
use std::path::PathBuf; use std::path::PathBuf;
use serde::Serialize;
use garage_util::config::*; use garage_util::config::*;
use garage_util::error::*; use garage_util::error::*;
use garage_model::garage::Garage; use garage_model::garage::Garage;
use garage_table::{replication::TableReplication, *};
use crate::cli::structs::*; use crate::cli::structs::*;
use crate::secrets::{fill_secrets, Secrets}; use crate::secrets::{fill_secrets, Secrets};
pub async fn offline_repair( pub fn offline_repair(
config_file: PathBuf, config_file: PathBuf,
secrets: Secrets, secrets: Secrets,
opt: OfflineRepairOpt, opt: OfflineRepairOpt,
@@ -45,3 +49,100 @@ pub async fn offline_repair(
Ok(()) Ok(())
} }
pub fn dump(config_file: PathBuf, secrets: Secrets, opt: DumpNodeOpt) -> Result<(), Error> {
let what = opt.what.as_str();
info!("Loading configuration...");
let config = fill_secrets(read_config(config_file)?, secrets)?;
info!("Initializing Garage main data store...");
let garage = Garage::new(config)?;
match what {
"bucket" | "buckets" => dump_table_inner(&garage.bucket_table),
"bucket_alias" | "bucket_aliases" => dump_table_inner(&garage.bucket_alias_table),
"key" | "keys" => dump_table_inner(&garage.key_table),
"object" | "objects" => dump_table_inner(&garage.object_table),
"object_counter" | "object_counters" => Err(Error::Message(
"object_counters cannot be JSON-serialized".into(),
)),
"mpu" => dump_table_inner(&garage.mpu_table),
"mpu_counter" | "mpu_counters" => Err(Error::Message(
"mpu_counters cannot be JSON-serialized".into(),
)),
"version" | "versions" => dump_table_inner(&garage.version_table),
"block_ref" | "block_refs" => dump_table_inner(&garage.block_ref_table),
#[cfg(feature = "k2v")]
"k2v_item" | "k2v_items" => dump_table_inner(&garage.k2v.item_table),
//#[cfg(feature = "k2v")]
"k2v_counter" | "k2v_counters" => Err(Error::Message(
"k2v_counters cannot be JSON-serialized".into(),
)),
other => {
let mut stdout = std::io::stdout().lock();
match other {
"cluster_layout" => Err(Error::Message(
"cluster_layout cannot be JSON-serialized".into(),
)),
_ => Err(Error::Message(format!("invalid thing to dump: {}", what))),
}
}
}
}
#[derive(Serialize)]
struct DumpEntry<'a, T: Serialize> {
#[serde(with = "serde_bytes")]
partition_key: &'a [u8],
#[serde(with = "serde_bytes")]
sort_key: &'a [u8],
entry: &'a T,
}
fn dump_table_inner<F, R>(table: &Table<F, R>) -> Result<(), Error>
where
F: TableSchema,
R: TableReplication,
{
eprintln!("Dumping table {}...", F::TABLE_NAME);
let mut stdout = std::io::stdout().lock();
for line in table.data.store.iter()? {
let (_k, v) = line?;
let v_dec = table.data.decode_entry(&v)?;
let pkh = v_dec.partition_key().hash();
let dump_entry = DumpEntry {
partition_key: pkh.as_slice(),
sort_key: v_dec.sort_key().sort_key(),
entry: &v_dec,
};
dump_line(&mut stdout, dump_entry)?;
}
stdout.flush()?;
Ok(())
}
fn dump_line<T: Serialize>(
mut stdout: &mut std::io::StdoutLock<'static>,
dump_entry: T,
) -> Result<(), Error> {
let mut ser = serde_json::ser::Serializer::with_formatter(&mut stdout, DumpFormatter);
dump_entry.serialize(&mut ser)?;
stdout.write_all(b"\n")?;
Ok(())
}
struct DumpFormatter;
impl serde_json::ser::Formatter for DumpFormatter {
fn write_byte_array<W>(&mut self, writer: &mut W, value: &[u8]) -> std::io::Result<()>
where
W: ?Sized + std::io::Write,
{
writer.write_all(b"\"")?;
writer.write_all(hex::encode(&value).as_bytes())?;
writer.write_all(b"\"")
}
}
-5
View File
@@ -92,11 +92,6 @@ pub async fn launch_online_repair(
info!("Repairing bucket aliases (foreground)"); info!("Repairing bucket aliases (foreground)");
garage.locked_helper().await.repair_aliases().await?; garage.locked_helper().await.repair_aliases().await?;
} }
RepairWhat::ClearResyncQueue => {
let garage = garage.clone();
tokio::task::spawn_blocking(move || garage.block_manager.resync.clear_resync_queue())
.await??
}
} }
Ok(()) Ok(())
} }
+2 -2
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "garage_model" name = "garage_model"
version = "1.3.0" version = "1.2.0"
authors = ["Alex Auvolat <alex@adnab.me>"] authors = ["Alex Auvolat <alex@adnab.me>"]
edition = "2018" edition = "2018"
license = "AGPL-3.0" license = "AGPL-3.0"
@@ -44,4 +44,4 @@ default = [ "lmdb", "sqlite" ]
k2v = [ "garage_util/k2v" ] k2v = [ "garage_util/k2v" ]
lmdb = [ "garage_db/lmdb" ] lmdb = [ "garage_db/lmdb" ]
sqlite = [ "garage_db/sqlite" ] sqlite = [ "garage_db/sqlite" ]
fjall = [ "garage_db/fjall" ] fjall = [ "garage_db/fjall" ]
+1 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "garage_net" name = "garage_net"
version = "1.3.0" version = "1.2.0"
authors = ["Alex Auvolat <alex@adnab.me>"] authors = ["Alex Auvolat <alex@adnab.me>"]
edition = "2018" edition = "2018"
license = "AGPL-3.0" license = "AGPL-3.0"
+1 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "garage_rpc" name = "garage_rpc"
version = "1.3.0" version = "1.2.0"
authors = ["Alex Auvolat <alex@adnab.me>"] authors = ["Alex Auvolat <alex@adnab.me>"]
edition = "2018" edition = "2018"
license = "AGPL-3.0" license = "AGPL-3.0"
+8 -6
View File
@@ -229,11 +229,13 @@ impl LayoutManager {
} }
/// Save cluster layout data to disk /// Save cluster layout data to disk
async fn save_cluster_layout(&self) { async fn save_cluster_layout(&self) -> Result<(), Error> {
let layout = self.layout.read().unwrap().inner().clone(); let layout = self.layout.read().unwrap().inner().clone();
if let Err(e) = self.persist_cluster_layout.save_async(&layout).await { self.persist_cluster_layout
error!("Failed to save cluster_layout: {}", e); .save_async(&layout)
} .await
.expect("Cannot save current cluster layout");
Ok(())
} }
fn broadcast_update(self: &Arc<Self>, rpc: SystemRpc) { fn broadcast_update(self: &Arc<Self>, rpc: SystemRpc) {
@@ -311,7 +313,7 @@ impl LayoutManager {
self.change_notify.notify_waiters(); self.change_notify.notify_waiters();
self.broadcast_update(SystemRpc::AdvertiseClusterLayout(new_layout)); self.broadcast_update(SystemRpc::AdvertiseClusterLayout(new_layout));
self.save_cluster_layout().await; self.save_cluster_layout().await?;
} }
Ok(SystemRpc::Ok) Ok(SystemRpc::Ok)
@@ -326,7 +328,7 @@ impl LayoutManager {
if let Some(new_trackers) = self.merge_layout_trackers(trackers) { if let Some(new_trackers) = self.merge_layout_trackers(trackers) {
self.change_notify.notify_waiters(); self.change_notify.notify_waiters();
self.broadcast_update(SystemRpc::AdvertiseClusterLayoutTrackers(new_trackers)); self.broadcast_update(SystemRpc::AdvertiseClusterLayoutTrackers(new_trackers));
self.save_cluster_layout().await; self.save_cluster_layout().await?;
} }
Ok(SystemRpc::Ok) Ok(SystemRpc::Ok)
+1 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "garage_table" name = "garage_table"
version = "1.3.0" version = "1.2.0"
authors = ["Alex Auvolat <alex@adnab.me>"] authors = ["Alex Auvolat <alex@adnab.me>"]
edition = "2018" edition = "2018"
license = "AGPL-3.0" license = "AGPL-3.0"
+1 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "garage_util" name = "garage_util"
version = "1.3.0" version = "1.2.0"
authors = ["Alex Auvolat <alex@adnab.me>"] authors = ["Alex Auvolat <alex@adnab.me>"]
edition = "2018" edition = "2018"
license = "AGPL-3.0" license = "AGPL-3.0"
-7
View File
@@ -75,10 +75,6 @@ pub struct Config {
)] )]
pub block_ram_buffer_max: usize, pub block_ram_buffer_max: usize,
/// Maximum number of concurrent reads of block files on disk
#[serde(default = "default_block_max_concurrent_reads")]
pub block_max_concurrent_reads: usize,
/// Skip the permission check of secret files. Useful when /// Skip the permission check of secret files. Useful when
/// POSIX ACLs (or more complex chmods) are used. /// POSIX ACLs (or more complex chmods) are used.
#[serde(default)] #[serde(default)]
@@ -284,9 +280,6 @@ fn default_block_size() -> usize {
fn default_block_ram_buffer_max() -> usize { fn default_block_ram_buffer_max() -> usize {
256 * 1024 * 1024 256 * 1024 * 1024
} }
fn default_block_max_concurrent_reads() -> usize {
16
}
fn default_consistency_mode() -> String { fn default_consistency_mode() -> String {
"consistent".into() "consistent".into()
+1 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "garage_web" name = "garage_web"
version = "1.3.0" version = "1.2.0"
authors = ["Alex Auvolat <alex@adnab.me>", "Quentin Dufour <quentin@dufour.io>"] authors = ["Alex Auvolat <alex@adnab.me>", "Quentin Dufour <quentin@dufour.io>"]
edition = "2018" edition = "2018"
license = "AGPL-3.0" license = "AGPL-3.0"