Skip to content

Commit d8bbd33

Browse files
gustavoavenameta-codesync[bot]
authored andcommitted
code_tenting: Extract CountingBlobstore into separate crate
Summary: ## This stack Introduces AclManifest, a **sparse hierarchical derived data type** that tracks which directories in a Mononoke repo are ACL-restricted (have `.slacl` files). Unlike a naive full-tree manifest, AclManifest only stores **restriction roots** (directories with `.slacl` files) and their **ancestor waypoints**, making it O(depth × restricted_roots) instead of O(repo). Each restriction root's ACL metadata is stored as a separate **content-addressed blob** (`AclManifestEntryBlob`), so identical `.slacl` files at different paths share storage. Key properties: - **Sparse**: only restriction roots and their ancestor waypoints have entries — no entries for unrestricted directories - **Content-addressed ACL blobs**: identical `.slacl` files produce the same `AclManifestEntryBlobId` - **Rollup pruning**: `has_restricted` rollup data enables skipping subtrees with no restrictions - **Incremental derivation**: reuses unchanged subtrees from parent manifests - **Non-incremental derivation**: derives from scratch using BSSMV3 (basename lookup for `.slacl` files) + Fsnodes (content ID resolution) - **JustKnob gated**: rollout controlled per-repo via `scm/mononoke:use_acl_manifest_for_restricted_paths` - **HgAugmentedManifest integration**: optional `acl_manifest_directory_id` pointer enables SLAPI `check_permission` lookups without SQL path resolution ## This diff As title. Will use it in D95201153. Reviewed By: YousefSalama Differential Revision: D95210909 fbshipit-source-id: b4e93080364b26364bb668b3dadd80d298e1c447
1 parent bce6637 commit d8bbd33

9 files changed

Lines changed: 178 additions & 109 deletions

File tree

