Skip to content

Commit 1727258

Browse files
authored
Reduce text insert and import overhead (#1022)
* fix: reduce text insert and import overhead * chore: add changeset for import performance fixes
1 parent d107199 commit 1727258

6 files changed

Lines changed: 134 additions & 72 deletions

File tree

.changeset/faster-text-imports.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"loro-crdt": patch
3+
"loro-crdt-map": patch
4+
---
5+
6+
Improve text insert and snapshot import performance by avoiding duplicate text boundary validation and skipping eager imported change block parsing.

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/loro-internal/src/arena.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -782,7 +782,7 @@ mod tests {
782782
ContainerID::new_mergeable(&top_root, "profile", ContainerType::Map);
783783
let child = ContainerID::new_normal(
784784
loro_common::ID::new(1, 0),
785-
ContainerType::Counter,
785+
ContainerType::List,
786786
);
787787

788788
let mergeable_parent_for_resolver = mergeable_parent.clone();

crates/loro-internal/src/handler.rs

Lines changed: 38 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1572,7 +1572,12 @@ impl TextHandler {
15721572
return Ok(());
15731573
};
15741574

1575-
if pos > self.len(pos_type) {
1575+
let len = self.len(pos_type);
1576+
if pos > len {
1577+
return Ok(());
1578+
}
1579+
1580+
if self.all_text_positions_are_boundaries(pos_type, len) {
15761581
return Ok(());
15771582
}
15781583

@@ -1586,6 +1591,15 @@ impl TextHandler {
15861591
Ok(())
15871592
}
15881593

1594+
fn all_text_positions_are_boundaries(&self, pos_type: PosType, len: usize) -> bool {
1595+
match pos_type {
1596+
PosType::Bytes => len == self.len_unicode(),
1597+
PosType::Utf16 => len == self.len_unicode(),
1598+
PosType::Event if cfg!(feature = "wasm") => len == self.len_unicode(),
1599+
_ => false,
1600+
}
1601+
}
1602+
15891603
pub fn diagnose(&self) {
15901604
match &self.inner {
15911605
MaybeDetached::Detached(t) => {
@@ -1851,18 +1865,18 @@ impl TextHandler {
18511865
///
18521866
/// This method requires auto_commit to be enabled.
18531867
pub fn insert(&self, pos: usize, s: &str, pos_type: PosType) -> LoroResult<()> {
1854-
let len = self.len(pos_type);
1855-
if pos > len {
1856-
return Err(LoroError::OutOfBound {
1857-
pos,
1858-
len,
1859-
info: format!("Position: {}:{}", file!(), line!()).into_boxed_str(),
1860-
});
1861-
}
1862-
self.validate_text_boundary(pos, pos_type)?;
1863-
18641868
match &self.inner {
18651869
MaybeDetached::Detached(t) => {
1870+
let len = self.len(pos_type);
1871+
if pos > len {
1872+
return Err(LoroError::OutOfBound {
1873+
pos,
1874+
len,
1875+
info: format!("Position: {}:{}", file!(), line!()).into_boxed_str(),
1876+
});
1877+
}
1878+
self.validate_text_boundary(pos, pos_type)?;
1879+
18661880
let mut t = t.lock();
18671881
let (index, _) = t
18681882
.value
@@ -1876,6 +1890,19 @@ impl TextHandler {
18761890
Ok(())
18771891
}
18781892
MaybeDetached::Attached(a) => {
1893+
if s.is_empty() {
1894+
let len = self.len(pos_type);
1895+
if pos > len {
1896+
return Err(LoroError::OutOfBound {
1897+
pos,
1898+
len,
1899+
info: format!("Position: {}:{}", file!(), line!()).into_boxed_str(),
1900+
});
1901+
}
1902+
self.validate_text_boundary(pos, pos_type)?;
1903+
return Ok(());
1904+
}
1905+
18791906
a.with_txn(|txn| self.insert_with_txn(txn, pos, s, pos_type))
18801907
}
18811908
}

crates/loro-internal/src/oplog/change_store.rs

Lines changed: 76 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -325,10 +325,11 @@ impl ChangeStore {
325325
pub fn visit_all_changes(&self, f: &mut dyn FnMut(&Change)) {
326326
self.ensure_block_loaded_in_range(Bound::Unbounded, Bound::Unbounded);
327327
let mut inner = self.inner.lock();
328-
for (_, block) in inner.mem_parsed_kv.iter_mut() {
329-
block
330-
.ensure_changes(&self.arena)
331-
.expect("Parse block error");
328+
for (id, block) in inner.mem_parsed_kv.iter_mut() {
329+
if let Err(err) = block.ensure_changes(&self.arena) {
330+
warn!(block_id = ?id, ?err, "failed to parse change block");
331+
continue;
332+
}
332333
for c in block.content.try_changes().unwrap() {
333334
f(c);
334335
}
@@ -368,9 +369,10 @@ impl ChangeStore {
368369
return None;
369370
}
370371

371-
block
372-
.ensure_changes(&self.arena)
373-
.expect("Parse block error");
372+
if let Err(err) = block.ensure_changes(&self.arena) {
373+
warn!(block_id = ?_id, ?err, "failed to parse change block");
374+
return None;
375+
}
374376
let changes = block.content.try_changes().unwrap();
375377
let start;
376378
let end;
@@ -456,9 +458,10 @@ impl ChangeStore {
456458
return None;
457459
}
458460

459-
block
460-
.ensure_changes(&self.arena)
461-
.expect("Parse block error");
461+
if let Err(err) = block.ensure_changes(&self.arena) {
462+
warn!(block_id = ?_id, ?err, "failed to parse change block");
463+
return None;
464+
}
462465
Some(block.clone())
463466
})
464467
// TODO: PERF avoid alloc
@@ -639,7 +642,6 @@ mod mut_external_kv {
639642
.import_all(bytes)
640643
.map_err(|e| LoroError::DecodeError(e.into_boxed_str()))?;
641644
drop(kv_store);
642-
self.validate_imported_change_blocks()?;
643645
let vv_bytes = self.external_kv.lock().get(VV_KEY).unwrap_or_default();
644646
let vv = VersionVector::decode(&vv_bytes)
645647
.map_err(|_| LoroError::DecodeDataCorruptionError)?;
@@ -720,24 +722,6 @@ mod mut_external_kv {
720722
})
721723
}
722724

723-
fn validate_imported_change_blocks(&self) -> LoroResult<()> {
724-
let blocks: Vec<(ID, Bytes)> = {
725-
let kv_store = self.external_kv.lock();
726-
kv_store
727-
.scan(Bound::Unbounded, Bound::Unbounded)
728-
.filter(|(id, _)| id.len() == 12)
729-
.map(|(id, bytes)| (ID::from_bytes(&id), bytes))
730-
.collect()
731-
};
732-
733-
for (_id, bytes) in blocks {
734-
let mut block = Arc::new(ChangesBlock::from_bytes(bytes)?);
735-
block.ensure_changes(&self.arena)?;
736-
}
737-
738-
Ok(())
739-
}
740-
741725
/// Flush the cached change to kv_store
742726
pub(crate) fn flush_and_compact(&self, vv: &VersionVector, frontiers: &Frontiers) {
743727
let mut inner = self.inner.lock();
@@ -911,9 +895,10 @@ mod mut_inner_kv {
911895
}
912896

913897
// Found the block
914-
block
915-
.ensure_changes(&self.arena)
916-
.expect("Parse block error");
898+
if let Err(err) = block.ensure_changes(&self.arena) {
899+
warn!(block_id = ?id, ?err, "failed to parse change block");
900+
return None;
901+
}
917902
let index = block.get_change_index_by_lamport_lte(idlp.lamport)?;
918903
return Some(BlockChangeRef {
919904
change_index: index,
@@ -994,7 +979,18 @@ mod mut_inner_kv {
994979

995980
for (id, bytes) in iter {
996981
let mut block = ChangesBlockBytes::new(bytes.clone());
997-
let (lamport_start, _lamport_end) = block.lamport_range();
982+
let (lamport_start, _lamport_end) = match block.lamport_range() {
983+
Ok(range) => range,
984+
Err(err) => {
985+
let block_id = ID::from_bytes(&id);
986+
warn!(
987+
?block_id,
988+
?err,
989+
"failed to decode external change block range"
990+
);
991+
continue;
992+
}
993+
};
998994
if lamport_start <= idlp.lamport {
999995
break 'block_scan (id, bytes);
1000996
}
@@ -1004,13 +1000,17 @@ mod mut_inner_kv {
10041000
};
10051001

10061002
let block_id = ID::from_bytes(&id);
1007-
let mut block = Arc::new(
1008-
ChangesBlock::from_bytes(bytes)
1009-
.expect("validated external change block should decode"),
1010-
);
1011-
block
1012-
.ensure_changes(&self.arena)
1013-
.expect("Parse block error");
1003+
let mut block = match ChangesBlock::from_bytes(bytes) {
1004+
Ok(block) => Arc::new(block),
1005+
Err(err) => {
1006+
warn!(?block_id, ?err, "failed to decode external change block");
1007+
return None;
1008+
}
1009+
};
1010+
if let Err(err) = block.ensure_changes(&self.arena) {
1011+
warn!(?block_id, ?err, "failed to parse external change block");
1012+
return None;
1013+
}
10141014
inner.mem_parsed_kv.insert(block_id, block.clone());
10151015
let index = block.get_change_index_by_lamport_lte(idlp.lamport)?;
10161016
Some(BlockChangeRef {
@@ -1120,9 +1120,10 @@ mod mut_inner_kv {
11201120
let mut inner = self.inner.lock();
11211121
if let Some((_id, block)) = inner.mem_parsed_kv.range_mut(..=id).next_back() {
11221122
if block.peer == id.peer && block.counter_range.1 > id.counter {
1123-
block
1124-
.ensure_changes(&self.arena)
1125-
.expect("Parse block error");
1123+
if let Err(err) = block.ensure_changes(&self.arena) {
1124+
warn!(block_id = ?_id, ?err, "failed to parse cached change block");
1125+
return None;
1126+
}
11261127
return Some(block.clone());
11271128
}
11281129
}
@@ -1144,16 +1145,22 @@ mod mut_inner_kv {
11441145

11451146
let (b_id, b_bytes) = iter.next_back()?;
11461147
let block_id: ID = ID::from_bytes(&b_id[..]);
1147-
let block = ChangesBlock::from_bytes(b_bytes)
1148-
.expect("validated external change block should decode");
1148+
let block = match ChangesBlock::from_bytes(b_bytes) {
1149+
Ok(block) => block,
1150+
Err(err) => {
1151+
warn!(?block_id, ?err, "failed to decode external change block");
1152+
return None;
1153+
}
1154+
};
11491155
if block_id.peer == id.peer
11501156
&& block_id.counter <= id.counter
11511157
&& block.counter_range.1 > id.counter
11521158
{
11531159
let mut arc_block = Arc::new(block);
1154-
arc_block
1155-
.ensure_changes(&self.arena)
1156-
.expect("Parse block error");
1160+
if let Err(err) = arc_block.ensure_changes(&self.arena) {
1161+
warn!(?block_id, ?err, "failed to parse external change block");
1162+
return None;
1163+
}
11571164
inner.mem_parsed_kv.insert(block_id, arc_block.clone());
11581165
return Some(arc_block);
11591166
}
@@ -1195,8 +1202,13 @@ mod mut_inner_kv {
11951202
continue;
11961203
}
11971204

1198-
let block = ChangesBlock::from_bytes(bytes.clone())
1199-
.expect("validated external change block should decode");
1205+
let block = match ChangesBlock::from_bytes(bytes.clone()) {
1206+
Ok(block) => block,
1207+
Err(err) => {
1208+
warn!(?id, ?err, "failed to decode external change block");
1209+
continue;
1210+
}
1211+
};
12001212
inner.mem_parsed_kv.insert(id, Arc::new(block));
12011213
}
12021214
}
@@ -1222,8 +1234,17 @@ mod mut_inner_kv {
12221234
return;
12231235
}
12241236

1225-
let block = ChangesBlock::from_bytes(next_back_bytes)
1226-
.expect("validated external change block should decode");
1237+
let block = match ChangesBlock::from_bytes(next_back_bytes) {
1238+
Ok(block) => block,
1239+
Err(err) => {
1240+
warn!(
1241+
?next_back_id,
1242+
?err,
1243+
"failed to decode external change block"
1244+
);
1245+
return;
1246+
}
1247+
};
12271248
inner.mem_parsed_kv.insert(next_back_id, Arc::new(block));
12281249
}
12291250
}
@@ -1658,11 +1679,11 @@ impl ChangesBlockBytes {
16581679
bytes
16591680
}
16601681

1661-
fn lamport_range(&mut self) -> (Lamport, Lamport) {
1682+
fn lamport_range(&mut self) -> LoroResult<(Lamport, Lamport)> {
16621683
if let Some(header) = self.header.get() {
1663-
(header.lamports[0], *header.lamports.last().unwrap())
1684+
Ok((header.lamports[0], *header.lamports.last().unwrap()))
16641685
} else {
1665-
decode_block_range(&self.bytes).unwrap().1
1686+
decode_block_range(&self.bytes).map(|(_, lamport_range)| lamport_range)
16661687
}
16671688
}
16681689

crates/loro-internal/src/oplog/change_store/block_encode.rs

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -476,10 +476,18 @@ pub fn decode_block_range(
476476
));
477477
}
478478

479-
let counter_start = leb128::read::unsigned(&mut bytes).unwrap() as Counter;
480-
let counter_len = leb128::read::unsigned(&mut bytes).unwrap() as Counter;
481-
let lamport_start = leb128::read::unsigned(&mut bytes).unwrap() as Lamport;
482-
let lamport_len = leb128::read::unsigned(&mut bytes).unwrap() as Lamport;
479+
let counter_start = leb128::read::unsigned(&mut bytes).map_err(|e| {
480+
LoroError::DecodeError(format!("Failed to read counter start: {e}").into_boxed_str())
481+
})? as Counter;
482+
let counter_len = leb128::read::unsigned(&mut bytes).map_err(|e| {
483+
LoroError::DecodeError(format!("Failed to read counter length: {e}").into_boxed_str())
484+
})? as Counter;
485+
let lamport_start = leb128::read::unsigned(&mut bytes).map_err(|e| {
486+
LoroError::DecodeError(format!("Failed to read lamport start: {e}").into_boxed_str())
487+
})? as Lamport;
488+
let lamport_len = leb128::read::unsigned(&mut bytes).map_err(|e| {
489+
LoroError::DecodeError(format!("Failed to read lamport length: {e}").into_boxed_str())
490+
})? as Lamport;
483491
Ok((
484492
(counter_start, counter_start + counter_len),
485493
(lamport_start, lamport_start + lamport_len),

0 commit comments

Comments
 (0)