···4242 .map_err(|_| StatusCode::BAD_REQUEST)?;
43434444 let db = &state.db;
4545- let ks = db.records.clone();
4545+ let ks = db
4646+ .record_partition(&req.collection)
4747+ .map_err(|_| StatusCode::NOT_FOUND)?;
46484747- // {did_prefix}\x00{collection}\x00
4949+ // {TrimmedDid}\x00
4850 let mut prefix = Vec::new();
4951 TrimmedDid::from(&did).write_to_vec(&mut prefix);
5050- prefix.push(keys::SEP);
5151- prefix.extend_from_slice(req.collection.as_bytes());
5252 prefix.push(keys::SEP);
53535454 let count = tokio::task::spawn_blocking(move || {
···185185 let items = tokio::task::spawn_blocking(move || {
186186 let limit = req.limit.unwrap_or(50).min(1000);
187187188188- // Helper closure to avoid generic type complexity
189188 let collect = |iter: &mut dyn Iterator<Item = fjall::Guard>| {
190189 let mut items = Vec::new();
191190 for guard in iter.take(limit) {
···247246fn get_keyspace_by_name(db: &crate::db::Db, name: &str) -> Result<fjall::Keyspace, StatusCode> {
248247 match name {
249248 "repos" => Ok(db.repos.clone()),
250250- "records" => Ok(db.records.clone()),
251249 "blocks" => Ok(db.blocks.clone()),
252250 "cursors" => Ok(db.cursors.clone()),
253251 "pending" => Ok(db.pending.clone()),
254252 "resync" => Ok(db.resync.clone()),
255253 "events" => Ok(db.events.clone()),
256254 "counts" => Ok(db.counts.clone()),
257257- _ => Err(StatusCode::BAD_REQUEST),
255255+ _ => {
256256+ if let Some(col) = name.strip_prefix(crate::db::RECORDS_PARTITION_PREFIX) {
257257+ db.record_partition(col).map_err(|_| StatusCode::NOT_FOUND)
258258+ } else {
259259+ Err(StatusCode::BAD_REQUEST)
260260+ }
261261+ }
258262 }
259263}
+2-5
src/api/stream.rs
···97979898 let marshallable = {
9999 let mut record_val = None;
100100- if let Some(cid_struct) = &cid {
101101- let cid_str = cid_struct.to_string();
102102- if let Ok(Some(block_bytes)) =
103103- db.blocks.get(keys::block_key(&cid_str))
104104- {
100100+ if let Some(cid) = &cid {
101101+ if let Ok(Some(block_bytes)) = db.blocks.get(&cid.to_bytes()) {
105102 if let Ok(raw_data) =
106103 serde_ipld_dagcbor::from_slice::<RawData>(&block_bytes)
107104 {
+73-97
src/api/xrpc.rs
···33use crate::db::{self, Db, keys};
44use axum::{Json, Router, extract::State, http::StatusCode};
55use futures::TryFutureExt;
66+use jacquard::cowstr::ToCowStr;
67use jacquard::types::ident::AtIdentifier;
78use jacquard::{
89 IntoStatic,
···2122 },
2223 xrpc::{GenericXrpcError, XrpcError},
2324};
2525+use miette::IntoDiagnostic;
2426use serde::{Deserialize, Serialize};
2527use smol_str::ToSmolStr;
2628use std::{fmt::Display, sync::Arc};
···7678 .await
7779 .map_err(|e| bad_request(GetRecord::NSID, e))?;
78807979- let db_key = keys::record_key(&did, req.collection.as_str(), req.rkey.0.as_str());
8181+ let partition = db
8282+ .record_partition(req.collection.as_str())
8383+ .map_err(|e| internal_error(GetRecord::NSID, e))?;
80848181- let cid_bytes = Db::get(db.records.clone(), db_key)
8585+ let db_key = keys::record_key(&did, req.rkey.0.as_str()); // 2 args
8686+8787+ let cid_bytes = Db::get(partition, db_key)
8288 .await
8389 .map_err(|e| internal_error(GetRecord::NSID, e))?;
84908591 if let Some(cid_bytes) = cid_bytes {
8686- let cid_str =
8787- std::str::from_utf8(&cid_bytes).map_err(|e| internal_error(GetRecord::NSID, e))?;
8888-8989- let block_bytes = Db::get(db.blocks.clone(), keys::block_key(cid_str))
9292+ // lookup block using binary cid
9393+ let block_bytes = Db::get(db.blocks.clone(), &cid_bytes)
9094 .await
9195 .map_err(|e| internal_error(GetRecord::NSID, e))?
9296 .ok_or_else(|| internal_error(GetRecord::NSID, "not found"))?;
···9498 let value: Data = serde_ipld_dagcbor::from_slice(&block_bytes)
9599 .map_err(|e| internal_error(GetRecord::NSID, e))?;
961009797- let cid = Cid::new(cid_str.as_bytes()).unwrap().into_static();
101101+ let cid = Cid::new(&cid_bytes)
102102+ .map_err(|e| internal_error(GetRecord::NSID, e))?
103103+ .into_static();
9810499105 Ok(Json(GetRecordOutput {
100106 uri: AtUri::from_parts_owned(
···103109 req.rkey.0.as_str(),
104110 )
105111 .unwrap(),
106106- cid: Some(cid),
112112+ cid: Some(Cid::Str(cid.to_cowstr()).into_static()),
107113 value: value.into_static(),
108114 extra_data: Default::default(),
109115 }))
···126132 .await
127133 .map_err(|e| bad_request(ListRecords::NSID, e))?;
128134129129- let prefix = format!(
130130- "{}{}{}{}",
131131- TrimmedDid::from(&did),
132132- keys::SEP as char,
133133- req.collection.as_str(),
134134- keys::SEP as char
135135- );
135135+ let ks = db
136136+ .record_partition(req.collection.as_str())
137137+ .map_err(|e| internal_error(ListRecords::NSID, e))?;
138138+139139+ let mut prefix = Vec::new();
140140+ TrimmedDid::from(&did).write_to_vec(&mut prefix);
141141+ prefix.push(keys::SEP);
136142137143 let limit = req.limit.unwrap_or(50).min(100) as usize;
138144 let reverse = req.reverse.unwrap_or(false);
139139- let ks = db.records.clone();
140145 let blocks_ks = db.blocks.clone();
141146142147 let did_str = smol_str::SmolStr::from(did.as_str());
···146151 let mut results = Vec::new();
147152 let mut cursor = None;
148153149149- let mut end_prefix = prefix.clone().into_bytes();
150150- if let Some(last) = end_prefix.last_mut() {
151151- *last += 1;
152152- }
154154+ let iter: Box<dyn Iterator<Item = _>> = if !reverse {
155155+ let mut end_prefix = prefix.clone();
156156+ if let Some(last) = end_prefix.last_mut() {
157157+ *last += 1;
158158+ }
153159154154- if !reverse {
155160 let end_key = if let Some(cursor) = &req.cursor {
156156- format!("{}{}", prefix, cursor).into_bytes()
161161+ let mut k = prefix.clone();
162162+ k.extend_from_slice(cursor.as_bytes());
163163+ k
157164 } else {
158158- end_prefix.clone()
165165+ end_prefix
159166 };
160167161161- for item in ks.range(prefix.as_bytes()..end_key.as_slice()).rev() {
162162- let (key, cid_bytes) = item.into_inner().ok()?;
163163-164164- if !key.starts_with(prefix.as_bytes()) {
165165- break;
166166- }
167167- if results.len() >= limit {
168168- let key_str = String::from_utf8_lossy(&key);
169169- if let Some(last_part) = key_str.split(keys::SEP as char).last() {
170170- cursor = Some(smol_str::SmolStr::from(last_part));
171171- }
172172- break;
173173- }
174174-175175- let key_str = String::from_utf8_lossy(&key);
176176- let parts: Vec<&str> = key_str.split(keys::SEP as char).collect();
177177- if parts.len() == 3 {
178178- let rkey = parts[2];
179179- let cid_str = std::str::from_utf8(&cid_bytes).ok()?;
180180-181181- if let Ok(Some(block_bytes)) = blocks_ks.get(keys::block_key(cid_str)) {
182182- let val: Data =
183183- serde_ipld_dagcbor::from_slice(&block_bytes).unwrap_or(Data::Null);
184184- let cid = Cid::new(cid_str.as_bytes()).unwrap().into_static();
185185- results.push(RepoRecord {
186186- uri: AtUri::from_parts_owned(
187187- did_str.as_str(),
188188- collection_str.as_str(),
189189- rkey,
190190- )
191191- .unwrap(),
192192- cid,
193193- value: val.into_static(),
194194- extra_data: Default::default(),
195195- });
196196- }
197197- }
198198- }
168168+ Box::new(ks.range(prefix.as_slice()..end_key.as_slice()).rev())
199169 } else {
200170 let start_key = if let Some(cursor) = &req.cursor {
201201- format!("{}{}\0", prefix, cursor).into_bytes()
171171+ let mut k = prefix.clone();
172172+ k.extend_from_slice(cursor.as_bytes());
173173+ k.push(0);
174174+ k
202175 } else {
203203- prefix.clone().into_bytes()
176176+ prefix.clone()
204177 };
205178206206- for item in ks.range(start_key.as_slice()..) {
207207- let (key, cid_bytes) = item.into_inner().ok()?;
179179+ Box::new(ks.range(start_key.as_slice()..))
180180+ };
208181209209- if !key.starts_with(prefix.as_bytes()) {
210210- break;
211211- }
212212- if results.len() >= limit {
213213- let key_str = String::from_utf8_lossy(&key);
214214- if let Some(last_part) = key_str.split(keys::SEP as char).last() {
215215- cursor = Some(smol_str::SmolStr::from(last_part));
216216- }
217217- break;
218218- }
182182+ for item in iter {
183183+ let (key, cid_bytes) = item.into_inner().into_diagnostic()?;
219184185185+ if !key.starts_with(prefix.as_slice()) {
186186+ break;
187187+ }
188188+ if results.len() >= limit {
220189 let key_str = String::from_utf8_lossy(&key);
221221- let parts: Vec<&str> = key_str.split(keys::SEP as char).collect();
222222- if parts.len() == 3 {
223223- let rkey = parts[2];
224224- let cid_str = std::str::from_utf8(&cid_bytes).ok()?;
190190+ if let Some(last_part) = key_str.split(keys::SEP as char).last() {
191191+ cursor = Some(smol_str::SmolStr::from(last_part));
192192+ }
193193+ break;
194194+ }
225195226226- if let Ok(Some(block_bytes)) = blocks_ks.get(keys::block_key(cid_str)) {
227227- let val: Data =
228228- serde_ipld_dagcbor::from_slice(&block_bytes).unwrap_or(Data::Null);
229229- let cid = Cid::new(cid_str.as_bytes()).unwrap().into_static();
230230- results.push(RepoRecord {
231231- uri: AtUri::from_parts_owned(
232232- did_str.as_str(),
233233- collection_str.as_str(),
234234- rkey,
235235- )
236236- .unwrap(),
237237- cid,
238238- value: val.into_static(),
239239- extra_data: Default::default(),
240240- });
241241- }
196196+ // key: {TrimmedDid}|{RKey}
197197+ let key_str = String::from_utf8_lossy(&key);
198198+ let parts: Vec<&str> = key_str.split(keys::SEP as char).collect();
199199+ if parts.len() == 2 {
200200+ let rkey = parts[1];
201201+ // look up using binary cid bytes from the record
202202+ if let Ok(Some(block_bytes)) = blocks_ks.get(&cid_bytes) {
203203+ let val: Data =
204204+ serde_ipld_dagcbor::from_slice(&block_bytes).unwrap_or(Data::Null);
205205+ let cid =
206206+ Cid::Str(Cid::new(&cid_bytes).into_diagnostic()?.to_cowstr()).into_static();
207207+ results.push(RepoRecord {
208208+ uri: AtUri::from_parts_owned(
209209+ did_str.as_str(),
210210+ collection_str.as_str(),
211211+ rkey,
212212+ )
213213+ .into_diagnostic()?,
214214+ cid,
215215+ value: val.into_static(),
216216+ extra_data: Default::default(),
217217+ });
242218 }
243219 }
244220 }
245245- Some((results, cursor))
221221+ Result::<_, miette::Report>::Ok((results, cursor))
246222 })
247223 .await
248224 .map_err(|e| internal_error(ListRecords::NSID, e))?
249249- .ok_or_else(|| internal_error(ListRecords::NSID, "not found"))?;
225225+ .map_err(|e| internal_error(ListRecords::NSID, e))?;
250226251227 Ok(Json(ListRecordsOutput {
252228 records: results,
+36-32
src/backfill/mod.rs
···1111use jacquard_common::xrpc::XrpcError;
1212use jacquard_repo::mst::Mst;
1313use jacquard_repo::{BlockStore, MemoryBlockStore};
1414-use miette::{Context, IntoDiagnostic, Result};
1414+use miette::{IntoDiagnostic, Result};
1515use smol_str::{SmolStr, ToSmolStr};
1616use std::collections::HashMap;
1717use std::sync::Arc;
···369369 let mut batch = app_state.db.inner.batch();
370370 let store = mst.storage();
371371372372- // pre-load existing record CIDs for this DID to detect duplicates/updates
373372 let prefix = keys::record_prefix(&did);
374374- let prefix_len = prefix.len();
375373 let mut existing_cids: HashMap<(SmolStr, SmolStr), SmolStr> = HashMap::new();
376376- for guard in app_state.db.records.prefix(&prefix) {
377377- let (key, cid_bytes) = guard.into_inner().into_diagnostic()?;
378378- // extract path (collection/rkey) from key by skipping the DID prefix
379379- let mut path_split = key[prefix_len..].split(|b| *b == keys::SEP);
380380- let collection =
381381- std::str::from_utf8(path_split.next().wrap_err("collection not found")?)
382382- .into_diagnostic()?
383383- .to_smolstr();
384384- let rkey = std::str::from_utf8(path_split.next().wrap_err("rkey not found")?)
385385- .into_diagnostic()?
386386- .to_smolstr();
387387- let cid = std::str::from_utf8(&cid_bytes)
388388- .into_diagnostic()?
389389- .to_smolstr();
390390- existing_cids.insert((collection, rkey), cid);
374374+375375+ let mut partitions = Vec::new();
376376+ app_state.db.record_partitions.iter_sync(|col, ks| {
377377+ partitions.push((col.clone(), ks.clone()));
378378+ true
379379+ });
380380+381381+ for (col_name, ks) in partitions {
382382+ for guard in ks.prefix(&prefix) {
383383+ let (key, cid_bytes) = guard.into_inner().into_diagnostic()?;
384384+ // key: {DID}|{RKey}
385385+ let key_str = std::str::from_utf8(&key).into_diagnostic()?;
386386+ let parts: Vec<&str> = key_str.split(keys::SEP as char).collect();
387387+ if parts.len() == 2 {
388388+ let rkey = parts[1].to_smolstr();
389389+ let cid = if let Ok(c) = cid::Cid::read_bytes(cid_bytes.as_ref()) {
390390+ c.to_string().to_smolstr()
391391+ } else {
392392+ continue;
393393+ };
394394+395395+ existing_cids.insert((col_name.as_str().into(), rkey), cid);
396396+ }
397397+ }
391398 }
392399393400 for (key, cid) in leaves {
···398405 if let Some(val) = val_bytes {
399406 let (collection, rkey) = ops::parse_path(&key)?;
400407 let path = (collection.to_smolstr(), rkey.to_smolstr());
401401- let cid = Cid::ipld(cid);
408408+ let cid_obj = Cid::ipld(cid);
409409+ let partition = app_state.db.record_partition(collection)?;
402410403411 // check if this record already exists with same CID
404412 let (action, is_new) =
405413 if let Some(existing_cid) = existing_cids.remove(&path) {
406406- if existing_cid == cid.as_str() {
414414+ if existing_cid == cid_obj.as_str() {
407415 debug!("skip {did}/{collection}/{rkey} ({cid})");
408416 continue; // skip unchanged record
409417 }
···413421 };
414422 debug!("{action} {did}/{collection}/{rkey} ({cid})");
415423416416- let db_key = keys::record_key(&did, &collection, &rkey);
424424+ // Key is just did|rkey
425425+ let db_key = keys::record_key(&did, &rkey);
417426418418- batch.insert(
419419- &app_state.db.blocks,
420420- keys::block_key(cid.as_str()),
421421- val.as_ref(),
422422- );
423423- batch.insert(&app_state.db.records, db_key, cid.as_str().as_bytes());
427427+ batch.insert(&app_state.db.blocks, cid.to_bytes(), val.as_ref());
428428+ batch.insert(&partition, db_key, cid.to_bytes());
424429425430 added_blocks += 1;
426431 if is_new {
···436441 collection: CowStr::Borrowed(collection),
437442 rkey: DbRkey::new(rkey),
438443 action: DbAction::from(action),
439439- cid: Some(cid.to_ipld().expect("valid cid")),
444444+ cid: Some(cid_obj.to_ipld().expect("valid cid")),
440445 };
441446 let bytes = rmp_serde::to_vec(&evt).into_diagnostic()?;
442447 batch.insert(&app_state.db.events, keys::event_key(event_id), bytes);
···448453 // remove any remaining existing records (they weren't in the new MST)
449454 for ((collection, rkey), cid) in existing_cids {
450455 debug!("remove {did}/{collection}/{rkey} ({cid})");
451451- batch.remove(
452452- &app_state.db.records,
453453- keys::record_key(&did, &collection, &rkey),
454454- );
456456+ let partition = app_state.db.record_partition(collection.as_str())?;
457457+458458+ batch.remove(&partition, keys::record_key(&did, &rkey));
455459456460 let event_id = app_state.db.next_event_id.fetch_add(1, Ordering::SeqCst);
457461 let evt = StoredEvent {
+4-11
src/db/keys.rs
···1414 vec
1515}
16161717-// key format: {DID}\x00{Collection}\x00{RKey} (DID trimmed)
1818-pub fn record_key(did: &Did, collection: &str, rkey: &str) -> Vec<u8> {
1717+// key format: {DID}\x00{RKey} (DID trimmed)
1818+pub fn record_key(did: &Did, rkey: &str) -> Vec<u8> {
1919 let repo = TrimmedDid::from(did);
2020- let mut key = Vec::with_capacity(repo.len() + collection.len() + rkey.len() + 2);
2020+ let mut key = Vec::with_capacity(repo.len() + rkey.len() + 1);
2121 repo.write_to_vec(&mut key);
2222- key.push(SEP);
2323- key.extend_from_slice(collection.as_bytes());
2422 key.push(SEP);
2523 key.extend_from_slice(rkey.as_bytes());
2624 key
2725}
28262929-// prefix format: {DID}\x00 (DID trimmed) - for scanning all records of a DID
2727+// prefix format: {DID}\x00 (DID trimmed) - for scanning all records of a DID within a collection
3028pub fn record_prefix(did: &Did) -> Vec<u8> {
3129 let repo = TrimmedDid::from(did);
3230 let mut prefix = Vec::with_capacity(repo.len() + 1);
···4038// key format: {SEQ}
4139pub fn event_key(seq: u64) -> [u8; 8] {
4240 seq.to_be_bytes()
4343-}
4444-4545-// key format: {CID}
4646-pub fn block_key(cid: &str) -> &[u8] {
4747- cid.as_bytes()
4841}
49425043pub const COUNT_KS_PREFIX: &[u8] = &[b'k', SEP];
+42-5
src/db/mod.rs
···44use jacquard_common::types::string::Did;
55use miette::{Context, IntoDiagnostic, Result};
66use scc::HashMap;
77-use smol_str::SmolStr;
77+use smol_str::{SmolStr, format_smolstr};
88use std::path::Path;
99use std::sync::Arc;
1010···1414use std::sync::atomic::AtomicU64;
1515use tokio::sync::broadcast;
1616use tracing::error;
1717+1818+pub const RECORDS_PARTITION_PREFIX: &str = "r:";
1919+2020+fn default_opts() -> KeyspaceCreateOptions {
2121+ KeyspaceCreateOptions::default()
2222+}
2323+2424+fn record_partition_opts() -> KeyspaceCreateOptions {
2525+ default_opts().max_memtable_size(32 * 2_u64.pow(20))
2626+}
17271828pub struct Db {
1929 pub inner: Arc<Database>,
2030 pub repos: Keyspace,
2121- pub records: Keyspace,
3131+ pub record_partitions: HashMap<String, Keyspace>,
2232 pub blocks: Keyspace,
2333 pub cursors: Keyspace,
2434 pub pending: Keyspace,
···4858 .into_diagnostic()?;
4959 let db = Arc::new(db);
50605151- let opts = KeyspaceCreateOptions::default;
6161+ let opts = default_opts;
5262 let open_ks = |name: &str, opts: KeyspaceCreateOptions| {
5363 db.keyspace(name, move || opts).into_diagnostic()
5464 };
55655666 let repos = open_ks("repos", opts().expect_point_read_hits(true))?;
5757- let records = open_ks("records", opts().max_memtable_size(32 * 2_u64.pow(20)))?;
5867 let blocks = open_ks(
5968 "blocks",
6069 opts()
···6877 let events = open_ks("events", opts())?;
6978 let counts = open_ks("counts", opts().expect_point_read_hits(true))?;
70798080+ let record_partitions = HashMap::new();
8181+ {
8282+ let names = db.list_keyspace_names();
8383+ for name in names {
8484+ let name_str: &str = name.as_ref();
8585+ if let Some(collection) = name_str.strip_prefix(RECORDS_PARTITION_PREFIX) {
8686+ let popts = record_partition_opts();
8787+ let ks = db.keyspace(name_str, move || popts).into_diagnostic()?;
8888+ let _ = record_partitions.insert_sync(collection.to_string(), ks);
8989+ }
9090+ }
9191+ }
9292+7193 let mut last_id = 0;
7294 if let Some(guard) = events.iter().next_back() {
7395 let k = guard.key().into_diagnostic()?;
···97119 Ok(Self {
98120 inner: db,
99121 repos,
100100- records,
122122+ record_partitions,
101123 blocks,
102124 cursors,
103125 pending,
···108130 counts_map,
109131 next_event_id: Arc::new(AtomicU64::new(last_id + 1)),
110132 })
133133+ }
134134+135135+ pub fn record_partition(&self, collection: &str) -> Result<Keyspace> {
136136+ use scc::hash_map::Entry;
137137+ match self.record_partitions.entry_sync(collection.to_string()) {
138138+ Entry::Occupied(o) => Ok(o.get().clone()),
139139+ Entry::Vacant(v) => {
140140+ let name = format_smolstr!("{}{}", RECORDS_PARTITION_PREFIX, collection);
141141+ let ks = self
142142+ .inner
143143+ .keyspace(&name, record_partition_opts)
144144+ .into_diagnostic()?;
145145+ Ok(v.insert_entry(ks).get().clone())
146146+ }
147147+ }
111148 }
112149113150 pub fn persist(&self) -> Result<()> {
+27-17
src/ops.rs
···88use fjall::OwnedWriteBatch;
99use jacquard::CowStr;
1010use jacquard::IntoStatic;
1111-use jacquard::cowstr::ToCowStr;
1111+1212use jacquard::types::cid::Cid;
1313use jacquard_api::com_atproto::sync::subscribe_repos::Commit;
1414use jacquard_common::types::crypto::PublicKey;
···6767 batch.remove(&db.pending, &repo_key);
6868 batch.remove(&db.resync, &repo_key);
69697070- // 2. delete from records (prefix: repo_key + SEP)
7171- let mut records_prefix = repo_key.clone();
7272- records_prefix.push(keys::SEP);
7373- for guard in db.records.prefix(&records_prefix) {
7474- let k = guard.key().into_diagnostic()?;
7575- batch.remove(&db.records, k);
7070+ // 2. delete from records (all partitions)
7171+ let mut partitions = Vec::new();
7272+ db.record_partitions.iter_sync(|_, v| {
7373+ partitions.push(v.clone());
7474+ true
7575+ });
7676+7777+ let records_prefix = keys::record_prefix(did);
7878+ for ks in partitions {
7979+ for guard in ks.prefix(&records_prefix) {
8080+ let k = guard.key().into_diagnostic()?;
8181+ batch.remove(&ks, k);
8282+ }
7683 }
77847885 // 3. reset collection counts
···218225219226 // store all blocks in the CAS
220227 for (cid, bytes) in &parsed.blocks {
221221- batch.insert(
222222- &db.blocks,
223223- keys::block_key(&cid.to_cowstr()),
224224- bytes.to_vec(),
225225- );
228228+ batch.insert(&db.blocks, cid.to_bytes(), bytes.to_vec());
226229 }
227230228231 // 2. iterate ops and update records index
···231234232235 for op in &commit.ops {
233236 let (collection, rkey) = parse_path(&op.path)?;
234234- let db_key = keys::record_key(did, collection, rkey);
237237+ let partition = db.record_partition(collection)?;
238238+ let db_key = keys::record_key(did, rkey); // removed collection arg
235239236240 let event_id = db.next_event_id.fetch_add(1, Ordering::SeqCst);
237241···240244 let Some(cid) = &op.cid else {
241245 continue;
242246 };
243243- let s = smol_str::SmolStr::from(cid.as_str());
244244- batch.insert(&db.records, db_key, s.as_bytes().to_vec());
247247+ batch.insert(
248248+ &partition,
249249+ db_key.clone(),
250250+ cid.to_ipld()
251251+ .into_diagnostic()
252252+ .wrap_err("expected valid cid from relay")?
253253+ .to_bytes(),
254254+ );
245255246256 // accumulate counts
247257 if op.action.as_str() == "create" {
···250260 }
251261 }
252262 "delete" => {
253253- batch.remove(&db.records, db_key);
263263+ batch.remove(&partition, db_key);
254264255265 // accumulate counts
256266 records_delta -= 1;
···268278 collection: CowStr::Borrowed(collection),
269279 rkey: DbRkey::new(rkey),
270280 action: DbAction::from(op.action.as_str()),
271271- cid: op.cid.as_ref().map(|c| c.0.to_ipld().expect("valid cid")),
281281+ cid: op.cid.as_ref().map(|c| c.to_ipld().expect("valid cid")),
272282 };
273283274284 let bytes = rmp_serde::to_vec(&evt).into_diagnostic()?;