eden/mononoke/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ members = [
6262
"blobstore/blobstore_stats",
6363
"blobstore/cacheblob",
6464
"blobstore/chaosblob",
65+
"blobstore/counting_blob",
6566
"blobstore/delayblob",
6667
"blobstore/ephemeral_blobstore",
6768
"blobstore/factory",

eden/mononoke/benchmarks/derived_data/BUCK

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,13 @@ rust_library(
88
srcs = ["benchmark_utils.rs"],
99
deps = [
1010
"fbsource//third-party/rust:anyhow",
11-
"fbsource//third-party/rust:async-trait",
1211
"fbsource//third-party/rust:rand_08",
1312
"//common/rust/shed/facet:facet",
1413
"//common/rust/shed/fbinit:fbinit",
1514
"//eden/mononoke/blobstore:blobstore",
15+
"//eden/mononoke/blobstore:counting_blob",
1616
"//eden/mononoke/blobstore:delayblob",
1717
"//eden/mononoke/blobstore:memblob",
18-
"//eden/mononoke/mononoke_types:mononoke_types",
1918
"//eden/mononoke/repo_attributes/bonsai_hg_mapping:bonsai_hg_mapping",
2019
"//eden/mononoke/repo_attributes/bookmarks:bookmarks",
2120
"//eden/mononoke/repo_attributes/commit_graph/commit_graph:commit_graph",
@@ -24,7 +23,6 @@ rust_library(
2423
"//eden/mononoke/repo_attributes/repo_derived_data:repo_derived_data",
2524
"//eden/mononoke/repo_attributes/repo_identity:repo_identity",
2625
"//eden/mononoke/repo_factory:test_repo_factory",
27-
"//eden/mononoke/servers/slapi/slapi_server/context:context",
2826
],
2927
)
3028

eden/mononoke/benchmarks/derived_data/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,14 +28,14 @@ path = "benchmark_large_directory.rs"
2828

2929
[dependencies]
3030
anyhow = "1.0.101"
31-
async-trait = "0.1.86"
3231
blobstore = { version = "0.1.0", path = "../../blobstore" }
3332
bonsai_hg_mapping = { version = "0.1.0", path = "../../repo_attributes/bonsai_hg_mapping" }
3433
bookmarks = { version = "0.1.0", path = "../../repo_attributes/bookmarks" }
3534
clap = { version = "4.5.42", features = ["derive", "env", "string", "unicode", "wrap_help"] }
3635
commit_graph = { version = "0.1.0", path = "../../repo_attributes/commit_graph/commit_graph" }
3736
content_manifest_derivation = { version = "0.1.0", path = "../../derived_data/content_manifest_derivation" }
3837
context = { version = "0.1.0", path = "../../servers/slapi/slapi_server/context" }
38+
counting_blob = { version = "0.1.0", path = "../../blobstore/counting_blob" }
3939
criterion = { version = "0.5.1", features = ["async_tokio", "csv_output"] }
4040
delayblob = { version = "0.1.0", path = "../../blobstore/delayblob" }
4141
deleted_manifest = { version = "0.1.0", path = "../../derived_data/deleted_manifest" }

eden/mononoke/benchmarks/derived_data/benchmark_hg_augmented_manifest_batch.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -263,7 +263,8 @@ async fn main(fb: FacebookInit) -> Result<()> {
263263
.await?;
264264

265265
let derive_time = derive_start.elapsed();
266-
let (total_gets, total_puts, _) = counters.snapshot();
266+
let snap = counters.snapshot();
267+
let (total_gets, total_puts) = (snap.gets, snap.puts);
267268

268269
// Print results
269270
println!();

eden/mononoke/benchmarks/derived_data/benchmark_hg_manifest.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,8 @@ async fn main(fb: FacebookInit) -> Result<()> {
156156
.timed()
157157
.await;
158158
result?;
159-
let (gets, puts, _) = counters.snapshot();
159+
let snap = counters.snapshot();
160+
let (gets, puts) = (snap.gets, snap.puts);
160161

161162
println!();
162163
println!(

eden/mononoke/benchmarks/derived_data/benchmark_utils.rs

Lines changed: 3 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -14,28 +14,23 @@
1414
//! - Helper functions for creating repos and generating test data
1515
1616
use std::sync::Arc;
17-
use std::sync::atomic::AtomicU64;
18-
use std::sync::atomic::Ordering;
1917

2018
use anyhow::Context;
2119
use anyhow::Result;
22-
use async_trait::async_trait;
2320
use blobstore::Blobstore;
24-
use blobstore::BlobstoreGetData;
25-
use blobstore::BlobstoreIsPresent;
26-
use blobstore::OverwriteStatus;
2721
use blobstore::PutBehaviour;
2822
use bonsai_hg_mapping::BonsaiHgMapping;
2923
use bookmarks::Bookmarks;
3024
use commit_graph::CommitGraph;
3125
use commit_graph::CommitGraphWriter;
32-
use context::CoreContext;
26+
pub use counting_blob::BlobstoreCounters;
27+
pub use counting_blob::BlobstoreCountersSnapshot;
28+
pub use counting_blob::CountingBlobstore;
3329
use delayblob::DelayedBlobstore;
3430
use delayblob::Normal;
3531
use fbinit::FacebookInit;
3632
use filestore::FilestoreConfig;
3733
use memblob::Memblob;
38-
use mononoke_types::BlobstoreBytes;
3934
use rand::Rng;
4035
use rand::distributions::Alphanumeric;
4136
use repo_blobstore::RepoBlobstore;
@@ -49,101 +44,6 @@ pub const GET_LATENCY_STDDEV_MS: f64 = 3.0;
4944
pub const PUT_LATENCY_MS: f64 = 15.0;
5045
pub const PUT_LATENCY_STDDEV_MS: f64 = 5.0;
5146

52-
/// Counters for blobstore operations
53-
#[derive(Debug, Default)]
54-
pub struct BlobstoreCounters {
55-
pub gets: AtomicU64,
56-
pub puts: AtomicU64,
57-
pub is_presents: AtomicU64,
58-
}
59-
60-
impl BlobstoreCounters {
61-
pub fn new() -> Self {
62-
Self::default()
63-
}
64-
65-
pub fn reset(&self) {
66-
self.gets.store(0, Ordering::SeqCst);
67-
self.puts.store(0, Ordering::SeqCst);
68-
self.is_presents.store(0, Ordering::SeqCst);
69-
}
70-
71-
pub fn snapshot(&self) -> (u64, u64, u64) {
72-
(
73-
self.gets.load(Ordering::SeqCst),
74-
self.puts.load(Ordering::SeqCst),
75-
self.is_presents.load(Ordering::SeqCst),
76-
)
77-
}
78-
}
79-
80-
/// A blobstore wrapper that counts operations
81-
#[derive(Debug)]
82-
pub struct CountingBlobstore<B> {
83-
inner: B,
84-
counters: Arc<BlobstoreCounters>,
85-
}
86-
87-
impl<B: std::fmt::Display> std::fmt::Display for CountingBlobstore<B> {
88-
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
89-
write!(f, "CountingBlobstore<{}>", &self.inner)
90-
}
91-
}
92-
93-
impl<B> CountingBlobstore<B> {
94-
pub fn new(inner: B, counters: Arc<BlobstoreCounters>) -> Self {
95-
Self { inner, counters }
96-
}
97-
}
98-
99-
#[async_trait]
100-
impl<B: Blobstore> Blobstore for CountingBlobstore<B> {
101-
async fn get<'a>(
102-
&'a self,
103-
ctx: &'a CoreContext,
104-
key: &'a str,
105-
) -> Result<Option<BlobstoreGetData>> {
106-
self.counters.gets.fetch_add(1, Ordering::SeqCst);
107-
self.inner.get(ctx, key).await
108-
}
109-
110-
async fn is_present<'a>(
111-
&'a self,
112-
ctx: &'a CoreContext,
113-
key: &'a str,
114-
) -> Result<BlobstoreIsPresent> {
115-
self.counters.is_presents.fetch_add(1, Ordering::SeqCst);
116-
self.inner.is_present(ctx, key).await
117-
}
118-
119-
async fn unlink<'a>(&'a self, ctx: &'a CoreContext, key: &'a str) -> Result<()> {
120-
self.inner.unlink(ctx, key).await
121-
}
122-
123-
async fn put_explicit<'a>(
124-
&'a self,
125-
ctx: &'a CoreContext,
126-
key: String,
127-
value: BlobstoreBytes,
128-
put_behaviour: PutBehaviour,
129-
) -> Result<OverwriteStatus> {
130-
self.counters.puts.fetch_add(1, Ordering::SeqCst);
131-
self.inner
132-
.put_explicit(ctx, key, value, put_behaviour)
133-
.await
134-
}
135-
136-
async fn put_with_status<'a>(
137-
&'a self,
138-
ctx: &'a CoreContext,
139-
key: String,
140-
value: BlobstoreBytes,
141-
) -> Result<OverwriteStatus> {
142-
self.counters.puts.fetch_add(1, Ordering::SeqCst);
143-
self.inner.put_with_status(ctx, key, value).await
144-
}
145-
}
146-
14747
/// Repository container with all facets needed for benchmarks
14848
#[facet::container]
14949
#[derive(Clone)]

