Use transaction to insert inventories

Instead of inserting inventory entries one by one, we use a transaction,
which is much faster.
This commit is contained in:
Alexis Sellier 2023-07-14 15:46:46 +02:00
parent b27313bc5c
commit 6b6f087c14
No known key found for this signature in database
3 changed files with 135 additions and 83 deletions

View File

@ -333,6 +333,11 @@ where
&mut self.addresses &mut self.addresses
} }
/// Get the routing store.
pub fn routing(&self) -> &R {
&self.routing
}
/// Get the storage instance. /// Get the storage instance.
pub fn storage(&self) -> &S { pub fn storage(&self) -> &S {
&self.storage &self.storage
@ -386,9 +391,11 @@ where
// Ensure that our inventory is recorded in our routing table, and we are tracking // Ensure that our inventory is recorded in our routing table, and we are tracking
// all of it. It can happen that inventory is not properly tracked if for eg. the // all of it. It can happen that inventory is not properly tracked if for eg. the
// user creates a new repository while the node is stopped. // user creates a new repository while the node is stopped.
for rid in self.storage.inventory()? { let rids = self.storage.inventory()?;
self.routing.insert(rid, self.node_id(), time.as_millis())?; self.routing
.insert(&rids, self.node_id(), time.as_millis())?;
for rid in rids {
if !self.is_tracking(&rid)? { if !self.is_tracking(&rid)? {
if self if self
.track_repo(&rid, tracking::Scope::Trusted) .track_repo(&rid, tracking::Scope::Trusted)
@ -913,11 +920,11 @@ where
// We update inventories when receiving ref announcements, as these could come // We update inventories when receiving ref announcements, as these could come
// from a new repository being initialized. // from a new repository being initialized.
if let Ok(result) = self if let Ok(result) =
.routing self.routing
.insert(message.rid, *announcer, message.timestamp) .insert([&message.rid], *announcer, message.timestamp)
{ {
if let InsertResult::SeedAdded = result { if let &[(_, InsertResult::SeedAdded)] = result.as_slice() {
self.emitter.emit(Event::SeedDiscovered { self.emitter.emit(Event::SeedDiscovered {
rid: message.rid, rid: message.rid,
nid: *relayer, nid: *relayer,
@ -1182,28 +1189,24 @@ where
timestamp: Timestamp, timestamp: Timestamp,
) -> Result<SyncedRouting, Error> { ) -> Result<SyncedRouting, Error> {
let mut synced = SyncedRouting::default(); let mut synced = SyncedRouting::default();
let mut included = HashSet::new(); let included: HashSet<&Id> = HashSet::from_iter(inventory);
for rid in inventory { for (rid, result) in self.routing.insert(inventory, from, timestamp)? {
included.insert(rid); match result {
match self.routing.insert(*rid, from, timestamp)? {
InsertResult::SeedAdded => { InsertResult::SeedAdded => {
info!(target: "service", "Routing table updated for {rid} with seed {from}"); info!(target: "service", "Routing table updated for {rid} with seed {from}");
self.emitter.emit(Event::SeedDiscovered { self.emitter.emit(Event::SeedDiscovered { rid, nid: from });
rid: *rid,
nid: from,
});
if self.tracking.is_repo_tracked(rid).expect( if self.tracking.is_repo_tracked(&rid).expect(
"Service::process_inventory: error accessing tracking configuration", "Service::process_inventory: error accessing tracking configuration",
) { ) {
// TODO: We should fetch here if we're already connected, case this seed has // TODO: We should fetch here if we're already connected, case this seed has
// refs we don't have. // refs we don't have.
} }
synced.added.push(*rid); synced.added.push(rid);
} }
InsertResult::TimeUpdated => { InsertResult::TimeUpdated => {
synced.updated.push(*rid); synced.updated.push(rid);
} }
InsertResult::NotUpdated => {} InsertResult::NotUpdated => {}
} }
@ -1539,8 +1542,6 @@ pub trait ServiceState {
fn clock_mut(&mut self) -> &mut LocalTime; fn clock_mut(&mut self) -> &mut LocalTime;
/// Get service configuration. /// Get service configuration.
fn config(&self) -> &Config; fn config(&self) -> &Config;
/// Get reference to routing table.
fn routing(&self) -> &dyn routing::Store;
} }
impl<R, A, S, G> ServiceState for Service<R, A, S, G> impl<R, A, S, G> ServiceState for Service<R, A, S, G>
@ -1572,10 +1573,6 @@ where
fn config(&self) -> &Config { fn config(&self) -> &Config {
&self.config &self.config
} }
fn routing(&self) -> &dyn routing::Store {
&self.routing
}
} }
/// Disconnect reason. /// Disconnect reason.

View File

@ -8,6 +8,7 @@ use std::time;
use crossbeam_channel as chan; use crossbeam_channel as chan;
use netservices::Direction as Link; use netservices::Direction as Link;
use radicle::node::routing::Store as _;
use radicle::storage::ReadRepository; use radicle::storage::ReadRepository;
use crate::collections::{HashMap, HashSet}; use crate::collections::{HashMap, HashSet};

View File

@ -95,7 +95,12 @@ pub trait Store {
Ok(self.len()? == 0) Ok(self.len()? == 0)
} }
/// Add a new node seeding the given id. /// Add a new node seeding the given id.
fn insert(&mut self, id: Id, node: NodeId, time: Timestamp) -> Result<InsertResult, Error>; fn insert<'a>(
&mut self,
ids: impl IntoIterator<Item = &'a Id>,
node: NodeId,
time: Timestamp,
) -> Result<Vec<(Id, InsertResult)>, Error>;
/// Remove a node for the given id. /// Remove a node for the given id.
fn remove(&mut self, id: &Id, node: &NodeId) -> Result<bool, Error>; fn remove(&mut self, id: &Id, node: &NodeId) -> Result<bool, Error>;
/// Iterate over all entries in the routing table. /// Iterate over all entries in the routing table.
@ -149,14 +154,21 @@ impl Store for Table {
Ok(None) Ok(None)
} }
fn insert(&mut self, id: Id, node: NodeId, time: Timestamp) -> Result<InsertResult, Error> { fn insert<'a>(
&mut self,
ids: impl IntoIterator<Item = &'a Id>,
node: NodeId,
time: Timestamp,
) -> Result<Vec<(Id, InsertResult)>, Error> {
let time: i64 = time.try_into().map_err(|_| Error::UnitOverflow)?; let time: i64 = time.try_into().map_err(|_| Error::UnitOverflow)?;
let mut results = Vec::new();
transaction(&self.db, |db| { transaction(&self.db, |db| {
for id in ids.into_iter() {
let mut stmt = let mut stmt =
db.prepare("SELECT (time) FROM routing WHERE resource = ? AND node = ?")?; db.prepare("SELECT (time) FROM routing WHERE resource = ? AND node = ?")?;
stmt.bind((1, &id))?; stmt.bind((1, id))?;
stmt.bind((2, &node))?; stmt.bind((2, &node))?;
let existed = stmt.into_iter().next().is_some(); let existed = stmt.into_iter().next().is_some();
@ -168,16 +180,19 @@ impl Store for Table {
WHERE time < ?3", WHERE time < ?3",
)?; )?;
stmt.bind((1, &id))?; stmt.bind((1, id))?;
stmt.bind((2, &node))?; stmt.bind((2, &node))?;
stmt.bind((3, time))?; stmt.bind((3, time))?;
stmt.next()?; stmt.next()?;
Ok(match (self.db.change_count() > 0, existed) { let result = match (self.db.change_count() > 0, existed) {
(true, true) => InsertResult::TimeUpdated, (true, true) => InsertResult::TimeUpdated,
(true, false) => InsertResult::SeedAdded, (true, false) => InsertResult::SeedAdded,
(false, _) => InsertResult::NotUpdated, (false, _) => InsertResult::NotUpdated,
}) };
results.push((*id, result));
}
Ok(results)
}) })
.map_err(Error::from) .map_err(Error::from)
} }
@ -271,10 +286,13 @@ mod test {
let nodes = arbitrary::set::<NodeId>(5..10); let nodes = arbitrary::set::<NodeId>(5..10);
let mut db = Table::open(":memory:").unwrap(); let mut db = Table::open(":memory:").unwrap();
for id in &ids {
for node in &nodes { for node in &nodes {
assert_eq!(db.insert(*id, *node, 0).unwrap(), InsertResult::SeedAdded); assert_eq!(
} db.insert(&ids, *node, 0).unwrap(),
ids.iter()
.map(|id| (*id, InsertResult::SeedAdded))
.collect::<Vec<_>>()
);
} }
for id in &ids { for id in &ids {
@ -291,10 +309,8 @@ mod test {
let nodes = arbitrary::set::<NodeId>(5..10); let nodes = arbitrary::set::<NodeId>(5..10);
let mut db = Table::open(":memory:").unwrap(); let mut db = Table::open(":memory:").unwrap();
for id in &ids {
for node in &nodes { for node in &nodes {
assert_eq!(db.insert(*id, *node, 0).unwrap(), InsertResult::SeedAdded); db.insert(&ids, *node, 0).unwrap();
}
} }
for node in &nodes { for node in &nodes {
@ -311,10 +327,12 @@ mod test {
let nodes = arbitrary::set::<NodeId>(6..9); let nodes = arbitrary::set::<NodeId>(6..9);
let mut db = Table::open(":memory:").unwrap(); let mut db = Table::open(":memory:").unwrap();
for id in &ids {
for node in &nodes { for node in &nodes {
assert_eq!(db.insert(*id, *node, 0).unwrap(), InsertResult::SeedAdded); assert!(db
} .insert(&ids, *node, 0)
.unwrap()
.iter()
.all(|(_, r)| *r == InsertResult::SeedAdded));
} }
let results = db.entries().unwrap().collect::<Vec<_>>(); let results = db.entries().unwrap().collect::<Vec<_>>();
@ -332,10 +350,8 @@ mod test {
let nodes = arbitrary::set::<NodeId>(5..10); let nodes = arbitrary::set::<NodeId>(5..10);
let mut db = Table::open(":memory:").unwrap(); let mut db = Table::open(":memory:").unwrap();
for id in &ids {
for node in &nodes { for node in &nodes {
db.insert(*id, *node, 0).unwrap(); db.insert(&ids, *node, 0).unwrap();
}
} }
for id in &ids { for id in &ids {
for node in &nodes { for node in &nodes {
@ -353,9 +369,18 @@ mod test {
let node = arbitrary::gen::<NodeId>(1); let node = arbitrary::gen::<NodeId>(1);
let mut db = Table::open(":memory:").unwrap(); let mut db = Table::open(":memory:").unwrap();
assert_eq!(db.insert(id, node, 0).unwrap(), InsertResult::SeedAdded); assert_eq!(
assert_eq!(db.insert(id, node, 0).unwrap(), InsertResult::NotUpdated); db.insert([&id], node, 0).unwrap(),
assert_eq!(db.insert(id, node, 0).unwrap(), InsertResult::NotUpdated); vec![(id, InsertResult::SeedAdded)]
);
assert_eq!(
db.insert([&id], node, 0).unwrap(),
vec![(id, InsertResult::NotUpdated)]
);
assert_eq!(
db.insert([&id], node, 0).unwrap(),
vec![(id, InsertResult::NotUpdated)]
);
} }
#[test] #[test]
@ -364,18 +389,54 @@ mod test {
let node = arbitrary::gen::<NodeId>(1); let node = arbitrary::gen::<NodeId>(1);
let mut db = Table::open(":memory:").unwrap(); let mut db = Table::open(":memory:").unwrap();
assert_eq!(db.insert(id, node, 0).unwrap(), InsertResult::SeedAdded); assert_eq!(
assert_eq!(db.insert(id, node, 1).unwrap(), InsertResult::TimeUpdated); db.insert([&id], node, 0).unwrap(),
vec![(id, InsertResult::SeedAdded)]
);
assert_eq!(
db.insert([&id], node, 1).unwrap(),
vec![(id, InsertResult::TimeUpdated)]
);
assert_eq!(db.entry(&id, &node).unwrap(), Some(1)); assert_eq!(db.entry(&id, &node).unwrap(), Some(1));
} }
#[test]
fn test_update_existing_multi() {
let id1 = arbitrary::gen::<Id>(1);
let id2 = arbitrary::gen::<Id>(1);
let node = arbitrary::gen::<NodeId>(1);
let mut db = Table::open(":memory:").unwrap();
assert_eq!(
db.insert([&id1], node, 0).unwrap(),
vec![(id1, InsertResult::SeedAdded)]
);
assert_eq!(
db.insert([&id1, &id2], node, 0).unwrap(),
vec![
(id1, InsertResult::NotUpdated),
(id2, InsertResult::SeedAdded)
]
);
assert_eq!(
db.insert([&id1, &id2], node, 1).unwrap(),
vec![
(id1, InsertResult::TimeUpdated),
(id2, InsertResult::TimeUpdated)
]
);
}
#[test] #[test]
fn test_remove_redundant() { fn test_remove_redundant() {
let id = arbitrary::gen::<Id>(1); let id = arbitrary::gen::<Id>(1);
let node = arbitrary::gen::<NodeId>(1); let node = arbitrary::gen::<NodeId>(1);
let mut db = Table::open(":memory:").unwrap(); let mut db = Table::open(":memory:").unwrap();
assert_eq!(db.insert(id, node, 0).unwrap(), InsertResult::SeedAdded); assert_eq!(
db.insert([&id], node, 0).unwrap(),
vec![(id, InsertResult::SeedAdded)]
);
assert!(db.remove(&id, &node).unwrap()); assert!(db.remove(&id, &node).unwrap());
assert!(!db.remove(&id, &node).unwrap()); assert!(!db.remove(&id, &node).unwrap());
} }
@ -386,9 +447,7 @@ mod test {
let ids = arbitrary::vec::<Id>(10); let ids = arbitrary::vec::<Id>(10);
let node = arbitrary::gen(1); let node = arbitrary::gen(1);
for id in ids { db.insert(&ids, node, LocalTime::now().as_millis()).unwrap();
db.insert(id, node, LocalTime::now().as_millis()).unwrap();
}
assert_eq!(10, db.len().unwrap(), "correct number of rows in table"); assert_eq!(10, db.len().unwrap(), "correct number of rows in table");
} }
@ -401,21 +460,17 @@ mod test {
let nodes = arbitrary::vec::<NodeId>(10); let nodes = arbitrary::vec::<NodeId>(10);
let mut db = Table::open(":memory:").unwrap(); let mut db = Table::open(":memory:").unwrap();
for id in &ids {
for node in &nodes { for node in &nodes {
let time = rng.u64(..now.as_millis()); let time = rng.u64(..now.as_millis());
db.insert(*id, *node, time).unwrap(); db.insert(&ids, *node, time).unwrap();
}
} }
let ids = arbitrary::vec::<Id>(10); let ids = arbitrary::vec::<Id>(10);
let nodes = arbitrary::vec::<NodeId>(10); let nodes = arbitrary::vec::<NodeId>(10);
for id in &ids {
for node in &nodes { for node in &nodes {
let time = rng.u64(now.as_millis()..i64::MAX as u64); let time = rng.u64(now.as_millis()..i64::MAX as u64);
db.insert(*id, *node, time).unwrap(); db.insert(&ids, *node, time).unwrap();
}
} }
let pruned = db.prune(now.as_millis(), None).unwrap(); let pruned = db.prune(now.as_millis(), None).unwrap();
@ -436,9 +491,8 @@ mod test {
let mut db = Table::open(":memory:").unwrap(); let mut db = Table::open(":memory:").unwrap();
for node in &nodes { for node in &nodes {
db.insert(id, *node, 0).unwrap(); db.insert([&id], *node, 0).unwrap();
} }
assert_eq!(db.count(&id).unwrap(), nodes.len()); assert_eq!(db.count(&id).unwrap(), nodes.len());
} }
} }