mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-28 07:57:01 +00:00
fix(scanner): preserve newer usage snapshots (#3370)
* fix(scanner): preserve newer usage snapshots * fix(table-catalog): require namespace locking backend --------- Co-authored-by: Henry Guo <marshawcoco@users.noreply.github.com>
This commit is contained in:
@@ -45,7 +45,7 @@ use rustfs_ecstore::disk::RUSTFS_META_BUCKET;
|
|||||||
use rustfs_ecstore::error::Error as EcstoreError;
|
use rustfs_ecstore::error::Error as EcstoreError;
|
||||||
use rustfs_ecstore::global::is_erasure_sd;
|
use rustfs_ecstore::global::is_erasure_sd;
|
||||||
use rustfs_ecstore::store::ECStore;
|
use rustfs_ecstore::store::ECStore;
|
||||||
use rustfs_ecstore::store_api::{BucketOperations, NamespaceLocking as _};
|
use rustfs_ecstore::store_api::{BucketOperations, NamespaceLocking as _, ObjectIO};
|
||||||
use rustfs_storage_api::BucketOptions;
|
use rustfs_storage_api::BucketOptions;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use tokio::sync::mpsc;
|
use tokio::sync::mpsc;
|
||||||
@@ -738,7 +738,7 @@ impl Drop for ScannerScanModeGuard {
|
|||||||
#[instrument(skip(ctx, storeapi))]
|
#[instrument(skip(ctx, storeapi))]
|
||||||
pub async fn store_data_usage_in_backend(
|
pub async fn store_data_usage_in_backend(
|
||||||
ctx: CancellationToken,
|
ctx: CancellationToken,
|
||||||
storeapi: Arc<ECStore>,
|
storeapi: Arc<impl ObjectIO>,
|
||||||
mut receiver: mpsc::Receiver<DataUsageInfo>,
|
mut receiver: mpsc::Receiver<DataUsageInfo>,
|
||||||
) {
|
) {
|
||||||
let mut attempts = 1u32;
|
let mut attempts = 1u32;
|
||||||
@@ -749,6 +749,18 @@ pub async fn store_data_usage_in_backend(
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Ok(buf) = read_config(storeapi.clone(), DATA_USAGE_OBJ_NAME_PATH.as_str()).await
|
||||||
|
&& let Ok(existing) = serde_json::from_slice::<DataUsageInfo>(&buf)
|
||||||
|
&& let (Some(new_ts), Some(existing_ts)) = (data_usage_info.last_update, existing.last_update)
|
||||||
|
&& new_ts <= existing_ts
|
||||||
|
{
|
||||||
|
info!(
|
||||||
|
"Skip persisting data usage: incoming last_update {:?} <= existing {:?}",
|
||||||
|
new_ts, existing_ts
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
// Serialize to JSON
|
// Serialize to JSON
|
||||||
let data = match serde_json::to_vec(&data_usage_info) {
|
let data = match serde_json::to_vec(&data_usage_info) {
|
||||||
Ok(data) => data,
|
Ok(data) => data,
|
||||||
@@ -785,8 +797,13 @@ pub async fn store_data_usage_in_backend(
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use rustfs_ecstore::store_api::{GetObjectReader, ObjectIO, ObjectInfo, ObjectOptions, PutObjReader};
|
||||||
use serial_test::serial;
|
use serial_test::serial;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::io::Cursor;
|
||||||
use temp_env::{with_var, with_var_unset};
|
use temp_env::{with_var, with_var_unset};
|
||||||
|
use tokio::io::AsyncReadExt;
|
||||||
|
use tokio::sync::Mutex;
|
||||||
|
|
||||||
struct ScannerDefaultSpeedGuard;
|
struct ScannerDefaultSpeedGuard;
|
||||||
|
|
||||||
@@ -818,6 +835,51 @@ mod tests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
struct MemoryConfigStore {
|
||||||
|
objects: Mutex<HashMap<String, Vec<u8>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn memory_config_key(bucket: &str, object: &str) -> String {
|
||||||
|
format!("{bucket}/{object}")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl ObjectIO for MemoryConfigStore {
|
||||||
|
async fn get_object_reader(
|
||||||
|
&self,
|
||||||
|
bucket: &str,
|
||||||
|
object: &str,
|
||||||
|
_range: Option<rustfs_ecstore::store_api::HTTPRangeSpec>,
|
||||||
|
_h: http::HeaderMap,
|
||||||
|
_opts: &ObjectOptions,
|
||||||
|
) -> rustfs_ecstore::error::Result<GetObjectReader> {
|
||||||
|
let objects = self.objects.lock().await;
|
||||||
|
let data = objects
|
||||||
|
.get(&memory_config_key(bucket, object))
|
||||||
|
.cloned()
|
||||||
|
.ok_or(rustfs_ecstore::error::Error::FileNotFound)?;
|
||||||
|
|
||||||
|
Ok(GetObjectReader {
|
||||||
|
stream: Box::new(Cursor::new(data)),
|
||||||
|
object_info: ObjectInfo::default(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn put_object(
|
||||||
|
&self,
|
||||||
|
bucket: &str,
|
||||||
|
object: &str,
|
||||||
|
data: &mut PutObjReader,
|
||||||
|
_opts: &ObjectOptions,
|
||||||
|
) -> rustfs_ecstore::error::Result<ObjectInfo> {
|
||||||
|
let mut buf = Vec::new();
|
||||||
|
data.stream.read_to_end(&mut buf).await?;
|
||||||
|
self.objects.lock().await.insert(memory_config_key(bucket, object), buf);
|
||||||
|
Ok(ObjectInfo::default())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn with_unset_scanner_timing_env(f: impl FnOnce()) {
|
fn with_unset_scanner_timing_env(f: impl FnOnce()) {
|
||||||
with_var_unset(ENV_SCANNER_SPEED, || {
|
with_var_unset(ENV_SCANNER_SPEED, || {
|
||||||
with_var_unset("MINIO_SCANNER_SPEED", || {
|
with_var_unset("MINIO_SCANNER_SPEED", || {
|
||||||
@@ -1022,6 +1084,39 @@ mod tests {
|
|||||||
global_metrics().set_cycle(None).await;
|
global_metrics().set_cycle(None).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_store_data_usage_in_backend_preserves_newer_snapshot() {
|
||||||
|
let store = Arc::new(MemoryConfigStore::default());
|
||||||
|
let (sender, receiver) = mpsc::channel(2);
|
||||||
|
let ctx = CancellationToken::new();
|
||||||
|
|
||||||
|
let newer = DataUsageInfo {
|
||||||
|
last_update: Some(std::time::SystemTime::UNIX_EPOCH + Duration::from_secs(20)),
|
||||||
|
buckets_count: 2,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
let older = DataUsageInfo {
|
||||||
|
last_update: Some(std::time::SystemTime::UNIX_EPOCH + Duration::from_secs(10)),
|
||||||
|
buckets_count: 1,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
sender.send(newer).await.expect("newer usage snapshot should enqueue");
|
||||||
|
sender.send(older).await.expect("older usage snapshot should enqueue");
|
||||||
|
drop(sender);
|
||||||
|
|
||||||
|
store_data_usage_in_backend(ctx, store.clone(), receiver).await;
|
||||||
|
|
||||||
|
let objects = store.objects.lock().await;
|
||||||
|
let saved = objects
|
||||||
|
.get(&memory_config_key(RUSTFS_META_BUCKET, DATA_USAGE_OBJ_NAME_PATH.as_str()))
|
||||||
|
.expect("data usage config should be saved");
|
||||||
|
let saved = serde_json::from_slice::<DataUsageInfo>(saved).expect("saved usage snapshot should decode");
|
||||||
|
|
||||||
|
assert_eq!(saved.buckets_count, 2);
|
||||||
|
assert_eq!(saved.last_update, Some(std::time::SystemTime::UNIX_EPOCH + Duration::from_secs(20)));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[serial]
|
#[serial]
|
||||||
fn test_cycle_interval_prefers_explicit_cycle_override() {
|
fn test_cycle_interval_prefers_explicit_cycle_override() {
|
||||||
|
|||||||
@@ -1447,7 +1447,7 @@ impl<S> Clone for EcStoreTableCatalogObjectBackend<S> {
|
|||||||
|
|
||||||
impl<S> EcStoreTableCatalogObjectBackend<S>
|
impl<S> EcStoreTableCatalogObjectBackend<S>
|
||||||
where
|
where
|
||||||
S: StorageAPI,
|
S: StorageAPI + NamespaceLocking,
|
||||||
{
|
{
|
||||||
pub fn new(store: Arc<S>) -> Self {
|
pub fn new(store: Arc<S>) -> Self {
|
||||||
Self { store }
|
Self { store }
|
||||||
@@ -1558,7 +1558,7 @@ where
|
|||||||
|
|
||||||
impl<S> EcStoreTableCatalogObjectBackend<S>
|
impl<S> EcStoreTableCatalogObjectBackend<S>
|
||||||
where
|
where
|
||||||
S: StorageAPI,
|
S: StorageAPI + NamespaceLocking,
|
||||||
{
|
{
|
||||||
async fn read_object_with_options(
|
async fn read_object_with_options(
|
||||||
&self,
|
&self,
|
||||||
|
|||||||
Reference in New Issue
Block a user