eden/mononoke/blobstore/BUCK

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -491,6 +491,18 @@ rust_library(
491491
],
492492
)
493493

494+
rust_library(
495+
name = "counting_blob",
496+
srcs = glob(["counting_blob/src/*.rs"]),
497+
autocargo = {"cargo_toml_dir": "counting_blob"},
498+
deps = [
499+
"fbsource//third-party/rust:anyhow",
500+
"fbsource//third-party/rust:async-trait",
501+
":blobstore",
502+
"//eden/mononoke/servers/slapi/slapi_server/context:context",
503+
],
504+
)
505+
494506
rust_library(
495507
name = "logblob",
496508
srcs = glob(["logblob/src/*.rs"]),
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# @generated by autocargo from //eden/mononoke/blobstore:counting_blob
2+
3+
[package]
4+
name = "counting_blob"
5+
version = "0.1.0"
6+
authors = ["Facebook"]
7+
edition = "2024"
8+
license = "GPLv2+"
9+
10+
[dependencies]
11+
anyhow = "1.0.101"
12+
async-trait = "0.1.86"
13+
blobstore = { version = "0.1.0", path = ".." }
14+
context = { version = "0.1.0", path = "../../servers/slapi/slapi_server/context" }
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This software may be used and distributed according to the terms of the
5+
* GNU General Public License version 2.
6+
*/
7+
8+
//! A blobstore wrapper that counts operations via shared atomic counters.
9+
//!
10+
//! Unlike `CountedBlobstore` (ODS timeseries) or `LogBlob` (CoreContext perf
11+
//! counters), this wrapper exposes operation counts programmatically through
12+
//! `BlobstoreCounters`, making it suitable for tests and benchmarks that need
13+
//! to assert on the number of blobstore operations performed.
14+
15+
use std::sync::Arc;
16+
use std::sync::atomic::AtomicU64;
17+
use std::sync::atomic::Ordering;
18+
19+
use anyhow::Result;
20+
use async_trait::async_trait;
21+
use blobstore::Blobstore;
22+
use blobstore::BlobstoreBytes;
23+
use blobstore::BlobstoreGetData;
24+
use blobstore::BlobstoreIsPresent;
25+
use blobstore::OverwriteStatus;
26+
use blobstore::PutBehaviour;
27+
use context::CoreContext;
28+
29+
/// Shared atomic counters for blobstore operations.
30+
#[derive(Debug, Default)]
31+
pub struct BlobstoreCounters {
32+
pub gets: AtomicU64,
33+
pub puts: AtomicU64,
34+
pub is_presents: AtomicU64,
35+
}
36+
37+
/// Snapshot of blobstore operation counts at a point in time.
38+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
39+
pub struct BlobstoreCountersSnapshot {
40+
pub gets: u64,
41+
pub puts: u64,
42+
pub is_presents: u64,
43+
}
44+
45+
impl std::ops::Sub for BlobstoreCountersSnapshot {
46+
type Output = BlobstoreCountersSnapshot;
47+
48+
fn sub(self, rhs: BlobstoreCountersSnapshot) -> BlobstoreCountersSnapshot {
49+
BlobstoreCountersSnapshot {
50+
gets: self.gets - rhs.gets,
51+
puts: self.puts - rhs.puts,
52+
is_presents: self.is_presents - rhs.is_presents,
53+
}
54+
}
55+
}
56+
57+
impl BlobstoreCounters {
58+
pub fn new() -> Self {
59+
Self::default()
60+
}
61+
62+
pub fn reset(&self) {
63+
self.gets.store(0, Ordering::SeqCst);
64+
self.puts.store(0, Ordering::SeqCst);
65+
self.is_presents.store(0, Ordering::SeqCst);
66+
}
67+
68+
pub fn snapshot(&self) -> BlobstoreCountersSnapshot {
69+
BlobstoreCountersSnapshot {
70+
gets: self.gets.load(Ordering::SeqCst),
71+
puts: self.puts.load(Ordering::SeqCst),
72+
is_presents: self.is_presents.load(Ordering::SeqCst),
73+
}
74+
}
75+
}
76+
77+
/// A blobstore wrapper that counts operations via shared atomic counters.
78+
#[derive(Debug)]
79+
pub struct CountingBlobstore<B> {
80+
inner: B,
81+
counters: Arc<BlobstoreCounters>,
82+
}
83+
84+
impl<B: std::fmt::Display> std::fmt::Display for CountingBlobstore<B> {
85+
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
86+
write!(f, "CountingBlobstore<{}>", &self.inner)
87+
}
88+
}
89+
90+
impl<B> CountingBlobstore<B> {
91+
pub fn new(inner: B, counters: Arc<BlobstoreCounters>) -> Self {
92+
Self { inner, counters }
93+
}
94+
}
95+
96+
#[async_trait]
97+
impl<B: Blobstore> Blobstore for CountingBlobstore<B> {
98+
async fn get<'a>(
99+
&'a self,
100+
ctx: &'a CoreContext,
101+
key: &'a str,
102+
) -> Result<Option<BlobstoreGetData>> {
103+
self.counters.gets.fetch_add(1, Ordering::SeqCst);
104+
self.inner.get(ctx, key).await
105+
}
106+
107+
async fn is_present<'a>(
108+
&'a self,
109+
ctx: &'a CoreContext,
110+
key: &'a str,
111+
) -> Result<BlobstoreIsPresent> {
112+
self.counters.is_presents.fetch_add(1, Ordering::SeqCst);
113+
self.inner.is_present(ctx, key).await
114+
}
115+
116+
async fn unlink<'a>(&'a self, ctx: &'a CoreContext, key: &'a str) -> Result<()> {
117+
self.inner.unlink(ctx, key).await
118+
}
119+
120+
async fn put_explicit<'a>(
121+
&'a self,
122+
ctx: &'a CoreContext,
123+
key: String,
124+
value: BlobstoreBytes,
125+
put_behaviour: PutBehaviour,
126+
) -> Result<OverwriteStatus> {
127+
self.counters.puts.fetch_add(1, Ordering::SeqCst);
128+
self.inner
129+
.put_explicit(ctx, key, value, put_behaviour)
130+
.await
131+
}
132+
133+
async fn put_with_status<'a>(
134+
&'a self,
135+
ctx: &'a CoreContext,
136+
key: String,
137+
value: BlobstoreBytes,
138+
) -> Result<OverwriteStatus> {
139+
self.counters.puts.fetch_add(1, Ordering::SeqCst);
140+
self.inner.put_with_status(ctx, key, value).await
141+
}
142+
}

0 commit comments

Comments
 (0)