mirror of
https://github.com/bitcoinresearchkit/brk.git
synced 2026-07-24 09:28:11 -07:00
store: better caching layer
This commit is contained in:
@@ -55,7 +55,7 @@ fn main() -> Result<()> {
|
|||||||
|
|
||||||
loop {
|
loop {
|
||||||
let i = Instant::now();
|
let i = Instant::now();
|
||||||
indexer.checked_index(&blocks, &client, &exit)?;
|
indexer.index(&blocks, &client, &exit)?;
|
||||||
info!("Done in {:?}", i.elapsed());
|
info!("Done in {:?}", i.elapsed());
|
||||||
|
|
||||||
sleep(Duration::from_secs(60));
|
sleep(Duration::from_secs(60));
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ impl Stores {
|
|||||||
let database_ref = &database;
|
let database_ref = &database;
|
||||||
|
|
||||||
let create_addresshash_to_addressindex_store = |index| {
|
let create_addresshash_to_addressindex_store = |index| {
|
||||||
Store::import(
|
Store::import_cached(
|
||||||
database_ref,
|
database_ref,
|
||||||
path,
|
path,
|
||||||
&format!("h2i{}", index),
|
&format!("h2i{}", index),
|
||||||
@@ -66,7 +66,6 @@ impl Stores {
|
|||||||
version,
|
version,
|
||||||
Mode3::PushOnly,
|
Mode3::PushOnly,
|
||||||
Kind3::Vec,
|
Kind3::Vec,
|
||||||
0,
|
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -78,7 +77,6 @@ impl Stores {
|
|||||||
version,
|
version,
|
||||||
Mode3::Any,
|
Mode3::Any,
|
||||||
Kind3::Vec,
|
Kind3::Vec,
|
||||||
0,
|
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -92,7 +90,6 @@ impl Stores {
|
|||||||
version,
|
version,
|
||||||
Mode3::PushOnly,
|
Mode3::PushOnly,
|
||||||
Kind3::Sequential,
|
Kind3::Sequential,
|
||||||
0,
|
|
||||||
)?,
|
)?,
|
||||||
addresstype_to_addresshash_to_addressindex: ByAddressType::new_with_index(
|
addresstype_to_addresshash_to_addressindex: ByAddressType::new_with_index(
|
||||||
create_addresshash_to_addressindex_store,
|
create_addresshash_to_addressindex_store,
|
||||||
@@ -110,9 +107,8 @@ impl Stores {
|
|||||||
version,
|
version,
|
||||||
Mode3::PushOnly,
|
Mode3::PushOnly,
|
||||||
Kind3::Random,
|
Kind3::Random,
|
||||||
0,
|
|
||||||
)?,
|
)?,
|
||||||
txidprefix_to_txindex: Store::import(
|
txidprefix_to_txindex: Store::import_cached(
|
||||||
database_ref,
|
database_ref,
|
||||||
path,
|
path,
|
||||||
"txidprefix_to_txindex",
|
"txidprefix_to_txindex",
|
||||||
|
|||||||
@@ -3,29 +3,13 @@ use std::{borrow::Cow, cmp::Ordering, fmt::Debug, fs, hash::Hash, mem, path::Pat
|
|||||||
use brk_error::Result;
|
use brk_error::Result;
|
||||||
use brk_types::{Height, Version};
|
use brk_types::{Height, Version};
|
||||||
use byteview_f3::ByteView;
|
use byteview_f3::ByteView;
|
||||||
use fjall3::{
|
use fjall3::{Database, Keyspace, KeyspaceCreateOptions, config::*};
|
||||||
Database, Keyspace, KeyspaceCreateOptions,
|
|
||||||
config::{
|
|
||||||
BloomConstructionPolicy, FilterPolicy, FilterPolicyEntry, PartitioningPolicy, PinningPolicy,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
mod meta;
|
|
||||||
|
|
||||||
use meta::*;
|
|
||||||
use rustc_hash::{FxHashMap, FxHashSet};
|
use rustc_hash::{FxHashMap, FxHashSet};
|
||||||
|
|
||||||
use crate::any::AnyStore;
|
mod meta;
|
||||||
|
use meta::*;
|
||||||
|
|
||||||
#[derive(Clone)]
|
use crate::any::AnyStore;
|
||||||
pub struct StoreFjallV3<Key, Value> {
|
|
||||||
meta: StoreMeta,
|
|
||||||
name: &'static str,
|
|
||||||
keyspace: Keyspace,
|
|
||||||
puts: FxHashMap<Key, Value>,
|
|
||||||
dels: FxHashSet<Key>,
|
|
||||||
prev_puts: Vec<FxHashMap<Key, Value>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
const MAJOR_FJALL_VERSION: Version = Version::new(3);
|
const MAJOR_FJALL_VERSION: Version = Version::new(3);
|
||||||
|
|
||||||
@@ -35,6 +19,16 @@ pub fn open_fjall3_database(path: &Path) -> fjall3::Result<Database> {
|
|||||||
.open()
|
.open()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct StoreFjallV3<K, V> {
|
||||||
|
meta: StoreMeta,
|
||||||
|
name: &'static str,
|
||||||
|
keyspace: Keyspace,
|
||||||
|
puts: FxHashMap<K, V>,
|
||||||
|
dels: FxHashSet<K>,
|
||||||
|
cache: Option<Cache<K, V>>,
|
||||||
|
}
|
||||||
|
|
||||||
impl<K, V> StoreFjallV3<K, V>
|
impl<K, V> StoreFjallV3<K, V>
|
||||||
where
|
where
|
||||||
K: Debug + Clone + From<ByteView> + Ord + Eq + Hash,
|
K: Debug + Clone + From<ByteView> + Ord + Eq + Hash,
|
||||||
@@ -49,7 +43,30 @@ where
|
|||||||
version: Version,
|
version: Version,
|
||||||
mode: Mode3,
|
mode: Mode3,
|
||||||
kind: Kind3,
|
kind: Kind3,
|
||||||
cached_commits: usize,
|
) -> Result<Self> {
|
||||||
|
Self::import_inner(db, path, name, version, mode, kind, None)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn import_cached(
|
||||||
|
db: &Database,
|
||||||
|
path: &Path,
|
||||||
|
name: &str,
|
||||||
|
version: Version,
|
||||||
|
mode: Mode3,
|
||||||
|
kind: Kind3,
|
||||||
|
max_batches: u16,
|
||||||
|
) -> Result<Self> {
|
||||||
|
Self::import_inner(db, path, name, version, mode, kind, Some(max_batches))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn import_inner(
|
||||||
|
db: &Database,
|
||||||
|
path: &Path,
|
||||||
|
name: &str,
|
||||||
|
version: Version,
|
||||||
|
mode: Mode3,
|
||||||
|
kind: Kind3,
|
||||||
|
max_batches: Option<u16>,
|
||||||
) -> Result<Self> {
|
) -> Result<Self> {
|
||||||
fs::create_dir_all(path)?;
|
fs::create_dir_all(path)?;
|
||||||
|
|
||||||
@@ -65,10 +82,7 @@ where
|
|||||||
},
|
},
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let mut prev_puts = vec![];
|
let cache = max_batches.map(|max_batches| Cache::new(max_batches));
|
||||||
for _ in 0..cached_commits {
|
|
||||||
prev_puts.push(FxHashMap::default());
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
meta,
|
meta,
|
||||||
@@ -76,7 +90,7 @@ where
|
|||||||
keyspace,
|
keyspace,
|
||||||
puts: FxHashMap::default(),
|
puts: FxHashMap::default(),
|
||||||
dels: FxHashSet::default(),
|
dels: FxHashSet::default(),
|
||||||
prev_puts,
|
cache,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -125,10 +139,10 @@ where
|
|||||||
return Ok(Some(Cow::Borrowed(v)));
|
return Ok(Some(Cow::Borrowed(v)));
|
||||||
}
|
}
|
||||||
|
|
||||||
for prev in &self.prev_puts {
|
if let Some(cache) = &self.cache
|
||||||
if let Some(v) = prev.get(key) {
|
&& let Some(v) = cache.get(key)
|
||||||
return Ok(Some(Cow::Borrowed(v)));
|
{
|
||||||
}
|
return Ok(Some(Cow::Borrowed(v)));
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(slice) = self.keyspace.get(ByteView::from(key))? {
|
if let Some(slice) = self.keyspace.get(ByteView::from(key))? {
|
||||||
@@ -143,6 +157,12 @@ where
|
|||||||
self.keyspace.is_empty().map_err(|e| e.into())
|
self.keyspace.is_empty().map_err(|e| e.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn insert(&mut self, key: K, value: V) {
|
||||||
|
let _ = self.dels.is_empty() || self.dels.remove(&key);
|
||||||
|
self.puts.insert(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn insert_if_needed(&mut self, key: K, value: V, height: Height) {
|
pub fn insert_if_needed(&mut self, key: K, value: V, height: Height) {
|
||||||
if self.needs(height) {
|
if self.needs(height) {
|
||||||
@@ -150,19 +170,11 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn insert(&mut self, key: K, value: V) {
|
|
||||||
let _ = self.dels.is_empty() || self.dels.remove(&key);
|
|
||||||
self.puts.insert(key, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn remove(&mut self, key: K) {
|
pub fn remove(&mut self, key: K) {
|
||||||
// Hot path: key was recently inserted
|
|
||||||
if self.puts.remove(&key).is_some() {
|
if self.puts.remove(&key).is_some() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let newly_inserted = self.dels.insert(key);
|
let newly_inserted = self.dels.insert(key);
|
||||||
debug_assert!(newly_inserted, "Double deletion at {:?}", self.meta.path());
|
debug_assert!(newly_inserted, "Double deletion at {:?}", self.meta.path());
|
||||||
}
|
}
|
||||||
@@ -191,6 +203,13 @@ where
|
|||||||
fn needs(&self, height: Height) -> bool {
|
fn needs(&self, height: Height) -> bool {
|
||||||
self.meta.needs(height)
|
self.meta.needs(height)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn export_meta_if_needed(&mut self, height: Height) -> Result<()> {
|
||||||
|
if !self.has(height) {
|
||||||
|
self.meta.export(height)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<K, V> AnyStore for StoreFjallV3<K, V>
|
impl<K, V> AnyStore for StoreFjallV3<K, V>
|
||||||
@@ -200,7 +219,7 @@ where
|
|||||||
ByteView: From<K> + From<V>,
|
ByteView: From<K> + From<V>,
|
||||||
Self: Send + Sync,
|
Self: Send + Sync,
|
||||||
{
|
{
|
||||||
fn keyspace(&self) -> &fjall3::Keyspace {
|
fn keyspace(&self) -> &Keyspace {
|
||||||
&self.keyspace
|
&self.keyspace
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -213,11 +232,7 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn export_meta_if_needed(&mut self, height: Height) -> Result<()> {
|
fn export_meta_if_needed(&mut self, height: Height) -> Result<()> {
|
||||||
if self.has(height) {
|
self.export_meta_if_needed(height)
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
self.meta.export(height)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn name(&self) -> &'static str {
|
fn name(&self) -> &'static str {
|
||||||
@@ -244,48 +259,38 @@ where
|
|||||||
self.export_meta_if_needed(height)?;
|
self.export_meta_if_needed(height)?;
|
||||||
|
|
||||||
let puts = mem::take(&mut self.puts);
|
let puts = mem::take(&mut self.puts);
|
||||||
|
let dels = mem::take(&mut self.dels);
|
||||||
|
|
||||||
if !self.prev_puts.is_empty() {
|
if puts.is_empty() && dels.is_empty() {
|
||||||
self.prev_puts.pop();
|
|
||||||
self.prev_puts.insert(0, puts.clone());
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut items = puts
|
|
||||||
.into_iter()
|
|
||||||
.map(|(key, value)| Item::Value { key, value })
|
|
||||||
.chain(
|
|
||||||
mem::take(&mut self.dels)
|
|
||||||
.into_iter()
|
|
||||||
.map(|key| Item::Tomb(key)),
|
|
||||||
)
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
if items.is_empty() {
|
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
items.sort_unstable();
|
// Insert into cache here
|
||||||
|
if let Some(cache) = &mut self.cache {
|
||||||
|
for (k, v) in &puts {
|
||||||
|
cache.insert(k.clone(), v.clone());
|
||||||
|
}
|
||||||
|
cache.commit();
|
||||||
|
}
|
||||||
|
|
||||||
// let mut batch = OwnedWriteBatch::with_capacity(self.db.clone(), items.len());
|
let mut items: Vec<_> = puts
|
||||||
// let p = self.keyspace();
|
.into_iter()
|
||||||
// for item in items {
|
.map(|(key, value)| Item::Value { key, value })
|
||||||
// match item {
|
.chain(dels.into_iter().map(Item::Tomb))
|
||||||
// Item::Value { key, value } => {
|
.collect();
|
||||||
// batch.insert(p, ByteView::from(key), ByteView::from(value))
|
|
||||||
// }
|
items.sort_unstable();
|
||||||
// Item::Tomb(key) => batch.remove(p, ByteView::from(key)),
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// batch.commit()?;
|
|
||||||
|
|
||||||
let mut ingestion = self.keyspace.start_ingestion()?;
|
let mut ingestion = self.keyspace.start_ingestion()?;
|
||||||
for item in items {
|
for item in items {
|
||||||
match item {
|
match item {
|
||||||
Item::Value { key, value } => {
|
Item::Value { key, value } => {
|
||||||
ingestion.write(ByteView::from(key), ByteView::from(value))
|
ingestion.write(ByteView::from(key), ByteView::from(value))?;
|
||||||
}
|
}
|
||||||
Item::Tomb(key) => ingestion.write_tombstone(ByteView::from(key)),
|
Item::Tomb(key) => {
|
||||||
}?
|
ingestion.write_tombstone(ByteView::from(key))?;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
ingestion.finish()?;
|
ingestion.finish()?;
|
||||||
|
|
||||||
@@ -293,45 +298,43 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum Item<K, V> {
|
enum Item<K, V> {
|
||||||
Value { key: K, value: V },
|
Value { key: K, value: V },
|
||||||
Tomb(K),
|
Tomb(K),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<K: Ord, V> Ord for Item<K, V> {
|
|
||||||
fn cmp(&self, other: &Self) -> Ordering {
|
|
||||||
self.key().cmp(other.key())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<K: Ord, V> PartialOrd for Item<K, V> {
|
|
||||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
|
||||||
Some(self.cmp(other))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<K: Eq, V> PartialEq for Item<K, V> {
|
|
||||||
fn eq(&self, other: &Self) -> bool {
|
|
||||||
self.key() == other.key()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<K: Eq, V> Eq for Item<K, V> {}
|
|
||||||
|
|
||||||
impl<K, V> Item<K, V> {
|
impl<K, V> Item<K, V> {
|
||||||
|
#[inline]
|
||||||
fn key(&self) -> &K {
|
fn key(&self) -> &K {
|
||||||
match self {
|
match self {
|
||||||
Self::Value { key, .. } | Self::Tomb(key) => key,
|
Self::Value { key, .. } | Self::Tomb(key) => key,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
impl<K: Ord, V> Ord for Item<K, V> {
|
||||||
|
#[inline]
|
||||||
|
fn cmp(&self, other: &Self) -> Ordering {
|
||||||
|
self.key().cmp(other.key())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<K: Ord, V> PartialOrd for Item<K, V> {
|
||||||
|
#[inline]
|
||||||
|
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||||
|
Some(self.cmp(other))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<K: Eq, V> PartialEq for Item<K, V> {
|
||||||
|
#[inline]
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
self.key() == other.key()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<K: Eq, V> Eq for Item<K, V> {}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub enum Mode3 {
|
pub enum Mode3 {
|
||||||
Any,
|
Any,
|
||||||
PushOnly,
|
PushOnly,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Mode3 {
|
impl Mode3 {
|
||||||
pub fn is_any(&self) -> bool {
|
pub fn is_any(&self) -> bool {
|
||||||
matches!(*self, Self::Any)
|
matches!(*self, Self::Any)
|
||||||
@@ -348,7 +351,6 @@ pub enum Kind3 {
|
|||||||
Sequential,
|
Sequential,
|
||||||
Vec,
|
Vec,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Kind3 {
|
impl Kind3 {
|
||||||
pub fn is_sequential(&self) -> bool {
|
pub fn is_sequential(&self) -> bool {
|
||||||
matches!(*self, Self::Sequential)
|
matches!(*self, Self::Sequential)
|
||||||
@@ -362,3 +364,42 @@ impl Kind3 {
|
|||||||
!matches!(*self, Self::Vec)
|
!matches!(*self, Self::Vec)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
struct Cache<K, V> {
|
||||||
|
index: FxHashMap<K, (V, u16)>,
|
||||||
|
current_batch: u16,
|
||||||
|
max_batches: u16,
|
||||||
|
}
|
||||||
|
impl<K: Hash + Eq + Clone, V: Clone> Cache<K, V> {
|
||||||
|
fn new(max_batches: u16) -> Self {
|
||||||
|
Self {
|
||||||
|
index: FxHashMap::default(),
|
||||||
|
current_batch: 0,
|
||||||
|
max_batches,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn get(&self, key: &K) -> Option<&V> {
|
||||||
|
self.index.get(key).map(|(v, _)| v)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn insert(&mut self, key: K, value: V) {
|
||||||
|
self.index.insert(key, (value, self.current_batch));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn commit(&mut self) {
|
||||||
|
let max = self.max_batches;
|
||||||
|
let current = self.current_batch;
|
||||||
|
self.index
|
||||||
|
.retain(|_, (_, batch)| current.wrapping_sub(*batch) < max);
|
||||||
|
self.current_batch = self.current_batch.wrapping_add(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn clear(&mut self) {
|
||||||
|
self.index.clear();
|
||||||
|
self.current_batch = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user