use kevy_index::{IndexKind, IndexSpec, Segment};
use crate::ops_index::{IndexReg, ShardSegs};
impl ShardSegs {
#[cfg(all(feature = "tier", not(target_arch = "wasm32")))]
pub(crate) fn reserved_bytes(&mut self) -> u64 {
if !self.stats_dirty {
return self.reserved_cache;
}
let mut sum: u64 = self.segs.iter().map(|(_, s)| s.stats().approx_bytes).sum();
sum += self.agg.iter().map(|(_, a)| a.stats().approx_bytes).sum::<u64>();
#[cfg(feature = "text")]
{
sum += self.text.iter().map(|(_, t)| t.stats().approx_bytes).sum::<u64>();
}
#[cfg(feature = "vector")]
{
sum += self.ann.iter().map(|(_, g)| g.stats().approx_bytes).sum::<u64>();
}
self.reserved_cache = sum;
self.stats_dirty = false;
sum
}
}
#[cfg(all(feature = "tier", not(target_arch = "wasm32")))]
pub(crate) fn tier_floor_check(shards: &crate::store::Shards) -> crate::KevyResult<()> {
for shard in shards.iter() {
let mut g = crate::store::lock_write(shard);
if !g.store.tier_enabled() {
break;
}
let reserved = g.idx_segs.reserved_bytes() + g.view_segs.reserved_bytes();
g.store.set_tier_reserved(reserved);
if g.store.tier_index_floor_blocked(0) {
return Err(crate::KevyError::InvalidInput(
"index memory floor exceeds the tiering budget".into(),
));
}
}
Ok(())
}
#[cfg(feature = "text")]
pub(crate) fn new_text(spec: &IndexSpec) -> kevy_text::TextSegment {
kevy_text::TextSegment::with_shape(kevy_text::SegmentShape {
fields: spec.fields.len(),
positions: spec.with_positions,
values: spec.values.len(),
})
}
#[cfg(feature = "vector")]
pub(crate) fn new_graph(spec: &IndexSpec) -> kevy_vector::Hnsw {
let a = spec.ann.as_ref().expect("ann spec");
kevy_vector::Hnsw::new(
a.dim as usize,
kevy_vector::HnswParams {
m: a.m as usize,
ef_construction: a.ef as usize,
distance: match a.distance {
1 => kevy_vector::Distance::L2,
2 => kevy_vector::Distance::Ip,
_ => kevy_vector::Distance::Cosine,
},
},
)
}
pub(crate) fn sync_segs(
reg: &IndexReg,
shard_segs: &mut ShardSegs,
store: &mut kevy_store::Store,
) {
let g = reg
.catalog
.read()
.unwrap_or_else(std::sync::PoisonError::into_inner);
let (ver, cat) = &*g;
if shard_segs.version == *ver {
return;
}
rebuild_seg_lists(cat, *ver, shard_segs, store);
}
fn rebuild_seg_lists(
cat: &kevy_index::Catalog,
ver: u64,
shard_segs: &mut ShardSegs,
store: &mut kevy_store::Store,
) {
let mut next: Vec<(IndexSpec, Segment)> = Vec::new();
#[cfg(feature = "text")]
let mut next_text: Vec<(IndexSpec, kevy_text::TextSegment)> = Vec::new();
#[cfg(feature = "vector")]
let mut next_ann: Vec<(IndexSpec, kevy_vector::Hnsw)> = Vec::new();
let mut next_agg: Vec<(IndexSpec, kevy_index::AggSegment)> = Vec::new();
for (spec, _) in cat.iter() {
let (segs, st) = (&mut *shard_segs, &mut *store);
match spec.kind {
IndexKind::Agg => next_agg
.push(take_or_backfill(&mut segs.agg, spec, st, kevy_index::AggSegment::new, apply_agg_key)),
#[cfg(feature = "vector")]
IndexKind::Ann => next_ann
.push(take_or_backfill(&mut segs.ann, spec, st, || new_graph(spec), apply_ann_key)),
#[cfg(not(feature = "vector"))]
IndexKind::Ann => {}
#[cfg(feature = "text")]
IndexKind::Text => next_text
.push(take_or_backfill(&mut segs.text, spec, st, || new_text(spec), apply_text_key)),
#[cfg(not(feature = "text"))]
IndexKind::Text => {}
_ => next.push(take_or_backfill(&mut segs.segs, spec, st, || new_scalar(spec), apply_key)),
}
}
shard_segs.segs = next;
#[cfg(feature = "text")]
{
shard_segs.text = next_text;
}
#[cfg(feature = "vector")]
{
shard_segs.ann = next_ann;
}
shard_segs.agg = next_agg;
shard_segs.version = ver;
shard_segs.mark_stats_dirty();
}
fn take_or_backfill<S>(
have: &mut Vec<(IndexSpec, S)>,
spec: &IndexSpec,
store: &mut kevy_store::Store,
empty: impl FnOnce() -> S,
apply: impl Fn(&mut kevy_store::Store, &IndexSpec, &mut S, &[u8]),
) -> (IndexSpec, S) {
if let Some(i) = have.iter().position(|(s, _)| s == spec) {
return have.swap_remove(i);
}
let mut seg = empty();
let mut pat = spec.prefix.clone();
pat.push(b'*');
for key in store.collect_keys(Some(&pat), None) {
apply(store, spec, &mut seg, &key);
}
(spec.clone(), seg)
}
fn apply_agg_key(
store: &mut kevy_store::Store,
spec: &IndexSpec,
a: &mut kevy_index::AggSegment,
key: &[u8],
) {
let group_field = spec.group_by.as_deref().unwrap_or_default();
match store.peek_hash_fields(key, &[group_field, spec.field()]) {
Ok(Some(mut vals)) => {
let group = vals[0].take();
let val =
vals[1].take().and_then(|raw| kevy_index::IndexValue::coerce(spec.ty, &raw));
match (group, val) {
(Some(g), Some(v)) => a.apply(key, Some((g, v)), false),
_ => a.apply(key, None, true),
}
}
Ok(None) => a.apply(key, None, false),
Err(_) => a.apply(key, None, true),
}
}
#[cfg(feature = "vector")]
fn apply_ann_key(
store: &mut kevy_store::Store,
spec: &IndexSpec,
g: &mut kevy_vector::Hnsw,
key: &[u8],
) {
let v = match store.peek_hash_fields(key, &[spec.field()]) {
Ok(Some(mut vals)) => {
vals[0].take().and_then(|raw| kevy_vector::parse_vector(&raw, g.dim()))
}
_ => None,
};
g.apply(key, v);
}
#[cfg(feature = "text")]
fn apply_text_key(
store: &mut kevy_store::Store,
spec: &IndexSpec,
ts: &mut kevy_text::TextSegment,
key: &[u8],
) {
let names: Vec<&[u8]> = spec
.fields
.iter()
.map(|f| f.name.as_slice())
.chain(spec.values.iter().map(|v| v.name.as_slice()))
.collect();
let fetched = store.peek_hash_fields(key, &names).ok().flatten();
let (fields, values) = spec.read_row(|f| {
let vals = fetched.as_ref()?;
names.iter().position(|n| *n == f).and_then(|i| vals[i].clone())
});
let vals: Vec<Option<&[u8]>> = values.iter().map(|v| v.as_deref()).collect();
if fields.is_empty() {
ts.apply_doc(key, None, &vals);
} else {
ts.apply_doc(key, Some(&fields), &vals);
}
}
pub(crate) fn on_commit(
reg: &IndexReg,
shard_segs: &mut ShardSegs,
store: &mut kevy_store::Store,
parts: &[&[u8]],
) {
{
let g = reg
.catalog
.read()
.unwrap_or_else(std::sync::PoisonError::into_inner);
if g.1.is_empty() {
return;
}
}
sync_segs(reg, shard_segs, store);
let verb = parts.first().copied().unwrap_or(b"");
if verb.eq_ignore_ascii_case(b"FLUSHALL") || verb.eq_ignore_ascii_case(b"FLUSHDB") {
reset_all_segs(shard_segs);
shard_segs.mark_stats_dirty();
return;
}
let mut touched = false;
each_written_key(verb, parts, |key| {
touched |= apply_one_key(shard_segs, store, key);
});
if touched {
shard_segs.mark_stats_dirty();
}
}
fn apply_one_key(shard_segs: &mut ShardSegs, store: &mut kevy_store::Store, key: &[u8]) -> bool {
let mut touched = false;
for (spec, seg) in &mut shard_segs.segs {
if key.starts_with(&spec.prefix) {
apply_key(store, spec, seg, key);
touched = true;
}
}
#[cfg(feature = "text")]
for (spec, ts) in &mut shard_segs.text {
if key.starts_with(&spec.prefix) {
apply_text_key(store, spec, ts, key);
touched = true;
}
}
#[cfg(feature = "vector")]
for (spec, g) in &mut shard_segs.ann {
if key.starts_with(&spec.prefix) {
apply_ann_key(store, spec, g, key);
touched = true;
}
}
for (spec, a) in &mut shard_segs.agg {
if key.starts_with(&spec.prefix) {
apply_agg_key(store, spec, a, key);
touched = true;
}
}
touched
}
fn reset_all_segs(shard_segs: &mut ShardSegs) {
for (_, seg) in &mut shard_segs.segs {
*seg = Segment::new();
}
#[cfg(feature = "text")]
for (spec, ts) in &mut shard_segs.text {
*ts = new_text(spec);
}
#[cfg(feature = "vector")]
for (spec, g) in &mut shard_segs.ann {
*g = new_graph(spec);
}
for (_, a) in &mut shard_segs.agg {
*a = kevy_index::AggSegment::new();
}
}
pub(crate) fn each_written_key_pub(verb: &[u8], parts: &[&[u8]], f: impl FnMut(&[u8])) {
each_written_key(verb, parts, f);
}
fn each_written_key(verb: &[u8], parts: &[&[u8]], mut f: impl FnMut(&[u8])) {
let up = |v: &[u8], t: &[u8]| v.eq_ignore_ascii_case(t);
if up(verb, b"DEL") || up(verb, b"UNLINK") {
for k in &parts[1..] {
f(k);
}
} else if up(verb, b"MSET") {
let mut i = 1;
while i + 1 < parts.len() {
f(parts[i]);
i += 2;
}
} else if up(verb, b"COPY") || up(verb, b"RENAME") || up(verb, b"RENAMENX") {
if let Some(k) = parts.get(1) {
f(k);
}
if let Some(k) = parts.get(2) {
f(k);
}
} else if let Some(k) = parts.get(1) {
f(k);
}
}
fn new_scalar(spec: &IndexSpec) -> Segment {
if spec.values.is_empty() {
Segment::new()
} else {
Segment::with_values(spec.values.len())
}
}
fn apply_key(store: &mut kevy_store::Store, spec: &IndexSpec, seg: &mut Segment, key: &[u8]) {
let names = spec.scalar_read_names();
let w = spec.primary_width();
match store.peek_hash_fields(key, &names) {
Ok(None) | Err(_) => seg.remove(key),
Ok(Some(vals)) => {
let primary = spec.derive_scalar(&vals[..w]);
match primary {
None => seg.apply_with_values(key, None, &[]),
Some(v) if spec.values.is_empty() => seg.apply(key, Some(v)),
Some(v) => {
let refs: Vec<Option<&[u8]>> =
vals[w..].iter().map(|o| o.as_deref()).collect();
seg.apply_with_values(key, Some(v), &refs);
}
}
}
}
}