Constellation, Spacedust, Slingshot, UFOs: atproto crates and services for microcosm

slingshot cache hit rates

+58 -30
+1 -1
slingshot/api-description.md
··· 1 1 _A [gravitational slingshot](https://en.wikipedia.org/wiki/Gravity_assist) makes use of the gravity and relative movements of celestial bodies to accelerate a spacecraft and change its trajectory._ 2 2 3 3 4 - # Slingshot: edge record cache 4 + # Slingshot: edge record and identity cache 5 5 6 6 Applications in [ATProtocol](https://atproto.com/) store data in users' own [PDS](https://atproto.com/guides/self-hosting) (Personal Data Server), which are distributed across thousands of independently-run servers all over the world. Trying to access this data poses challenges for client applications: 7 7
+42 -23
slingshot/src/identity.rs
··· 11 11 /// 1. handle -> DID resolution: getRecord must accept a handle for `repo` param 12 12 /// 2. DID -> PDS resolution: so we know where to getRecord 13 13 /// 3. DID -> handle resolution: for bidirectional handle validation and in case we want to offer this 14 - use std::time::Duration; 14 + use std::time::{Duration, Instant}; 15 15 use tokio::sync::Mutex; 16 16 use tokio_util::sync::CancellationToken; 17 17 ··· 264 264 handle: &Handle, 265 265 ) -> Result<Option<Did>, IdentityError> { 266 266 let key = IdentityKey::Handle(handle.clone()); 267 + metrics::counter!("slingshot_get_handle").increment(1); 267 268 let entry = self 268 269 .cache 269 270 .get_or_fetch(&key, { 270 271 let handle = handle.clone(); 271 272 let resolver = self.handle_resolver.clone(); 272 273 || async move { 273 - match resolver.resolve(&handle).await { 274 - Ok(did) => Ok(IdentityVal(UtcDateTime::now(), IdentityData::Did(did))), 275 - Err(atrium_identity::Error::NotFound) => { 276 - Ok(IdentityVal(UtcDateTime::now(), IdentityData::NotFound)) 277 - } 274 + let t0 = Instant::now(); 275 + let (res, success) = match resolver.resolve(&handle).await { 276 + Ok(did) => ( 277 + Ok(IdentityVal(UtcDateTime::now(), IdentityData::Did(did))), 278 + "true", 279 + ), 280 + Err(atrium_identity::Error::NotFound) => ( 281 + Ok(IdentityVal(UtcDateTime::now(), IdentityData::NotFound)), 282 + "false", 283 + ), 278 284 Err(other) => { 279 285 log::debug!("other error resolving handle: {other:?}"); 280 - Err(IdentityError::ResolutionFailed(other)) 286 + (Err(IdentityError::ResolutionFailed(other)), "false") 281 287 } 282 - } 288 + }; 289 + metrics::histogram!("slingshot_fetch_handle", "success" => success) 290 + .record(t0.elapsed()); 291 + res 283 292 } 284 293 }) 285 294 .await?; ··· 314 323 did: &Did, 315 324 ) -> Result<Option<PartialMiniDoc>, IdentityError> { 316 325 let key = IdentityKey::Did(did.clone()); 326 + metrics::counter!("slingshot_get_did_doc").increment(1); 317 327 let entry = self 318 328 .cache 319 329 .get_or_fetch(&key, { 320 330 let did = did.clone(); 321 331 let resolver = self.did_resolver.clone(); 322 332 || async move { 323 - match resolver.resolve(&did).await { 324 - Ok(did_doc) => { 333 + let t0 = Instant::now(); 334 + let (res, success) = match resolver.resolve(&did).await { 335 + Ok(did_doc) if did_doc.id != did.to_string() => ( 325 336 // TODO: fix in atrium: should verify id is did 326 - if did_doc.id != did.to_string() { 327 - return Err(IdentityError::BadDidDoc( 328 - "did doc's id did not match did".to_string(), 329 - )); 330 - } 331 - let mini_doc = did_doc.try_into().map_err(IdentityError::BadDidDoc)?; 332 - Ok(IdentityVal(UtcDateTime::now(), IdentityData::Doc(mini_doc))) 333 - } 334 - Err(atrium_identity::Error::NotFound) => { 335 - Ok(IdentityVal(UtcDateTime::now(), IdentityData::NotFound)) 336 - } 337 - Err(other) => Err(IdentityError::ResolutionFailed(other)), 338 - } 337 + Err(IdentityError::BadDidDoc( 338 + "did doc's id did not match did".to_string(), 339 + )), 340 + "false", 341 + ), 342 + Ok(did_doc) => match did_doc.try_into() { 343 + Ok(mini_doc) => ( 344 + Ok(IdentityVal(UtcDateTime::now(), IdentityData::Doc(mini_doc))), 345 + "true", 346 + ), 347 + Err(e) => (Err(IdentityError::BadDidDoc(e)), "false"), 348 + }, 349 + Err(atrium_identity::Error::NotFound) => ( 350 + Ok(IdentityVal(UtcDateTime::now(), IdentityData::NotFound)), 351 + "false", 352 + ), 353 + Err(other) => (Err(IdentityError::ResolutionFailed(other)), "false"), 354 + }; 355 + metrics::histogram!("slingshot_fetch_did_doc", "success" => success) 356 + .record(t0.elapsed()); 357 + res 339 358 } 340 359 }) 341 360 .await?;
+13 -4
slingshot/src/server.rs
··· 9 9 use std::path::PathBuf; 10 10 use std::str::FromStr; 11 11 use std::sync::Arc; 12 + use std::time::Instant; 12 13 use tokio_util::sync::CancellationToken; 13 14 14 15 use poem::{ ··· 609 610 610 611 let at_uri = format!("at://{}/{}/{}", &*did, &*collection, &*rkey); 611 612 613 + metrics::counter!("slingshot_get_record").increment(1); 612 614 let fr = self 613 615 .cache 614 616 .get_or_fetch(&at_uri, { 615 617 let cid = cid.clone(); 616 618 let repo_api = self.repo.clone(); 617 - || async move { repo_api.get_record(&did, &collection, &rkey, &cid).await } 619 + || async move { 620 + let t0 = Instant::now(); 621 + let res = repo_api.get_record(&did, &collection, &rkey, &cid).await; 622 + let success = if res.is_ok() { "true" } else { "false" }; 623 + metrics::histogram!("slingshot_fetch_record", "success" => success) 624 + .record(t0.elapsed()); 625 + res 626 + } 618 627 }) 619 628 .await; 620 629 ··· 690 699 } 691 700 692 701 // TODO 693 - // #[oai(path = "/com.atproto.identity.resolveHandle", method = "get")] 694 702 // #[oai(path = "/com.atproto.identity.resolveDid", method = "get")] 695 703 // but these are both not specified to do bidirectional validation, which is what we want to offer 696 704 // com.atproto.identity.resolveIdentity seems right, but requires returning the full did-doc ··· 699 707 // handle -> verified did + pds url 700 708 // 701 709 // we could do horrible things and implement resolveIdentity with only a stripped-down fake did doc 702 - // but this will *definitely* cause problems because eg. we're not currently storing pubkeys and 703 - // those are a little bit important 710 + // but this will *definitely* cause problems probably 711 + // 712 + // resolveMiniDoc gets most of this well enough. 704 713 } 705 714 706 715 #[derive(Debug, Clone, Serialize)]
+2 -2
slingshot/static/index.html
··· 43 43 <body> 44 44 <header class="custom-header scalar-app"> 45 45 <p> 46 - TODO: thing 46 + get atproto records and identities faster 47 47 </p> 48 48 <nav> 49 49 <b>a <a href="https://microcosm.blue">microcosm</a> project</b> 50 50 <a href="https://bsky.app/profile/microcosm.blue">@microcosm.blue</a> 51 - <a href="https://github.com/at-microcosm">github</a> 51 + <a href="https://tangled.org/microcosm.blue/microcosm-rs">tangled</a> 52 52 </nav> 53 53 </header> 54 54