Skip to content

Commit 81d584a

Browse files
RajivTSfacebook-github-bot
authored andcommitted
Implement cached git ref content mapping using pub sub model
Summary: With the work done so far, I can finally implement what I actually wanted :) `git_ref_content_mapping` stores the mapping of refs (or bookmarks as they are known in Mononoke) to git shas of blobs or trees. Since refs pointings to content types is not standard in Mononoke, we have created this alternate route that bypasses the whole bookmark mechanism. As part of Git read protocol, we end up fetching all the refs all the time unfortunately. Since `git_ref_content_mapping` does not have a cached implementation, this means queries to the DB. We can't directly cache it like other mappings (bonsai_git_mapping, bonsai_hg_mapping, etc.) because this mapping also supports updates. We need a WBC kind-of mechanism that keeps the cache up to date without incessantly polling the DB. `CachedGitRefContentMapping` using pub-sub scribed based notification mechanism is the answer. Differential Revision: D75061565 fbshipit-source-id: 7dbabe0948d4bc9f7b6343a67fd7fb342a3f4cd7
1 parent 5488aa8 commit 81d584a

File tree

6 files changed

+228
-3
lines changed

6 files changed

+228
-3
lines changed

eden/mononoke/git_ref_content_mapping/BUCK

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,20 @@ rust_library(
99
"src/**/*.rs",
1010
"schemas/**/*.sql",
1111
]),
12+
named_deps = {"slog": "//common/rust/shed/tracing_slog_compat:tracing_slog_compat"},
1213
deps = [
1314
"fbsource//third-party/rust:anyhow",
15+
"fbsource//third-party/rust:arc-swap",
1416
"fbsource//third-party/rust:async-trait",
17+
"fbsource//third-party/rust:tokio",
18+
"//common/rust/shed/cloned:cloned",
1519
"//common/rust/shed/facet:facet",
20+
"//common/rust/shed/justknobs_stub:justknobs",
21+
"//common/rust/shed/stats:stats",
1622
"//eden/mononoke/common/rust/sql_ext:sql_ext",
1723
"//eden/mononoke/common/sql_construct:sql_construct",
24+
"//eden/mononoke/features/repo_update_logger:repo_update_logger",
25+
"//eden/mononoke/mononoke_macros:mononoke_macros",
1826
"//eden/mononoke/mononoke_types:mononoke_types",
1927
],
2028
)

eden/mononoke/git_ref_content_mapping/Cargo.toml

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,21 @@ path = "test/main.rs"
1313

1414
[dependencies]
1515
anyhow = "1.0.95"
16+
arc-swap = { version = "1.5", features = ["weak"] }
1617
async-trait = "0.1.86"
18+
cloned = { version = "0.1.0", git = "https://github.com/facebookexperimental/rust-shed.git", branch = "main" }
1719
facet = { version = "0.1.0", git = "https://github.com/facebookexperimental/rust-shed.git", branch = "main" }
20+
justknobs = { version = "0.1.0", git = "https://github.com/facebookexperimental/rust-shed.git", branch = "main" }
21+
mononoke_macros = { version = "0.1.0", path = "../mononoke_macros" }
1822
mononoke_types = { version = "0.1.0", path = "../mononoke_types" }
23+
repo_update_logger = { version = "0.1.0", path = "../features/repo_update_logger" }
24+
slog = { package = "tracing_slog_compat", version = "0.1.0", git = "https://github.com/facebookexperimental/rust-shed.git", branch = "main" }
1925
sql_construct = { version = "0.1.0", path = "../common/sql_construct" }
2026
sql_ext = { version = "0.1.0", path = "../common/rust/sql_ext" }
27+
stats = { version = "0.1.0", git = "https://github.com/facebookexperimental/rust-shed.git", branch = "main" }
28+
tokio = { version = "1.45.0", features = ["full", "test-util", "tracing"] }
2129

