Storage layer refactoring: faster id generation, automatic batching and virtual thread ids
This commit is contained in:
@@ -4,63 +4,97 @@
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use super::{
|
||||
Batch, BatchBuilder, BitmapClass, IntoOperations, MaybeDynamicId, MaybeDynamicValue, Operation,
|
||||
TagValue, ValueClass, ValueOp, assert::ToAssertValue,
|
||||
use std::sync::{
|
||||
LazyLock,
|
||||
atomic::{AtomicU64, Ordering},
|
||||
};
|
||||
|
||||
use utils::{
|
||||
map::bitmap::{Bitmap, ShortId},
|
||||
snowflake::SnowflakeIdGenerator,
|
||||
};
|
||||
|
||||
use crate::U32_LEN;
|
||||
|
||||
use super::{
|
||||
Batch, BatchBuilder, BitmapClass, IntoOperations, Operation, TagValue, ValueClass, ValueOp,
|
||||
assert::ToAssertValue,
|
||||
};
|
||||
|
||||
static CHANGE_SEQ: AtomicU64 = AtomicU64::new(0);
|
||||
static NODE_MUM: LazyLock<u16> = LazyLock::new(|| CHANGE_SEQ.swap(0, Ordering::Relaxed) as u16);
|
||||
|
||||
impl BatchBuilder {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
ops: Vec::with_capacity(16),
|
||||
ops: Vec::with_capacity(32),
|
||||
current_change_id: None,
|
||||
current_account_id: None,
|
||||
current_collection: None,
|
||||
current_document_id: None,
|
||||
changed_collections: Default::default(),
|
||||
batch_size: 0,
|
||||
batch_ops: 0,
|
||||
has_assertions: false,
|
||||
commit_points: Vec::new(),
|
||||
changelog: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_change_id(&mut self, change_id: u64) -> &mut Self {
|
||||
self.ops.push(Operation::ChangeId { change_id });
|
||||
self
|
||||
pub fn init_id_generator(node_number: u16) {
|
||||
CHANGE_SEQ.store(node_number as u64, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
fn generate_change_id(&mut self) -> u64 {
|
||||
let change_id = SnowflakeIdGenerator::from_params(
|
||||
CHANGE_SEQ.fetch_add(1, Ordering::Relaxed),
|
||||
*NODE_MUM,
|
||||
);
|
||||
self.current_change_id = Some(change_id);
|
||||
change_id
|
||||
}
|
||||
|
||||
pub fn with_account_id(&mut self, account_id: u32) -> &mut Self {
|
||||
self.ops.push(Operation::AccountId { account_id });
|
||||
if self
|
||||
.current_account_id
|
||||
.is_none_or(|current_account_id| current_account_id != account_id)
|
||||
{
|
||||
if self.current_account_id.is_some() && self.current_change_id.is_some() {
|
||||
self.serialize_changes();
|
||||
}
|
||||
self.current_account_id = account_id.into();
|
||||
self.ops.push(Operation::AccountId { account_id });
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_collection(&mut self, collection: impl Into<u8>) -> &mut Self {
|
||||
self.ops.push(Operation::Collection {
|
||||
collection: collection.into(),
|
||||
});
|
||||
let collection = collection.into();
|
||||
let collection_ = Some(collection);
|
||||
if collection_ != self.current_collection {
|
||||
self.current_collection = collection_;
|
||||
self.ops.push(Operation::Collection { collection });
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
pub fn create_document(&mut self) -> &mut Self {
|
||||
self.ops.push(Operation::DocumentId {
|
||||
document_id: u32::MAX,
|
||||
});
|
||||
|
||||
// Add document id
|
||||
self.ops.push(Operation::Bitmap {
|
||||
class: BitmapClass::DocumentIds,
|
||||
set: true,
|
||||
});
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
pub fn create_document_with_id(&mut self, document_id: u32) -> &mut Self {
|
||||
pub fn create_document(&mut self, document_id: u32) -> &mut Self {
|
||||
self.ops.push(Operation::DocumentId { document_id });
|
||||
|
||||
// Add document id
|
||||
self.ops.push(Operation::Bitmap {
|
||||
class: BitmapClass::DocumentIds,
|
||||
set: true,
|
||||
});
|
||||
|
||||
self.current_document_id = Some(document_id);
|
||||
self.batch_size += U32_LEN * 3;
|
||||
self.batch_ops += 1;
|
||||
self.has_assertions = false;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn update_document(&mut self, document_id: u32) -> &mut Self {
|
||||
self.ops.push(Operation::DocumentId { document_id });
|
||||
self.current_document_id = Some(document_id);
|
||||
self.has_assertions = false;
|
||||
self
|
||||
}
|
||||
|
||||
@@ -70,179 +104,255 @@ impl BatchBuilder {
|
||||
class: BitmapClass::DocumentIds,
|
||||
set: false,
|
||||
});
|
||||
self.current_document_id = Some(document_id);
|
||||
self.batch_size += U32_LEN * 3;
|
||||
self.batch_ops += 1;
|
||||
self.has_assertions = false;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn assert_value(
|
||||
&mut self,
|
||||
class: impl Into<ValueClass<MaybeDynamicId>>,
|
||||
class: impl Into<ValueClass>,
|
||||
value: impl ToAssertValue,
|
||||
) -> &mut Self {
|
||||
self.ops.push(Operation::AssertValue {
|
||||
class: class.into(),
|
||||
assert_value: value.to_assert_value(),
|
||||
});
|
||||
self
|
||||
}
|
||||
|
||||
pub fn set_and_index(&mut self, field: impl Into<u8>, value: impl Into<Vec<u8>>) -> &mut Self {
|
||||
let field = field.into();
|
||||
let value = value.into();
|
||||
|
||||
self.ops.push(Operation::Index {
|
||||
field,
|
||||
key: value.clone(),
|
||||
set: true,
|
||||
});
|
||||
self.ops.push(Operation::Value {
|
||||
class: ValueClass::Property(field),
|
||||
op: ValueOp::Set(value.into()),
|
||||
});
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
pub fn unset_and_unindex(
|
||||
&mut self,
|
||||
field: impl Into<u8>,
|
||||
value: impl Into<Vec<u8>>,
|
||||
) -> &mut Self {
|
||||
let field = field.into();
|
||||
let value = value.into();
|
||||
|
||||
self.ops.push(Operation::Index {
|
||||
field,
|
||||
key: value,
|
||||
set: false,
|
||||
});
|
||||
self.ops.push(Operation::Value {
|
||||
class: ValueClass::Property(field),
|
||||
op: ValueOp::Clear,
|
||||
});
|
||||
|
||||
self.batch_ops += 1;
|
||||
self.has_assertions = true;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn index(&mut self, field: impl Into<u8>, value: impl Into<Vec<u8>>) -> &mut Self {
|
||||
let field = field.into();
|
||||
let value = value.into();
|
||||
let value_len = value.len();
|
||||
|
||||
self.ops.push(Operation::Index {
|
||||
field,
|
||||
key: value.clone(),
|
||||
key: value,
|
||||
set: true,
|
||||
});
|
||||
self.batch_size += (U32_LEN * 3) + value_len;
|
||||
self.batch_ops += 1;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn unindex(&mut self, field: impl Into<u8>, value: impl Into<Vec<u8>>) -> &mut Self {
|
||||
let field = field.into();
|
||||
let value = value.into();
|
||||
let value_len = value.len();
|
||||
|
||||
self.ops.push(Operation::Index {
|
||||
field,
|
||||
key: value.clone(),
|
||||
key: value,
|
||||
set: false,
|
||||
});
|
||||
self.batch_size += (U32_LEN * 3) + value_len;
|
||||
self.batch_ops += 1;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn tag(
|
||||
&mut self,
|
||||
field: impl Into<u8>,
|
||||
value: impl Into<TagValue<MaybeDynamicId>>,
|
||||
) -> &mut Self {
|
||||
pub fn tag(&mut self, field: impl Into<u8>, value: impl Into<TagValue>) -> &mut Self {
|
||||
let value = value.into();
|
||||
let value_len = value.serialized_size();
|
||||
self.ops.push(Operation::Bitmap {
|
||||
class: BitmapClass::Tag {
|
||||
field: field.into(),
|
||||
value: value.into(),
|
||||
value,
|
||||
},
|
||||
set: true,
|
||||
});
|
||||
self.batch_size += (U32_LEN * 3) + value_len;
|
||||
self.batch_ops += 1;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn untag(
|
||||
&mut self,
|
||||
field: impl Into<u8>,
|
||||
value: impl Into<TagValue<MaybeDynamicId>>,
|
||||
) -> &mut Self {
|
||||
pub fn untag(&mut self, field: impl Into<u8>, value: impl Into<TagValue>) -> &mut Self {
|
||||
let value = value.into();
|
||||
let value_len = value.serialized_size();
|
||||
self.ops.push(Operation::Bitmap {
|
||||
class: BitmapClass::Tag {
|
||||
field: field.into(),
|
||||
value: value.into(),
|
||||
value,
|
||||
},
|
||||
set: false,
|
||||
});
|
||||
self.batch_size += (U32_LEN * 3) + value_len;
|
||||
self.batch_ops += 1;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn tag_many<T, V>(&mut self, field: impl Into<u8>, values: T) -> &mut Self
|
||||
where
|
||||
T: Iterator<Item = V>,
|
||||
V: Into<TagValue<MaybeDynamicId>>,
|
||||
{
|
||||
let field = field.into();
|
||||
for value in values {
|
||||
self.tag(field, value);
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
pub fn untag_many<T, V>(&mut self, field: impl Into<u8>, values: T) -> &mut Self
|
||||
where
|
||||
T: Iterator<Item = V>,
|
||||
V: Into<TagValue<MaybeDynamicId>>,
|
||||
{
|
||||
let field = field.into();
|
||||
for value in values {
|
||||
self.untag(field, value);
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
pub fn add(&mut self, class: impl Into<ValueClass<MaybeDynamicId>>, value: i64) -> &mut Self {
|
||||
pub fn add(&mut self, class: impl Into<ValueClass>, value: i64) -> &mut Self {
|
||||
let class = class.into();
|
||||
self.batch_size += class.serialized_size() + std::mem::size_of::<i64>();
|
||||
self.ops.push(Operation::Value {
|
||||
class: class.into(),
|
||||
class,
|
||||
op: ValueOp::AtomicAdd(value),
|
||||
});
|
||||
self.batch_ops += 1;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn add_and_get(
|
||||
&mut self,
|
||||
class: impl Into<ValueClass<MaybeDynamicId>>,
|
||||
value: i64,
|
||||
) -> &mut Self {
|
||||
pub fn add_and_get(&mut self, class: impl Into<ValueClass>, value: i64) -> &mut Self {
|
||||
let class = class.into();
|
||||
self.batch_size += class.serialized_size() + (std::mem::size_of::<i64>() * 2);
|
||||
self.ops.push(Operation::Value {
|
||||
class: class.into(),
|
||||
class,
|
||||
op: ValueOp::AddAndGet(value),
|
||||
});
|
||||
self.batch_ops += 1;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn set(
|
||||
&mut self,
|
||||
class: impl Into<ValueClass<MaybeDynamicId>>,
|
||||
value: impl Into<MaybeDynamicValue>,
|
||||
) -> &mut Self {
|
||||
pub fn set(&mut self, class: impl Into<ValueClass>, value: impl Into<Vec<u8>>) -> &mut Self {
|
||||
let class = class.into();
|
||||
let value = value.into();
|
||||
self.batch_size += class.serialized_size() + value.len();
|
||||
self.ops.push(Operation::Value {
|
||||
class: class.into(),
|
||||
op: ValueOp::Set(value.into()),
|
||||
class,
|
||||
op: ValueOp::Set(value),
|
||||
});
|
||||
self.batch_ops += 1;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn clear(&mut self, class: impl Into<ValueClass<MaybeDynamicId>>) -> &mut Self {
|
||||
pub fn clear(&mut self, class: impl Into<ValueClass>) -> &mut Self {
|
||||
let class = class.into();
|
||||
self.batch_size += class.serialized_size();
|
||||
self.ops.push(Operation::Value {
|
||||
class: class.into(),
|
||||
class,
|
||||
op: ValueOp::Clear,
|
||||
});
|
||||
self.batch_ops += 1;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn log(&mut self, value: impl Into<MaybeDynamicValue>) -> &mut Self {
|
||||
self.ops.push(Operation::Log { set: value.into() });
|
||||
pub fn acl_grant(&mut self, grant_account_id: u32, op: Vec<u8>) -> &mut Self {
|
||||
self.batch_size += (U32_LEN * 3) + op.len();
|
||||
self.ops.push(Operation::Value {
|
||||
class: ValueClass::Acl(grant_account_id),
|
||||
op: ValueOp::Set(op),
|
||||
});
|
||||
self.batch_ops += 1;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn acl_revoke(&mut self, grant_account_id: u32) -> &mut Self {
|
||||
self.batch_size += U32_LEN * 3;
|
||||
self.ops.push(Operation::Value {
|
||||
class: ValueClass::Acl(grant_account_id),
|
||||
op: ValueOp::Clear,
|
||||
});
|
||||
self.batch_ops += 1;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn log_insert(&mut self, prefix: Option<u32>) -> &mut Self {
|
||||
if let (Some(account_id), Some(collection)) =
|
||||
(self.current_account_id, self.current_collection)
|
||||
{
|
||||
self.changed_collections
|
||||
.get_mut_or_insert(account_id)
|
||||
.insert(ShortId(collection));
|
||||
if let Some(document_id) = self.current_document_id {
|
||||
self.changelog.log_insert(collection, prefix, document_id);
|
||||
}
|
||||
}
|
||||
if self.current_change_id.is_none() {
|
||||
self.generate_change_id();
|
||||
self.batch_ops += 1;
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
pub fn log_update(&mut self, prefix: Option<u32>) -> &mut Self {
|
||||
if let (Some(account_id), Some(collection)) =
|
||||
(self.current_account_id, self.current_collection)
|
||||
{
|
||||
self.changed_collections
|
||||
.get_mut_or_insert(account_id)
|
||||
.insert(ShortId(collection));
|
||||
if let Some(document_id) = self.current_document_id {
|
||||
self.changelog.log_update(collection, prefix, document_id);
|
||||
}
|
||||
}
|
||||
if self.current_change_id.is_none() {
|
||||
self.generate_change_id();
|
||||
self.batch_ops += 1;
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
pub fn log_delete(&mut self, prefix: Option<u32>) -> &mut Self {
|
||||
if let (Some(account_id), Some(collection)) =
|
||||
(self.current_account_id, self.current_collection)
|
||||
{
|
||||
self.changed_collections
|
||||
.get_mut_or_insert(account_id)
|
||||
.insert(ShortId(collection));
|
||||
if let Some(document_id) = self.current_document_id {
|
||||
self.changelog.log_delete(collection, prefix, document_id);
|
||||
}
|
||||
}
|
||||
if self.current_change_id.is_none() {
|
||||
self.generate_change_id();
|
||||
self.batch_ops += 1;
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
pub fn log_child_update(&mut self, collection: impl Into<u8>, parent_id: u32) -> &mut Self {
|
||||
let collection = collection.into();
|
||||
|
||||
if let Some(account_id) = self.current_account_id {
|
||||
self.changed_collections
|
||||
.get_mut_or_insert(account_id)
|
||||
.insert(ShortId(collection));
|
||||
}
|
||||
self.changelog.log_child_update(collection, None, parent_id);
|
||||
if self.current_change_id.is_none() {
|
||||
self.generate_change_id();
|
||||
self.batch_ops += 1;
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
fn serialize_changes(&mut self) {
|
||||
if let Some(change_id) = self.current_change_id.take() {
|
||||
if !self.changelog.is_empty() {
|
||||
for (collection, set) in std::mem::take(&mut self.changelog).serialize() {
|
||||
self.ops.push(Operation::Log {
|
||||
change_id,
|
||||
collection,
|
||||
set,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn commit_point(&mut self) -> &mut Self {
|
||||
if self.batch_size > 5_000_000 || self.batch_ops > 1000 {
|
||||
self.serialize_changes();
|
||||
self.commit_points.push(self.ops.len());
|
||||
self.batch_ops = 0;
|
||||
self.batch_size = 0;
|
||||
if let Some(account_id) = self.current_account_id {
|
||||
self.ops.push(Operation::AccountId { account_id });
|
||||
}
|
||||
if let Some(collection) = self.current_collection {
|
||||
self.ops.push(Operation::Collection { collection });
|
||||
}
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
pub fn any_op(&mut self, op: Operation) -> &mut Self {
|
||||
self.ops.push(op);
|
||||
self.batch_ops += 1;
|
||||
self
|
||||
}
|
||||
|
||||
@@ -251,38 +361,68 @@ impl BatchBuilder {
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
pub fn build(self) -> Batch {
|
||||
Batch { ops: self.ops }
|
||||
pub fn last_account_id(&self) -> Option<u32> {
|
||||
self.current_account_id
|
||||
}
|
||||
|
||||
pub fn build_batch(&mut self) -> Batch {
|
||||
pub fn change_id(&mut self) -> u64 {
|
||||
self.current_change_id
|
||||
.unwrap_or_else(|| self.generate_change_id())
|
||||
}
|
||||
|
||||
pub fn last_change_id(&self) -> Option<u64> {
|
||||
self.current_change_id
|
||||
}
|
||||
|
||||
pub fn build(&mut self) -> impl Iterator<Item = Batch<'_>> {
|
||||
self.serialize_changes();
|
||||
self.build_batches()
|
||||
}
|
||||
|
||||
fn build_batches(&self) -> impl Iterator<Item = Batch<'_>> {
|
||||
let mut offset_start = 0;
|
||||
self.commit_points
|
||||
.iter()
|
||||
.copied()
|
||||
.chain([self.ops.len()])
|
||||
.map(move |point| {
|
||||
let batch = Batch {
|
||||
ops: &self.ops[offset_start..point],
|
||||
};
|
||||
offset_start = point;
|
||||
batch
|
||||
})
|
||||
}
|
||||
|
||||
pub fn build_all(&mut self) -> Batch<'_> {
|
||||
self.serialize_changes();
|
||||
Batch {
|
||||
ops: std::mem::take(&mut self.ops),
|
||||
ops: self.ops.as_slice(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn last_account_id(&self) -> Option<u32> {
|
||||
self.ops.iter().rev().find_map(|op| match op {
|
||||
Operation::AccountId { account_id } => Some(*account_id),
|
||||
_ => None,
|
||||
})
|
||||
pub fn changed_collections(&mut self) -> impl Iterator<Item = (&u32, &Bitmap<ShortId>)> {
|
||||
self.changed_collections.iter()
|
||||
}
|
||||
|
||||
pub fn has_logs(&self) -> bool {
|
||||
!self.changed_collections.is_empty()
|
||||
}
|
||||
|
||||
pub fn ops(&self) -> &[Operation] {
|
||||
self.ops.as_slice()
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
self.batch_size
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.ops.is_empty()
|
||||
|| !self.ops.iter().any(|op| {
|
||||
!matches!(
|
||||
op,
|
||||
Operation::AccountId { .. }
|
||||
| Operation::Collection { .. }
|
||||
| Operation::DocumentId { .. }
|
||||
| Operation::AssertValue { .. }
|
||||
)
|
||||
})
|
||||
self.batch_ops == 0
|
||||
}
|
||||
}
|
||||
|
||||
impl Batch {
|
||||
impl Batch<'_> {
|
||||
pub fn is_atomic(&self) -> bool {
|
||||
!self.ops.iter().any(|op| {
|
||||
matches!(
|
||||
|
||||
Reference in New Issue
Block a user