From 4a39ca43c87707b1c8b14f7ea3240af98621ee10 Mon Sep 17 00:00:00 2001 From: Luke Granger-Brown Date: Sun, 11 Dec 2022 21:40:55 +0000 Subject: [PATCH] git: cache the extra metadata table Performance on repositories with many commits is limited somewhat by repeatedly stating the tablestore directory to work out what the head is. By caching the table rather than looking it up from disk on every request, we can much more rapidly satisfy requests. This avoids the pathological case in #845 where jj operations take several minutes to complete. This patch doesn't change the normal flow of the write path: that will still always call get_head() on the underlying TableStore, which will stat the directory before writing out changes. It will however empty the cache when the metadata has been written. Fixes #845. --- lib/src/git_backend.rs | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/lib/src/git_backend.rs b/lib/src/git_backend.rs index 084bdadec87..e2728332325 100644 --- a/lib/src/git_backend.rs +++ b/lib/src/git_backend.rs @@ -16,7 +16,7 @@ use std::fmt::{Debug, Error, Formatter}; use std::fs::File; use std::io::{Cursor, Read, Write}; use std::path::Path; -use std::sync::Mutex; +use std::sync::{Arc, Mutex}; use git2::Oid; use itertools::Itertools; @@ -29,7 +29,7 @@ use crate::backend::{ TreeId, TreeValue, }; use crate::repo_path::{RepoPath, RepoPathComponent}; -use crate::stacked_table::{TableSegment, TableStore}; +use crate::stacked_table::{ReadonlyTable, TableSegment, TableStore}; const HASH_LENGTH: usize = 20; /// Ref namespace used only for preventing GC. @@ -50,6 +50,7 @@ pub struct GitBackend { root_commit_id: CommitId, empty_tree_id: TreeId, extra_metadata_store: TableStore, + cached_extra_metadata: Mutex>>, } impl GitBackend { @@ -61,6 +62,7 @@ impl GitBackend { root_commit_id, empty_tree_id, extra_metadata_store, + cached_extra_metadata: Mutex::new(None), } } @@ -390,9 +392,17 @@ impl Backend for GitBackend { committer, }; - let table = self.extra_metadata_store.get_head().map_err(|err| { - BackendError::Other(format!("Failed to read non-git metadata: {err}")) - })?; + let table = { + let mut locked_head = self.cached_extra_metadata.lock().unwrap(); + match locked_head.take() { + Some(head) => Ok(head.clone()), + None => self.extra_metadata_store.get_head().map(|x| { + *locked_head = Some(x.clone()); + x + }), + } + } + .map_err(|err| BackendError::Other(format!("Failed to read non-git metadata: {err}")))?; let maybe_extras = table.get_value(git_commit_id.as_bytes()); if let Some(extras) = maybe_extras { deserialize_extras(&mut commit, extras); @@ -452,6 +462,7 @@ impl Backend for GitBackend { .map_err(|err| { BackendError::Other(format!("Failed to write non-git metadata: {err}")) })?; + *self.cached_extra_metadata.lock().unwrap() = None; Ok(id) } }