2230
[dev-dependencies]
2331
fbinit = { version = "0.2.0", git = "https://github.com/facebookexperimental/rust-shed.git", branch = "main" }
2432
fbinit-tokio = { version = "0.1.2", git = "https://github.com/facebookexperimental/rust-shed.git", branch = "main" }
25-
mononoke_macros = { version = "0.1.0", path = "../mononoke_macros" }
2633
mononoke_types-mocks = { version = "0.1.0", path = "../mononoke_types/mocks" }
Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
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+
use std::sync::Arc;
9+
10+
use anyhow::Context;
11+
use anyhow::Result;
12+
use arc_swap::ArcSwap;
13+
use async_trait::async_trait;
14+
use cloned::cloned;
15+
use mononoke_macros::mononoke;
16+
use mononoke_types::RepositoryId;
17+
use repo_update_logger::GitContentRefInfo;
18+
use slog::error;
19+
use slog::info;
20+
use stats::define_stats;
21+
use stats::prelude::TimeseriesStatic;
22+
use tokio::sync::broadcast::Receiver;
23+
use tokio::task::JoinHandle;
24+
25+
use crate::GitRefContentMapping;
26+
use crate::GitRefContentMappingEntry;
27+
28+
type Swappable<T> = Arc<ArcSwap<T>>;
29+
30+
define_stats! {
31+
prefix = "mononoke.content_refs_cache_update";
32+
update_failure_count: timeseries(Average, Sum, Count),
33+
}
34+
35+
pub struct CachedGitRefContentMapping {
36+
inner: Arc<dyn GitRefContentMapping>,
37+
entries: Swappable<Vec<GitRefContentMappingEntry>>,
38+
updater_task: JoinHandle<()>,
39+
}
40+
41+
#[allow(dead_code)]
42+
impl CachedGitRefContentMapping {
43+
pub async fn new(
44+
inner: Arc<dyn GitRefContentMapping>,
45+
update_notification_receiver: Receiver<GitContentRefInfo>,
46+
logger: slog::Logger,
47+
) -> Result<Self> {
48+
let initial_entries = inner
49+
.get_all_entries()
50+
.await
51+
.context("Error while getting initial set of git ref content mapping entries")?;
52+
let entries = Arc::new(ArcSwap::from_pointee(initial_entries));
53+
let updater_task = mononoke::spawn_task({
54+
cloned!(entries, inner);
55+
update_cache(entries, inner, update_notification_receiver, logger)
56+
});
57+
Ok(Self {
58+
inner,
59+
entries,
60+
updater_task,
61+
})
62+
}
63+
}
64+
65+
async fn update_cache(
66+
entries: Swappable<Vec<GitRefContentMappingEntry>>,
67+
bonsai_tag_mapping: Arc<dyn GitRefContentMapping>,
68+
mut update_notification_receiver: Receiver<GitContentRefInfo>,
69+
logger: slog::Logger,
70+
) {
71+
loop {
72+
let fallible_notification = update_notification_receiver
73+
.recv()
74+
.await
75+
.context("Error while receiving update notification");
76+
match fallible_notification {
77+
Ok(_) => {
78+
info!(
79+
logger,
80+
"Received update notification from scribe for updating content refs cache"
81+
);
82+
match bonsai_tag_mapping.get_all_entries().await {
83+
Ok(new_entries) => {
84+
let new_entries = Arc::new(new_entries);
85+
entries.store(new_entries);
86+
info!(
87+
logger,
88+
"Successfully updated the cache with new entries from the inner git ref content mapping"
89+
);
90+
}
91+
Err(e) => {
92+
error!(
93+
logger,
94+
"Failure in updating the cache with new entries from the inner git ref content mapping: {:?}",
95+
e
96+
);
97+
STATS::update_failure_count.add_value(1);
98+
}
99+
}
100+
}
101+
Err(e) => {
102+
error!(
103+
logger,
104+
"Failure in receiving notification from tags scribe category. Error: {:?}", e
105+
);
106+
STATS::update_failure_count.add_value(1);
107+
}
108+
}
109+
}
110+
}
111+
112+
impl Drop for CachedGitRefContentMapping {
113+
fn drop(&mut self) {
114+
// Need to abort the task before dropping the cache mapping
115+
self.updater_task.abort();
116+
}
117+
}
118+
119+
#[async_trait]
120+
impl GitRefContentMapping for CachedGitRefContentMapping {
121+
/// The repository for which this mapping has been created
122+
fn repo_id(&self) -> RepositoryId {
123+
self.inner.repo_id()
124+
}
125+
126+
/// Fetch all the tag mapping entries for the given repo
127+
async fn get_all_entries(&self) -> Result<Vec<GitRefContentMappingEntry>> {
128+
if justknobs::eval(
129+
"scm/mononoke:enable_git_ref_content_mapping_caching",
130+
None,
131+
None,
132+
)
133+
.unwrap_or(false)
134+
{
135+
Ok(self.entries.load_full().to_vec())
136+
} else {
137+
self.inner.get_all_entries().await
138+
}
139+
}
140+
141+
async fn get_entry_by_ref_name(
142+
&self,
143+
ref_name: String,
144+
) -> Result<Option<GitRefContentMappingEntry>> {
145+
if justknobs::eval(
146+
"scm/mononoke:enable_git_ref_content_mapping_caching",
147+
None,
148+
None,
149+
)
150+
.unwrap_or(false)
151+
{
152+
let entry = self
153+
.entries
154+
.load()
155+
.iter()
156+
.find(|&entry| entry.ref_name == ref_name)
157+
.cloned();
158+
Ok(entry)
159+
} else {
160+
self.inner.get_entry_by_ref_name(ref_name).await
161+
}
162+
}
163+
164+
async fn add_or_update_mappings(&self, entries: Vec<GitRefContentMappingEntry>) -> Result<()> {
165+
// Writes are directly delegated to inner git ref content mapping
166+
self.inner.add_or_update_mappings(entries).await
167+
}
168+
169+
async fn delete_mappings_by_name(&self, ref_names: Vec<String>) -> Result<()> {
170+
// Writes are directly delegated to inner git ref content mapping
171+
self.inner.delete_mappings_by_name(ref_names).await
172+
}
173+
}

