1use crate::format::base::{self, nflags, NodeRec, SegRec};
23use crate::format::wal::{self, AttrsView, EdgesView, OpView, SegsView, TouchView, UpsertView};
24use crate::key::{NodeId, SegKey, UrlKey};
25use crate::overlay::{KeyMap, OpRef, OverlayNode, OverlayView};
26use crate::store::{GenInner, Store};
27use crate::traverse::{Neighbors, NeighborsInner};
28use crate::ttl::TtlConfig;
29use std::cell::OnceCell;
30use std::sync::RwLockReadGuard;
31
32pub struct Snapshot<'s> {
34 store: &'s Store,
35 guard: RwLockReadGuard<'s, Option<GenInner>>,
36 frontier: usize,
37 view: OnceCell<OverlayView>,
38 inbound: OnceCell<KeyMap<Vec<wal::Edge>>>,
40 hub_threshold: OnceCell<u32>,
41 live: OnceCell<usize>,
42}
43
44impl std::fmt::Debug for Snapshot<'_> {
45 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
46 f.debug_struct("Snapshot")
47 .field("generation", &self.generation())
48 .field("overlay_ops", &self.frontier)
49 .finish()
50 }
51}
52
53#[derive(Clone, Copy, Debug)]
55pub struct NodeRef<'s> {
56 pub key: UrlKey,
58 pub id: Option<NodeId>,
61 pub url: &'s str,
63 pub title: Option<&'s str>,
65 pub snippet: Option<&'s str>,
67 pub etag: Option<&'s str>,
69 pub content_hash: u64,
71 pub fetched_at_ms: u64,
73 pub last_change_ms: u64,
75 pub interval_s: u32,
77 pub checks: u16,
79 pub changes: u16,
81 pub flags: u16,
83 pub rank_permille: u16,
85}
86
87impl NodeRef<'_> {
88 #[must_use]
90 pub fn pinned(&self) -> bool {
91 self.flags & nflags::PINNED != 0
92 }
93 #[must_use]
95 pub fn is_stub(&self) -> bool {
96 self.flags & nflags::STUB != 0
97 }
98 #[must_use]
100 pub fn is_tombstone(&self) -> bool {
101 self.flags & nflags::TOMBSTONE != 0
102 }
103}
104
105#[derive(Clone, Copy, Debug)]
107pub struct SegRef<'s> {
108 pub key: SegKey,
110 pub label: &'s str,
112 pub byte_range: Option<(u32, u32)>,
114 pub depth: u8,
116 pub importance: u16,
118 pub flags: u8,
120}
121
122#[derive(Clone, Copy, Debug)]
124pub struct DueItem<'s> {
125 pub url: &'s str,
127 pub key: UrlKey,
129 pub etag: Option<&'s str>,
131 pub overdue_ms: u64,
133 pub rank_permille: u16,
135}
136
137impl<'s> Snapshot<'s> {
138 pub(crate) fn new(store: &'s Store, guard: RwLockReadGuard<'s, Option<GenInner>>) -> Self {
139 let frontier = guard.as_ref().map_or(0, |g| g.log.len());
140 Self {
141 store,
142 guard,
143 frontier,
144 view: OnceCell::new(),
145 inbound: OnceCell::new(),
146 hub_threshold: OnceCell::new(),
147 live: OnceCell::new(),
148 }
149 }
150
151 fn inner(&self) -> &GenInner {
152 self.guard.as_ref().expect("snapshot pins a populated slot")
153 }
154
155 pub(crate) fn ttl(&self) -> &TtlConfig {
156 &self.store.cfg.ttl
157 }
158
159 pub(crate) fn overlay(&self) -> &OverlayView {
160 self.view.get_or_init(|| OverlayView::build(&self.inner().log, self.frontier))
161 }
162
163 fn op(&self, r: Option<OpRef>) -> Option<OpView<'_>> {
166 r.and_then(|r| self.inner().log.view(r))
167 }
168 fn upsert_op(&self, r: Option<OpRef>) -> Option<UpsertView<'_>> {
169 match self.op(r)? {
170 OpView::Upsert(v) => Some(v),
171 _ => None,
172 }
173 }
174 fn touch_op(&self, r: Option<OpRef>) -> Option<TouchView<'_>> {
175 match self.op(r)? {
176 OpView::Touch(v) => Some(v),
177 _ => None,
178 }
179 }
180 fn segs_op(&self, r: Option<OpRef>) -> Option<SegsView<'_>> {
181 match self.op(r)? {
182 OpView::Segs(v) => Some(v),
183 _ => None,
184 }
185 }
186 pub(crate) fn edges_op(&self, r: Option<OpRef>) -> Option<EdgesView<'_>> {
187 match self.op(r)? {
188 OpView::Edges(v) => Some(v),
189 _ => None,
190 }
191 }
192 fn attrs_op(&self, r: Option<OpRef>) -> Option<AttrsView<'_>> {
193 match self.op(r)? {
194 OpView::Attrs(v) => Some(v),
195 _ => None,
196 }
197 }
198
199 #[must_use]
201 pub fn generation(&self) -> u64 {
202 self.inner().dir.header.generation
203 }
204
205 pub(crate) fn bytes(&self) -> &[u8] {
208 self.inner().bytes()
209 }
210 pub(crate) fn nodes_bytes(&self) -> &[u8] {
211 self.inner().dir.section(self.bytes(), base::SectionKind::Nodes)
212 }
213 pub(crate) fn labels(&self) -> &[u8] {
214 self.inner().dir.section(self.bytes(), base::SectionKind::Labels)
215 }
216 pub(crate) fn segs_bytes(&self) -> &[u8] {
217 self.inner().dir.section(self.bytes(), base::SectionKind::Segs)
218 }
219 pub(crate) fn edges_bytes(&self) -> &[u8] {
220 self.inner().dir.section(self.bytes(), base::SectionKind::Edges)
221 }
222 pub(crate) fn edge_index(&self) -> &[u8] {
223 self.inner().dir.section(self.bytes(), base::SectionKind::EdgeIndex)
224 }
225 pub(crate) fn section_bytes(&self, kind: base::SectionKind) -> &[u8] {
226 self.section(kind)
227 }
228 fn section(&self, kind: base::SectionKind) -> &[u8] {
229 self.inner().dir.section(self.bytes(), kind)
230 }
231 pub(crate) fn ranks_bytes(&self) -> &[u8] {
232 self.inner().dir.section(self.bytes(), base::SectionKind::Ranks)
233 }
234 pub(crate) fn lexicon(&self) -> &[u8] {
235 self.inner().dir.section(self.bytes(), base::SectionKind::Lexicon)
236 }
237 pub(crate) fn postings(&self) -> &[u8] {
238 self.inner().dir.section(self.bytes(), base::SectionKind::Postings)
239 }
240
241 #[must_use]
243 pub fn base_len(&self) -> usize {
244 self.inner().dir.header.node_count as usize
245 }
246
247 pub(crate) fn rec(&self, id: NodeId) -> NodeRec<'_> {
248 let i = id.0 as usize;
249 NodeRec(&self.nodes_bytes()[i * base::NODE_LEN..(i + 1) * base::NODE_LEN])
250 }
251
252 pub(crate) fn base_node_id(&self, key: UrlKey) -> Option<NodeId> {
253 base::find_node(self.nodes_bytes(), key).map(NodeId)
254 }
255
256 pub(crate) fn node_identity(&self, id: NodeId) -> (UrlKey, Option<&str>) {
258 let rec = self.rec(id);
259 (rec.url_key(), base::label_at(self.labels(), rec.url_off()))
260 }
261
262 pub(crate) fn rank_permille_of(&self, id: NodeId) -> u16 {
263 let ranks = self.ranks_bytes();
264 let off = id.0 as usize * base::RANK_LEN;
265 if off + base::RANK_LEN <= ranks.len() {
266 crate::bytesio::get_u16(ranks, off)
267 } else {
268 0
269 }
270 }
271
272 #[must_use]
276 pub fn node(&self, key: UrlKey) -> Option<NodeRef<'_>> {
277 let patch = self.overlay().map.get(&key.0);
278 if let Some(p) = patch {
279 if p.removed {
280 return None;
281 }
282 }
283 let base_id = self.base_node_id(key);
284 match (base_id, patch.and_then(|p| self.upsert_op(p.core).map(|c| (p, c)))) {
285 (_, Some((p, core))) => {
286 let mut flags = core.flags();
288 match p.pinned {
293 Some(pinned) => {
294 flags =
295 if pinned { flags | nflags::PINNED } else { flags & !nflags::PINNED };
296 }
297 None => {
298 if let (Some(id), false) = (base_id, p.severed) {
299 if self.rec(id).flags() & nflags::PINNED != 0 {
300 flags |= nflags::PINNED;
301 }
302 }
303 }
304 }
305 let (mut fetched, mut interval, mut checks, mut changes, mut last_change) =
306 (core.fetched_at_ms(), self.ttl().base_s, 0u16, 0u16, 0u64);
307 let mut content_hash = core.content_hash();
308 let mut etag: Option<&str> = core.etag();
309 if let Some(t) = self.touch_op(p.touch) {
310 fetched = t.checked_at_ms();
311 interval = t.interval_s();
312 checks = t.checks();
313 changes = t.changes();
314 last_change = t.last_change_ms();
315 if let Some(h) = t.content_hash() {
316 content_hash = h;
317 }
318 if let Some(e) = t.etag() {
319 etag = Some(e);
320 }
321 if t.tombstone() {
322 flags |= nflags::TOMBSTONE;
323 }
324 } else {
325 if let Some((c, ch, lc)) = p.carried {
331 checks = c;
332 changes = ch;
333 last_change = lc;
334 } else if let (Some(id), false) = (base_id, p.severed) {
335 let rec = self.rec(id);
336 checks = rec.checks();
337 changes = rec.changes();
338 last_change = rec.last_change_ms();
339 }
340 if let (Some(id), false) = (base_id, p.severed) {
341 let rec = self.rec(id);
342 if rec.content_hash() == content_hash {
343 interval = rec.interval_s().max(1);
344 }
345 }
346 }
347 Some(NodeRef {
348 key,
349 id: base_id,
350 url: core.url(),
351 title: core.title(),
352 snippet: core.snippet(),
353 etag,
354 content_hash,
355 fetched_at_ms: fetched,
356 last_change_ms: last_change,
357 interval_s: interval,
358 checks,
359 changes,
360 flags,
361 rank_permille: base_id.map_or(0, |id| self.rank_permille_of(id)),
362 })
363 }
364 (Some(id), None) => Some(self.base_node_ref(id, patch)),
365 (None, None) => None,
366 }
367 }
368
369 pub(crate) fn base_node_ref<'a>(
370 &'a self,
371 id: NodeId,
372 patch: Option<&'a OverlayNode>,
373 ) -> NodeRef<'a> {
374 let rec = self.rec(id);
375 let labels = self.labels();
376 let mut flags = rec.flags();
377 let (mut fetched, mut interval, mut checks, mut changes, mut last_change) = (
378 rec.fetched_at_ms(),
379 rec.interval_s(),
380 rec.checks(),
381 rec.changes(),
382 rec.last_change_ms(),
383 );
384 let mut content_hash = rec.content_hash();
385 let mut etag = base::label_at(labels, rec.etag_off());
386 if let Some(p) = patch {
387 if let Some(pinned) = p.pinned {
388 flags = if pinned { flags | nflags::PINNED } else { flags & !nflags::PINNED };
389 }
390 if let Some(t) = self.touch_op(p.touch) {
391 fetched = t.checked_at_ms();
392 interval = t.interval_s();
393 checks = t.checks();
394 changes = t.changes();
395 last_change = t.last_change_ms();
396 if let Some(h) = t.content_hash() {
397 content_hash = h;
398 }
399 if let Some(e) = t.etag() {
400 etag = Some(e);
401 }
402 if t.tombstone() {
403 flags |= nflags::TOMBSTONE;
404 }
405 }
406 }
407 NodeRef {
408 key: rec.url_key(),
409 id: Some(id),
410 url: base::label_at(labels, rec.url_off()).unwrap_or(""),
411 title: base::label_at(labels, rec.title_off()),
412 snippet: base::label_at(labels, rec.snippet_off()),
413 etag,
414 content_hash,
415 fetched_at_ms: fetched,
416 last_change_ms: last_change,
417 interval_s: interval,
418 checks,
419 changes,
420 flags,
421 rank_permille: self.rank_permille_of(id),
422 }
423 }
424
425 #[must_use]
427 pub fn len(&self) -> usize {
428 *self.live.get_or_init(|| {
429 let mut n = 0usize;
430 self.for_each_live(|_| n += 1);
431 n
432 })
433 }
434
435 #[must_use]
437 pub fn is_empty(&self) -> bool {
438 self.len() == 0
439 }
440
441 pub fn for_each_live<'a>(&'a self, mut f: impl FnMut(NodeRef<'a>)) {
444 let view = self.overlay();
445 for i in 0..self.base_len() {
446 let id = NodeId(i as u32);
447 let key = self.rec(id).url_key();
448 let patch = view.map.get(&key.0);
449 if patch.is_some_and(|p| p.removed) {
450 continue;
451 }
452 let nref = match patch.and_then(|p| p.core) {
453 Some(_) => match self.node(key) {
454 Some(n) => n,
455 None => continue,
456 },
457 None => self.base_node_ref(id, patch),
458 };
459 if nref.is_stub() || nref.is_tombstone() {
460 continue;
461 }
462 f(nref);
463 }
464 let mut fresh: Vec<u64> = view
466 .map
467 .iter()
468 .filter(|(k, p)| {
469 !p.removed && p.core.is_some() && self.base_node_id(UrlKey(**k)).is_none()
470 })
471 .map(|(k, _)| *k)
472 .collect();
473 fresh.sort_unstable();
474 for k in fresh {
475 if let Some(nref) = self.node(UrlKey(k)) {
476 if !nref.is_tombstone() {
477 f(nref);
478 }
479 }
480 }
481 }
482
483 #[must_use]
486 pub fn neighbors(&self, key: UrlKey) -> Neighbors<'_> {
487 if let Some(p) = self.overlay().map.get(&key.0) {
488 if p.removed {
489 return Neighbors { inner: NeighborsInner::Empty };
490 }
491 if let Some(edges) = self.edges_op(p.edges) {
492 return Neighbors {
493 inner: NeighborsInner::Overlay { snap: self, edges: edges.entries(), idx: 0 },
494 };
495 }
496 if p.severed {
497 return Neighbors { inner: NeighborsInner::Empty };
500 }
501 }
502 let Some(id) = self.base_node_id(key) else {
503 return Neighbors { inner: NeighborsInner::Empty };
504 };
505 let idx = self.edge_index();
506 if idx.is_empty() {
507 return Neighbors { inner: NeighborsInner::Empty };
508 }
509 let start = crate::bytesio::get_u32(idx, id.0 as usize * 4) as usize;
510 let end = crate::bytesio::get_u32(idx, (id.0 as usize + 1) * 4) as usize;
511 Neighbors {
512 inner: NeighborsInner::Base { snap: self, edges: self.edges_bytes(), idx: start, end },
513 }
514 }
515
516 pub fn in_edges(&self, key: UrlKey, mut f: impl FnMut(UrlKey, crate::EdgeType, u8, u16)) {
521 let overlay = &self.overlay().map;
522 if let Some(id) = self.base_node_id(key) {
523 let idx = self.section(base::SectionKind::InEdgeIndex);
524 if !idx.is_empty() {
525 let at = |i: usize| crate::bytesio::get_u32(idx, i * 4) as usize;
526 let recs = self.section(base::SectionKind::InEdges);
527 for i in at(id.0 as usize)..at(id.0 as usize + 1) {
528 let rec = &recs[i * base::EDGE_LEN..(i + 1) * base::EDGE_LEN];
529 let (src, _) = self.node_identity(NodeId(crate::bytesio::get_u32(rec, 0)));
530 let replaced = overlay
531 .get(&src.0)
532 .is_some_and(|p| p.edges.is_some() || p.removed || p.severed);
533 if !replaced {
534 f(
535 src,
536 crate::EdgeType::from_tag(rec[4]),
537 rec[5],
538 crate::bytesio::get_u16(rec, 6),
539 );
540 }
541 }
542 }
543 }
544 let inbound = self.inbound.get_or_init(|| {
545 let mut m: KeyMap<Vec<wal::Edge>> = KeyMap::default();
546 for (&src, p) in overlay {
547 if p.removed {
548 continue;
549 }
550 for (dst, etype, flags, weight) in
551 self.edges_op(p.edges).iter().flat_map(EdgesView::iter)
552 {
553 m.entry(dst.0).or_default().push((UrlKey(src), etype, flags, weight));
554 }
555 }
556 for v in m.values_mut() {
557 v.sort_unstable_by_key(|e| e.0);
558 }
559 m
560 });
561 for &(src, etype, flags, weight) in inbound.get(&key.0).into_iter().flatten() {
562 f(src, crate::EdgeType::from_tag(etype), flags, weight);
563 }
564 }
565
566 #[must_use]
568 pub fn attr(&self, key: UrlKey, k: u8) -> Option<&str> {
569 let mut out = None;
570 self.attrs(key, |ak, v| {
571 if ak == k {
572 out = Some(v);
573 }
574 });
575 out
576 }
577
578 pub fn attrs<'a>(&'a self, key: UrlKey, mut f: impl FnMut(u8, &'a str)) {
581 if let Some(p) = self.overlay().map.get(&key.0) {
582 if p.removed || (p.attrs.is_none() && p.severed) {
583 return;
584 }
585 if let Some(attrs) = self.attrs_op(p.attrs) {
586 for (k, v) in attrs.iter() {
587 f(k, v);
588 }
589 return;
590 }
591 }
592 let Some(id) = self.base_node_id(key) else { return };
593 let idx = self.section(base::SectionKind::AttrIndex);
594 if idx.is_empty() {
595 return;
596 }
597 let at = |i: usize| crate::bytesio::get_u32(idx, i * 4) as usize;
598 let (recs, blobs) =
599 (self.section(base::SectionKind::Attrs), self.section(base::SectionKind::Blobs));
600 for i in at(id.0 as usize)..at(id.0 as usize + 1) {
601 let rec = &recs[i * base::ATTR_LEN..(i + 1) * base::ATTR_LEN];
602 let (o, l) = (
603 crate::bytesio::get_u32(rec, 4) as usize,
604 crate::bytesio::get_u32(rec, 8) as usize,
605 );
606 f(rec[0], std::str::from_utf8(&blobs[o..o + l]).unwrap_or(""));
608 }
609 }
610
611 pub fn named_attrs<'a>(&'a self, key: UrlKey, mut f: impl FnMut(&'a str, &'a str)) {
616 self.attrs(key, |_, rec| {
617 if let Some((name, value)) = rec.split_once('\0') {
618 if !name.is_empty() {
619 f(name, value);
620 }
621 }
622 });
623 }
624
625 #[must_use]
627 pub fn segments(&self, key: UrlKey) -> Vec<SegRef<'_>> {
628 if let Some(p) = self.overlay().map.get(&key.0) {
629 if p.removed {
630 return Vec::new();
631 }
632 if p.segs.is_none() && p.severed {
633 return Vec::new();
634 }
635 if let Some(segs) = self.segs_op(p.segs) {
636 return segs
637 .iter()
638 .map(|s| SegRef {
639 key: SegKey::derive(key, s.label),
640 label: s.label,
641 byte_range: (s.flags & base::sflags::NO_RANGE == 0)
642 .then_some((s.byte_start, s.byte_len)),
643 depth: s.depth,
644 importance: s.importance,
645 flags: s.flags,
646 })
647 .collect();
648 }
649 }
650 let Some(id) = self.base_node_id(key) else { return Vec::new() };
651 let rec = self.rec(id);
652 let segs = self.segs_bytes();
653 let labels = self.labels();
654 let start = rec.seg_start() as usize;
655 let mut out = Vec::with_capacity(usize::from(rec.seg_count()));
656 for i in start..start + usize::from(rec.seg_count()) {
657 let s = SegRec(&segs[i * base::SEG_LEN..(i + 1) * base::SEG_LEN]);
658 out.push(SegRef {
659 key: SegKey(s.seg_key()),
660 label: base::label_at(labels, s.label_off()).unwrap_or(""),
661 byte_range: (s.flags() & base::sflags::NO_RANGE == 0)
662 .then(|| (s.byte_start(), s.byte_len())),
663 depth: s.depth(),
664 importance: s.importance(),
665 flags: s.flags(),
666 });
667 }
668 out
669 }
670
671 #[must_use]
674 pub(crate) fn hub_threshold(&self) -> u32 {
675 *self.hub_threshold.get_or_init(|| {
676 let n = self.base_len();
677 if n == 0 {
678 return u32::MAX;
679 }
680 let idx = self.edge_index();
681 let mut degrees: Vec<u32> = (0..n)
682 .map(|i| {
683 let out = if idx.is_empty() {
684 0
685 } else {
686 crate::bytesio::get_u32(idx, (i + 1) * 4)
687 - crate::bytesio::get_u32(idx, i * 4)
688 };
689 out + self.rec(NodeId(i as u32)).in_degree()
690 })
691 .collect();
692 degrees.sort_unstable();
693 let p99 = degrees[(n * 99 / 100).min(n - 1)];
694 p99.max(8)
695 })
696 }
697
698 #[must_use]
700 pub fn due(&self, now_ms: u64, max: usize) -> Vec<DueItem<'_>> {
701 let cfg = self.ttl();
702 let mut out: Vec<DueItem<'_>> = Vec::new();
703 self.for_each_live(|n| {
705 let next = cfg.next_check_at_ms(n.fetched_at_ms, n.interval_s.max(1), n.rank_permille);
706 if next <= now_ms && !n.url.is_empty() {
707 out.push(DueItem {
708 url: n.url,
709 key: n.key,
710 etag: n.etag,
711 overdue_ms: now_ms - next,
712 rank_permille: n.rank_permille,
713 });
714 }
715 });
716 let order = |a: &DueItem<'_>, b: &DueItem<'_>| {
717 b.rank_permille
718 .cmp(&a.rank_permille)
719 .then(b.overdue_ms.cmp(&a.overdue_ms))
720 .then(a.key.0.cmp(&b.key.0))
721 };
722 if max < out.len() {
724 out.select_nth_unstable_by(max, order);
725 out.truncate(max);
726 }
727 out.sort_by(order);
728 out
729 }
730
731 pub(crate) fn overlay_keys(&self) -> Vec<u64> {
733 self.overlay().map.keys().copied().collect()
734 }
735}