use crate::{
api::key::opt::{DBScanInfo, KeyNumStats},
engine::{Engine, KvEntry, Partition},
error::{Error, Result},
key_composer::KeyTag,
meta::{KeyMeta, current_now_ms},
string::{
compose_string_prefix_stack as string_key_prefix_stack, decode_string_value, is_string_expired,
},
wedb::Db,
};
impl<E: Engine> Db<E>
where
Error: From<E::Error>,
{
#[inline]
pub fn key_count(&self) -> Result<usize> {
key_count_impl(self)
}
#[inline]
pub fn dbsize(&self) -> Result<usize> {
self.key_count()
}
#[inline]
pub fn dbsize_scan(&self) -> Result<KeyNumStats> {
dbsize_scan_impl(self)
}
#[inline]
pub fn dbsize_cached(&self) -> usize {
let kc = self.kc();
self
.inner
.db_scan_infos
.read()
.get(&(kc.ns_id(), kc.db()))
.map(|info| info.stats.n_key)
.unwrap_or(0)
}
#[inline]
pub fn key_num_stats(&self) -> KeyNumStats {
let kc = self.kc();
self
.inner
.db_scan_infos
.read()
.get(&(kc.ns_id(), kc.db()))
.map(|info| info.stats)
.unwrap_or_default()
}
#[inline]
pub fn last_dbsize_scan_time(&self) -> u64 {
let kc = self.kc();
self
.inner
.db_scan_infos
.read()
.get(&(kc.ns_id(), kc.db()))
.map(|info| info.last_scan_time_secs)
.unwrap_or(0)
}
#[inline]
pub fn keyspace_info_string(&self) -> String {
let stats = self.key_num_stats();
format!(
"keys={},expires={},avg_ttl={},expired={}",
stats.n_key, stats.n_expires, stats.avg_ttl, stats.n_expired
)
}
}
pub fn dbsize_scan_impl<E: Engine>(db: &Db<E>) -> Result<KeyNumStats>
where
Error: From<E::Error>,
{
let mut stats = KeyNumStats::default();
let mut ttl_sum_ms = 0u64;
let now_ms = current_now_ms();
let kc = db.kc();
let data_ks = db.data();
let meta_ks = db.meta();
let str_prefix = string_key_prefix_stack(&kc);
for item in data_ks.prefix(&str_prefix) {
let entry = item?;
let k = entry.key();
if !k.starts_with(str_prefix.as_slice()) {
break;
}
let (expire_at, _) = decode_string_value(entry.value());
if is_string_expired(expire_at, now_ms) {
stats.n_expired += 1;
} else {
stats.n_key += 1;
if expire_at > 0 {
stats.n_expires += 1;
ttl_sum_ms += expire_at.saturating_sub(now_ms);
}
}
}
let meta_prefix = kc.namespace_prefix_stack();
let scope_prefix_len = kc.scope_prefix_len();
for item in meta_ks.prefix(&meta_prefix) {
let entry = item?;
let k = entry.key();
if !k.starts_with(meta_prefix.as_slice()) {
break;
}
let remain = &k[scope_prefix_len..];
if remain.is_empty() {
continue;
}
let Some(tag) = KeyTag::from_u8(remain[0]) else {
continue;
};
if !tag.is_meta() {
continue;
}
if let Some(meta) = KeyMeta::decode(entry.value()) {
if meta.is_expired(now_ms) {
stats.n_expired += 1;
} else {
stats.n_key += 1;
if meta.expire_at > 0 {
stats.n_expires += 1;
ttl_sum_ms += meta.expire_at.saturating_sub(now_ms);
}
}
}
}
if stats.n_expires > 0 {
stats.avg_ttl = (ttl_sum_ms / stats.n_expires as u64) / 1000;
}
let scan_info = DBScanInfo {
stats,
last_scan_time_secs: now_ms / 1000,
};
db.inner
.db_scan_infos
.write()
.insert((kc.ns_id(), kc.db()), scan_info);
Ok(stats)
}
#[inline]
pub fn key_count_impl<E: Engine>(db: &Db<E>) -> Result<usize>
where
Error: From<E::Error>,
{
let stats = dbsize_scan_impl(db)?;
Ok(stats.n_key)
}