eden/mononoke/git_ref_content_mapping/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,15 @@
55
* GNU General Public License version 2.
66
*/
77

8+
mod cache;
89
mod sql;
910

1011
use anyhow::Result;
1112
use async_trait::async_trait;
1213
use mononoke_types::RepositoryId;
1314
use mononoke_types::hash::GitSha1;
1415

16+
pub use crate::cache::CachedGitRefContentMapping;
1517
pub use crate::sql::SqlGitRefContentMapping;
1618
pub use crate::sql::SqlGitRefContentMappingBuilder;
1719

eden/mononoke/repo_factory/src/lib.rs

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ use filestore::ArcFilestoreConfig;
114114
use filestore::FilestoreConfig;
115115
use futures_watchdog::WatchdogExt;
116116
use git_ref_content_mapping::ArcGitRefContentMapping;
117+
use git_ref_content_mapping::CachedGitRefContentMapping;
117118
use git_ref_content_mapping::SqlGitRefContentMappingBuilder;
118119
use git_source_of_truth::ArcGitSourceOfTruthConfig;
119120
use git_source_of_truth::SqlGitSourceOfTruthConfigBuilder;
@@ -1122,14 +1123,39 @@ impl RepoFactory {
11221123
&self,
11231124
repo_config: &ArcRepoConfig,
11241125
repo_identity: &ArcRepoIdentity,
1126+
repo_event_publisher: &ArcRepoEventPublisher,
11251127
) -> Result<ArcGitRefContentMapping> {
11261128
let git_ref_content_mapping = self
11271129
.open_sql::<SqlGitRefContentMappingBuilder>(repo_config)
11281130
.await
11291131
.context(RepoFactoryError::GitRefContentMapping)?
11301132
.build(repo_identity.id());
1131-
// TODO(rajshar): Add caching for git_ref_content_mapping
1132-
Ok(Arc::new(git_ref_content_mapping))
1133+
let repo_name = repo_identity.name();
1134+
if justknobs::eval(
1135+
"scm/mononoke:enable_git_ref_content_mapping_caching",
1136+
None,
1137+
Some(repo_name),
1138+
)
1139+
.unwrap_or(false)
1140+
{
1141+
let logger = self.env.logger.clone();
1142+
match repo_event_publisher.subscribe_for_content_refs_updates(&repo_name.to_string()) {
1143+
Ok(update_notification_receiver) => {
1144+
let cached_git_ref_content_mapping = CachedGitRefContentMapping::new(
1145+
Arc::new(git_ref_content_mapping),
1146+
update_notification_receiver,
1147+
logger,
1148+
)
1149+
.await?;
1150+
Ok(Arc::new(cached_git_ref_content_mapping))
1151+
}
1152+
// The scribe configuration does not exist for content ref updates for this repo, so use the non-cached
1153+
// version of git_ref_content_mapping
1154+
Err(_) => Ok(Arc::new(git_ref_content_mapping)),
1155+
}
1156+
} else {
1157+
Ok(Arc::new(git_ref_content_mapping))
1158+
}
11331159
}
11341160

11351161
pub async fn git_bundle_uri(

eden/mononoke/tests/integration/mononoke_git_server/test-mononoke-git-server-push-with-tree-and-blob-ref.t

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,22 @@
55
# directory of this source tree.
66

77
$ export GIT_CONTENT_REFS_SCRIBE_CATEGORY=mononoke_git_content_refs
8+
$ export CONTENT_REFS_SCRIBE_CATEGORY=mononoke_git_content_refs
89
$ export MONONOKE_TEST_SCRIBE_LOGGING_DIRECTORY=$TESTTMP/scribe_logs/
910
$ . "${TEST_FIXTURES}/library.sh"
1011
$ REPOTYPE="blob_files"
1112
$ setup_common_config $REPOTYPE
1213
$ GIT_REPO_ORIGIN="${TESTTMP}/origin/repo-git"
1314
$ GIT_REPO="${TESTTMP}/repo-git"
1415

16+
$ merge_just_knobs <<EOF
17+
> {
18+
> "bools": {
19+
> "scm/mononoke:enable_git_ref_content_mapping_caching": true
20+
> }
21+
> }
22+
> EOF
23+
1524
# Setup git repository
1625
$ mkdir -p "$GIT_REPO_ORIGIN"
1726
$ cd "$GIT_REPO_ORIGIN"

0 commit comments

Comments
 (0)