···219219| `PLC_URL` | `https://plc.wtf`, `https://plc.directory` if full network | base URL(s) of the PLC directory (comma-separated for multiple). |
220220| `EPHEMERAL` | `false` | if enabled, no records are stored. events are deleted after a certain duration (`EPHEMERAL_TTL`). |
221221| `EPHEMERAL_TTL` | `60min`, `3d` in relay mode | decides after how long events should be deleted. |
222222+| `ONLY_INDEX_LINKS` | `false` | indexer only. if enabled, record blocks are not stored, only the index (records, counts, events) is kept. `getRecord`, `listRecords`, and `getRepo` will return errors. the event stream still works but create/update events will not include record values. |
222223| `FULL_NETWORK` | `false` (indexer), `true` (relay) | if `true`, discovers and indexes all repositories in the network. |
223224| `FILTER_SIGNALS` | | comma-separated list of NSID patterns to use for the filter (e.g. `app.bsky.feed.post,app.bsky.graph.*`). |
224225| `FILTER_COLLECTIONS` | | comma-separated list of NSID patterns to use for the collections filter. |
+11-2
src/backfill/mod.rs
···567567568568 tokio::task::spawn_blocking(move || {
569569 let filter = app_state.filter.load();
570570+ let only_index_links = app_state.only_index_links;
570571 let mut count = 0;
571572 let mut delta = 0;
572573 let mut added_blocks = 0;
···646647 let cid_raw = cid.to_bytes();
647648 let block_key = Slice::from(keys::block_key(collection, &cid_raw));
648649 if !ephemeral {
649649- batch.insert(&app_state.db.blocks, block_key.clone(), val.as_ref());
650650+ if !only_index_links {
651651+ batch.insert(&app_state.db.blocks, block_key.clone(), val.as_ref());
652652+ }
650653 batch.insert(&app_state.db.records, db_key, cid_raw);
651654 #[cfg(feature = "backlinks")]
652655 if let Ok(value) = serde_ipld_dagcbor::from_slice::<jacquard_common::Data>(val.as_ref()) {
···675678 collection: CowStr::Borrowed(collection),
676679 rkey,
677680 action,
678678- data: ephemeral.then_some(StoredData::Block(val)).unwrap_or_else(|| StoredData::Ptr(cid_obj.to_ipld().expect("valid cid"))),
681681+ data: if ephemeral {
682682+ StoredData::Block(val)
683683+ } else if only_index_links {
684684+ StoredData::Nothing
685685+ } else {
686686+ StoredData::Ptr(cid_obj.to_ipld().expect("valid cid"))
687687+ },
679688 };
680689 let bytes = rmp_serde::to_vec(&evt).into_diagnostic()?;
681690 batch.insert(&app_state.db.events, keys::event_key(event_id), bytes);
+13
src/config.rs
···368368 /// set via `HYDRANT_ENABLE_BACKLINKS=true`.
369369 pub enable_backlinks: bool,
370370371371+ /// if `true`, record blocks are not stored; only the index (records, counts, events) is kept.
372372+ /// `getRecord`, `listRecords`, and `getRepo` will return errors when this is enabled.
373373+ /// event stream still functions but create/update events will not include record values.
374374+ /// only valid in indexer mode (not relay).
375375+ /// set via `HYDRANT_ONLY_INDEX_LINKS=true`.
376376+ pub only_index_links: bool,
377377+371378 /// base URL(s) of relay or aggregator services to seed firehose PDS sources from at startup.
372379 ///
373380 /// hydrant calls `com.atproto.sync.listHosts` on each URL and adds the returned PDSes
···484491 filter_collections: None,
485492 filter_excludes: None,
486493 enable_backlinks: false,
494494+ only_index_links: false,
487495 tier_rules: vec![],
488496 tier_policy: {
489497 let mut tiers = HashMap::new();
···659667 });
660668661669 let enable_backlinks: bool = cfg!("ENABLE_BACKLINKS", defaults.enable_backlinks);
670670+ let only_index_links: bool = cfg!("ONLY_INDEX_LINKS", defaults.only_index_links);
662671663672 // start with built-in tier definitions, then layer in any env-defined overrides.
664673 // format: HYDRANT_RATE_TIERS=name:base/mul/hourly/daily,...
···772781 filter_collections,
773782 filter_excludes,
774783 enable_backlinks,
784784+ only_index_links,
775785 tier_policy,
776786 tier_rules,
777787 cache_size,
···885895 }
886896 if self.enable_backlinks {
887897 config_line!(f, "backlinks", "enabled")?;
898898+ }
899899+ if self.only_index_links {
900900+ config_line!(f, "only index links", "true")?;
888901 }
889902 if !self.seed_hosts.is_empty() {
890903 config_line!(
+5
src/control/mod.rs
···128128 pub async fn new(config: Config) -> Result<Self> {
129129 info!("{config}");
130130131131+ #[cfg(feature = "relay")]
132132+ if config.only_index_links {
133133+ miette::bail!("HYDRANT_ONLY_INDEX_LINKS is not supported in relay mode");
134134+ }
135135+131136 // 1. open database and construct AppState
132137 let state = AppState::new(&config)?;
133138
+10
src/control/repos/indexer.rs
···334334impl<'i> RepoHandle<'i> {
335335 /// gets a record from this repository.
336336 pub async fn get_record(&self, collection: &str, rkey: &str) -> Result<Option<Record>> {
337337+ if self.state.only_index_links {
338338+ miette::bail!("block storage is disabled (HYDRANT_ONLY_INDEX_LINKS)");
339339+ }
340340+337341 let did = self.did.clone().into_static();
338342 let db_key = keys::record_key(&did, collection, &DbRkey::new(rkey));
339343···376380 reverse: bool,
377381 cursor: Option<&str>,
378382 ) -> Result<RecordList> {
383383+ if self.state.only_index_links {
384384+ miette::bail!("block storage is disabled (HYDRANT_ONLY_INDEX_LINKS)");
385385+ }
379386 let did = self.did.clone().into_static();
380387381388 let state = self.state.clone();
···479486 &self,
480487 ) -> Result<Option<impl futures::Stream<Item = std::io::Result<bytes::Bytes>> + Send + 'static>>
481488 {
489489+ if self.state.only_index_links {
490490+ miette::bail!("block storage is disabled (HYDRANT_ONLY_INDEX_LINKS)");
491491+ }
482492 use iroh_car::{CarHeader, CarWriter};
483493 use jacquard_repo::{BlockStore, MemoryBlockStore, Mst};
484494 use miette::WrapErr;