Skip to content
This repository was archived by the owner on Oct 19, 2026. It is now read-only.

Commit 3d06d60

Browse files
authored
perf(import): cut receive-pack filepath writes and root-lock wait (#2177)
* perf(import): cut receive-pack filepath writes and root-lock wait Batch blob file_path updates, hold the monorepo root lock only for the CAS attach (10ms Redis retry), and replace git_repo LIKE seq scans with index range / parent-walk lookups. crates-sync ETA now uses push rate instead of skip-inflated throughput. * fix(import): retry git_blob insert deadlocks and upsert git_repo by path Serialize 1000-row chunks, insert hashes in sorted order, and retry Postgres 40P01/40001 so concurrent receive-packs stop deadlocking on git_blob unique indexes. Reuse the persisted git_repo row on repo_path conflict instead of the caller's unused snowflake id. * fix(idgen): claim a unique snowflake worker_id per pod Hardcoded worker_id(1) made every replica share one partition, so concurrent generate_id() hit git_repo_pkey. Claim mega:snowflake:worker:N in Redis at boot (8+8 bits), with env/hash fallback when Redis is down.
1 parent 3a89570 commit 3d06d60

19 files changed

Lines changed: 1354 additions & 265 deletions

File tree

‎Cargo.lock‎

Lines changed: 52 additions & 52 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎ceres/src/application/api_service/mono/sync.rs‎

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,8 @@ impl MonoApiService {
3737
));
3838

