uqa_storage/diskann_index/pages/
memory.rs1use std::sync::Arc;
8
9use uqa_core::memory::{Budgeted, BudgetedMap, BudgetedVec, MemoryBudget};
10
11use super::{
12 copy, invalid, DiskANNArtifactSeal, DiskANNArtifactSealer, DiskANNPageSource,
13 DiskANNPageVisitor, DiskANNReadCapabilities, DiskANNRecordKey, DiskANNRecordVisitor,
14};
15use crate::diskann_index::format::{DiskANNGeneration, DiskANNManifest, PAGE_BYTES};
16use crate::{read_control::StorageReadControl, StorageBackendResult};
17
18struct Records {
19 records: BudgetedMap<DiskANNRecordKey, BudgetedVec<u8>>,
20 graph: BudgetedMap<u64, BudgetedVec<u8>>,
21}
22
23pub struct DiskANNMemoryBuilder {
24 generation: DiskANNGeneration,
25 records: Records,
26 budget: MemoryBudget,
27}
28
29#[derive(Clone)]
30pub struct DiskANNMemorySource {
31 seal: DiskANNArtifactSeal,
32 records: Arc<Budgeted<Records>>,
33}
34
35impl DiskANNMemoryBuilder {
36 pub fn generation(&self) -> DiskANNGeneration {
37 self.generation
38 }
39
40 pub fn new(generation: DiskANNGeneration, budget: &MemoryBudget) -> Self {
41 Self {
42 generation,
43 records: Records {
44 records: BudgetedMap::new(budget),
45 graph: BudgetedMap::new(budget),
46 },
47 budget: budget.clone(),
48 }
49 }
50
51 pub fn write_record(
52 &mut self,
53 key: DiskANNRecordKey,
54 bytes: &[u8],
55 control: &StorageReadControl,
56 ) -> StorageBackendResult<()> {
57 control.check()?;
58 if key == DiskANNRecordKey::Manifest || self.records.records.contains_key(&key) {
59 return Err(invalid(
60 "manifest is written at sealing; staging records cannot be replaced",
61 ));
62 }
63 let owner = StorageReadControl::new(&self.budget, control.cancellation());
64 let bytes = copy(bytes, usize::MAX, &owner)?;
65 self.records.records.insert(key, bytes)?;
66 Ok(())
67 }
68
69 pub fn write_graph_page(
70 &mut self,
71 id: u64,
72 bytes: &[u8],
73 control: &StorageReadControl,
74 ) -> StorageBackendResult<()> {
75 control.check()?;
76 if bytes.len() != PAGE_BYTES || self.records.graph.contains_key(&id) {
77 return Err(invalid("staged page length or identity differs"));
78 }
79 let owner = StorageReadControl::new(&self.budget, control.cancellation());
80 let bytes = copy(bytes, PAGE_BYTES, &owner)?;
81 self.records.graph.insert(id, bytes)?;
82 Ok(())
83 }
84
85 pub fn finish(
86 mut self,
87 manifest: DiskANNManifest,
88 control: &StorageReadControl,
89 ) -> StorageBackendResult<DiskANNMemorySource> {
90 control.check()?;
91 if manifest.input().generation != self.generation {
92 return Err(invalid("manifest does not belong to staging owner"));
93 }
94 let mut sealer = DiskANNArtifactSealer::new(manifest, control)?;
95 for (key, bytes) in &self.records.records {
96 match key {
97 DiskANNRecordKey::Codebook => sealer.codebook(bytes)?,
98 DiskANNRecordKey::Codes(first) => sealer.code_batch(*first, bytes)?,
99 DiskANNRecordKey::Side(first) => sealer.side_batch(*first, bytes)?,
100 DiskANNRecordKey::Origins(first) => sealer.origin_batch(*first, bytes)?,
101 DiskANNRecordKey::Manifest => {
102 return Err(invalid("staged manifest before sealing"))
103 }
104 }
105 }
106 for (id, bytes) in &self.records.graph {
107 sealer.graph_page(*id, bytes)?;
108 }
109 let seal = sealer.finish()?;
110 let owner = StorageReadControl::new(&self.budget, control.cancellation());
111 self.records
112 .records
113 .insert(DiskANNRecordKey::Manifest, manifest.encode(&owner)?)?;
114 let records = Budgeted::new(self.records, self.budget.empty_reservation()).into_shared()?;
115 control.check()?;
116 Ok(DiskANNMemorySource { seal, records })
117 }
118}
119
120impl DiskANNPageSource for DiskANNMemorySource {
121 fn generation(&self) -> DiskANNGeneration {
122 self.seal.manifest().input().generation
123 }
124 fn capabilities(&self) -> DiskANNReadCapabilities {
125 DiskANNReadCapabilities {
126 max_batch_pages: 32,
127 read_concurrency: 1,
128 }
129 }
130 fn read_record(
131 &self,
132 key: DiskANNRecordKey,
133 max_bytes: usize,
134 control: &StorageReadControl,
135 visit: &mut DiskANNRecordVisitor<'_>,
136 ) -> StorageBackendResult<()> {
137 control.check()?;
138 let bytes = self
139 .records
140 .records
141 .get(&key)
142 .ok_or_else(|| invalid("missing memory record"))?;
143 if bytes.len() > max_bytes {
144 return Err(uqa_core::memory::MemoryError::Limit {
145 required: bytes.len(),
146 limit: max_bytes,
147 }
148 .into());
149 }
150 visit(bytes)?;
151 control.check()
152 }
153 fn read_graph_pages(
154 &self,
155 pages: &[u64],
156 control: &StorageReadControl,
157 visit: &mut DiskANNPageVisitor<'_>,
158 ) -> StorageBackendResult<()> {
159 if pages.len() > self.capabilities().max_batch_pages() {
160 return Err(invalid("page request exceeds source batch limit"));
161 }
162 for &id in pages {
163 control.check()?;
164 let bytes = self
165 .records
166 .graph
167 .get(&id)
168 .ok_or_else(|| invalid("missing memory page"))?;
169 visit(id, bytes)?;
170 }
171 control.check()
172 }
173}