3939
for attempt in 0..MAX_ATTACH_ATTEMPTS {
40-
let guard = redlock.clone().lock().await?;
40+
// Same split as import receive-pack attach: walk/upload off the
41+
// global root lock; CAS on mega_refs still serializes the root update.
4142
let root_ref = storage
4243
.get_main_ref("/")
4344
.await?
@@ -68,6 +69,24 @@ impl MonoApiService {
6869
.save_blobs(&new_commit.id.to_string(), vec![gitkeep_blob])
6970
.await?;
7071

72+
let guard = redlock.clone().lock().await?;
73+
let current_root = storage
74+
.get_main_ref("/")
75+
.await?
76+
.ok_or_else(|| MegaError::Other("root ref not found".to_string()))?;
77+
if current_root.ref_commit_hash != expected_commit
78+
|| current_root.ref_tree_hash != expected_tree
79+
|| current_root.id != root_ref_id
80+
{
81+
let _ = guard.unlock().await;
82+
tracing::warn!(
83+
attempt,
84+
repo_path = %path,
85+
"attach_project_path_to_monorepo_root: root ref moved before txn, retrying"
86+
);
87+
continue;
88+
}
89+
7190
let txn = self.storage().begin_db_transaction().await?;
7291
match storage
7392
.attach_to_monorepo_parent_in_txn(

‎ceres/src/application/code_edit/post_receive/import.rs‎

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -67,12 +67,9 @@ pub async fn dispatch_import_receive_pack_finalized(
6767
let mut root_lock_wait_sum_ms: u128 = 0;
6868

6969
for attempt in 0..MAX_ATTACH_ATTEMPTS {
70-
let t_lock = Instant::now();
71-
let guard = unpack_redlock.clone().lock().await?;
72-
let lock_wait_ms = t_lock.elapsed().as_millis();
73-
root_lock_wait_max_ms = root_lock_wait_max_ms.max(lock_wait_ms);
74-
root_lock_wait_sum_ms += lock_wait_ms;
75-
70+
// Tree walk + .gitkeep upload are the expensive part of attach (~20ms of
71+
// the former 25ms hold). Do them without the global root lock; the CAS
72+
// on mega_refs still rejects a stale snapshot.
7673
let root_ref = mono_storage
7774
.get_main_ref("/")
7875
.await?
@@ -99,6 +96,29 @@ pub async fn dispatch_import_receive_pack_finalized(
9996
.save_blobs(&new_commit.id.to_string(), vec![gitkeep_blob])
10097
.await?;
10198

99+
let t_lock = Instant::now();
100+
let guard = unpack_redlock.clone().lock().await?;
101+
let lock_wait_ms = t_lock.elapsed().as_millis();
102+
root_lock_wait_max_ms = root_lock_wait_max_ms.max(lock_wait_ms);
103+
root_lock_wait_sum_ms += lock_wait_ms;
104+
105+
let current_root = mono_storage
106+
.get_main_ref("/")
107+
.await?
108+
.ok_or_else(|| MegaError::Other("root ref not found".to_string()))?;
109+
if current_root.ref_commit_hash != expected_commit
110+
|| current_root.ref_tree_hash != expected_tree
111+
|| current_root.id != root_ref_id
112+
{
113+
let _ = guard.unlock().await;
114+
tracing::warn!(
115+
attempt = attempt,
116+
repo_path = %repo_path.display(),
117+
"attach_to_monorepo_parent: root ref moved before txn, retrying"
118+
);
119+
continue;
120+
}
121+
102122
let txn = storage.begin_db_transaction().await?;
103123
let git_db = storage.git_db_storage();
104124
for &cmd in &branch_cmds {

‎ceres/src/transport/pack/import_repo.rs‎

Lines changed: 183 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -472,44 +472,50 @@ impl RepoHandler for ImportRepo {
472472
.unwrap()
473473
.clone(),
474474
);
475-
self.traverses_and_update_filepath(root_tree, PathBuf::new())
475+
let pairs = collect_git_blob_filepaths(
476+
self.storage.git_db_storage(),
477+
self.repo.repo_id,
478+
root_tree,
479+
PathBuf::new(),
480+
)
481+
.await?;
482+
self.storage
483+
.git_db_storage()
484+
.update_git_blob_filepaths(self.repo.repo_id, pairs)
476485
.await?;
477486
Ok(())
478487
}
479488
}
480489

481-
impl ImportRepo {
482-
#[async_recursion]
483-
async fn traverses_and_update_filepath(
484-
&self,
485-
tree: Tree,
486-
path: PathBuf,
487-
) -> Result<(), MegaError> {
488-
for item in tree.tree_items {
489-
if item.is_tree() {
490-
let tree = Tree::from_git_model(
491-
self.storage
492-
.git_db_storage()
493-
.get_tree_by_hash(self.repo.repo_id, &item.id.to_string())
494-
.await?
495-
.unwrap()
496-
.clone(),
497-
);
498-
499-
// 递归调用
500-
self.traverses_and_update_filepath(tree, path.join(item.name))
501-
.await?;
502-
} else {
503-
let id = item.id.to_string();
504-
self.storage
505-
.git_db_storage()
506-
.update_git_blob_filepath(&id, path.join(item.name).to_str().unwrap())
507-
.await?;
508-
}
490+
#[async_recursion]
491+
pub(crate) async fn collect_git_blob_filepaths(
492+
storage: GitDbStorage,
493+
repo_id: i64,
494+
tree: Tree,
495+
path: PathBuf,
496+
) -> Result<Vec<(String, String)>, MegaError> {
497+
let mut pairs = Vec::new();
498+
for item in tree.tree_items {
499+
if item.is_tree() {
500+
let child = Tree::from_git_model(
501+
storage
502+
.get_tree_by_hash(repo_id, &item.id.to_string())
503+
.await?
504+
.unwrap()
505+
.clone(),
506+
);
507+
pairs.extend(
508+
collect_git_blob_filepaths(storage.clone(), repo_id, child, path.join(item.name))
509+
.await?,
510+
);
511+
} else {
512+
pairs.push((
513+
item.id.to_string(),
514+
path.join(item.name).to_str().unwrap().to_string(),
515+
));
509516
}
510-
511-
Ok(())
512517
}
518+
Ok(pairs)
513519
}
514520

515521
async fn process_objects(
@@ -604,6 +610,23 @@ async fn process_objects(
604610
#[cfg(test)]
605611
mod test {
606612
use std::path::PathBuf;
613+
614+
use callisto::{git_blob, git_tree};
615+
use git_internal::internal::object::{
616+
ObjectTrait,
617+
blob::Blob,
618+
tree::{Tree, TreeItem, TreeItemMode},
619+
};
620+
use jupiter::{
621+
sea_orm::{ColumnTrait, EntityTrait, IntoActiveModel, QueryFilter},
622+
storage::base_storage::StorageConnector,
623+
tests::test_storage,
624+
utils::converter::FromGitModel,
625+
};
626+
use tempfile::TempDir;
627+
628+
use super::collect_git_blob_filepaths;
629+
607630
#[test]
608631
pub fn test_recurse_tree() {
609632
let path = PathBuf::from("/third-party/crates/tokio/tokio-console");
@@ -612,4 +635,133 @@ mod test {
612635
println!("{path:?}");
613636
}
614637
}
638+
639+
#[tokio::test]
640+
async fn collect_and_batch_update_nested_crate_tree() {
641+
let dir = TempDir::new().unwrap();
642+
let storage = test_storage(dir.path()).await;
643+
let stg = storage.git_db_storage();
644+
let repo_id = 11i64;
645+
646+
let cargo = Blob::from_content("[package]\nname = \"demo\"\n");
647+
let lib = Blob::from_content("pub fn f() {}\n");
648+
let src_tree = Tree::from_tree_items(vec![TreeItem {
649+
mode: TreeItemMode::Blob,
650+
id: lib.id,
651+
name: "lib.rs".into(),
652+
}])
653+
.unwrap();
654+
let root_tree = Tree::from_tree_items(vec![
655+
TreeItem {
656+
mode: TreeItemMode::Blob,
657+
id: cargo.id,
658+
name: "Cargo.toml".into(),
659+
},
660+
TreeItem {
661+
mode: TreeItemMode::Tree,
662+
id: src_tree.id,
663+
name: "src".into(),
664+
},
665+
])
666+
.unwrap();
667+
668+
let now = chrono::Utc::now().naive_utc();
669+
git_blob::Entity::insert_many([
670+
git_blob::Model {
671+
id: 1,
672+
repo_id,
673+
blob_id: cargo.id.to_string(),
674+
name: None,
675+
size: 0,
676+
created_at: now,
677+
pack_id: String::new(),
678+
file_path: String::new(),
679+
pack_offset: 0,
680+
is_delta_in_pack: false,
681+
}
682+
.into_active_model(),
683+
git_blob::Model {
684+
id: 2,
685+
repo_id,
686+
blob_id: lib.id.to_string(),
687+
name: None,
688+
size: 0,
689+
created_at: now,
690+
pack_id: String::new(),
691+
file_path: String::new(),
692+
pack_offset: 0,
693+
is_delta_in_pack: false,
694+
}
695+
.into_active_model(),
696+
])
697+
.exec(stg.get_connection())
698+
.await
699+
.unwrap();
700+
701+
git_tree::Entity::insert_many([
702+
git_tree::Model {
703+
id: 3,
704+
repo_id,
705+
tree_id: src_tree.id.to_string(),
706+
sub_trees: src_tree.to_data().unwrap(),
707+
size: 0,
708+
created_at: now,
709+
pack_id: String::new(),
710+
pack_offset: 0,
711+
}
712+
.into_active_model(),
713+
git_tree::Model {
714+
id: 4,
715+
repo_id,
716+
tree_id: root_tree.id.to_string(),
717+
sub_trees: root_tree.to_data().unwrap(),
718+
size: 0,
719+
created_at: now,
720+
pack_id: String::new(),
721+
pack_offset: 0,
722+
}
723+
.into_active_model(),
724+
])
725+
.exec(stg.get_connection())
726+
.await
727+
.unwrap();
728+
729+
let loaded_root = Tree::from_git_model(
730+
stg.get_tree_by_hash(repo_id, &root_tree.id.to_string())
731+
.await
732+
.unwrap()
733+
.unwrap(),
734+
);
735+
let mut pairs =
736+
collect_git_blob_filepaths(stg.clone(), repo_id, loaded_root, PathBuf::new())
737+
.await
738+
.unwrap();
739+
pairs.sort_by(|a, b| a.1.cmp(&b.1));
740+
assert_eq!(
741+
pairs,
742+
vec![
743+
(cargo.id.to_string(), "Cargo.toml".into()),
744+
(lib.id.to_string(), "src/lib.rs".into()),
745+
]
746+
);
747+
748+
stg.update_git_blob_filepaths(repo_id, pairs).await.unwrap();
749+
750+
let cargo_row = git_blob::Entity::find()
751+
.filter(git_blob::Column::RepoId.eq(repo_id))
752+
.filter(git_blob::Column::BlobId.eq(cargo.id.to_string()))
753+
.one(stg.get_connection())
754+
.await
755+
.unwrap()
756+
.unwrap();
757+
let lib_row = git_blob::Entity::find()
758+
.filter(git_blob::Column::RepoId.eq(repo_id))
759+
.filter(git_blob::Column::BlobId.eq(lib.id.to_string()))
760+
.one(stg.get_connection())
761+
.await
762+
.unwrap()
763+
.unwrap();
764+
assert_eq!(cargo_row.file_path, "Cargo.toml");
765+
assert_eq!(lib_row.file_path, "src/lib.rs");
766+
}
615767
}

0 commit comments

Comments
 (0)