diff --git a/slingshot/api-description.md b/slingshot/api-description.md index 81ff0fe..0bbdef1 100644 --- a/slingshot/api-description.md +++ b/slingshot/api-description.md @@ -1,7 +1,7 @@ _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._ -# Slingshot: edge record cache +# Slingshot: edge record and identity cache 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: diff --git a/slingshot/src/identity.rs b/slingshot/src/identity.rs index f84c14c..49ed5f3 100644 --- a/slingshot/src/identity.rs +++ b/slingshot/src/identity.rs @@ -11,7 +11,7 @@ use std::sync::Arc; /// 1. handle -> DID resolution: getRecord must accept a handle for `repo` param /// 2. DID -> PDS resolution: so we know where to getRecord /// 3. DID -> handle resolution: for bidirectional handle validation and in case we want to offer this -use std::time::Duration; +use std::time::{Duration, Instant}; use tokio::sync::Mutex; use tokio_util::sync::CancellationToken; @@ -264,22 +264,31 @@ impl Identity { handle: &Handle, ) -> Result, IdentityError> { let key = IdentityKey::Handle(handle.clone()); + metrics::counter!("slingshot_get_handle").increment(1); let entry = self .cache .get_or_fetch(&key, { let handle = handle.clone(); let resolver = self.handle_resolver.clone(); || async move { - match resolver.resolve(&handle).await { - Ok(did) => Ok(IdentityVal(UtcDateTime::now(), IdentityData::Did(did))), - Err(atrium_identity::Error::NotFound) => { - Ok(IdentityVal(UtcDateTime::now(), IdentityData::NotFound)) - } + let t0 = Instant::now(); + let (res, success) = match resolver.resolve(&handle).await { + Ok(did) => ( + Ok(IdentityVal(UtcDateTime::now(), IdentityData::Did(did))), + "true", + ), + Err(atrium_identity::Error::NotFound) => ( + Ok(IdentityVal(UtcDateTime::now(), IdentityData::NotFound)), + "false", + ), Err(other) => { log::debug!("other error resolving handle: {other:?}"); - Err(IdentityError::ResolutionFailed(other)) + (Err(IdentityError::ResolutionFailed(other)), "false") } - } + }; + metrics::histogram!("slingshot_fetch_handle", "success" => success) + .record(t0.elapsed()); + res } }) .await?; @@ -314,28 +323,38 @@ impl Identity { did: &Did, ) -> Result, IdentityError> { let key = IdentityKey::Did(did.clone()); + metrics::counter!("slingshot_get_did_doc").increment(1); let entry = self .cache .get_or_fetch(&key, { let did = did.clone(); let resolver = self.did_resolver.clone(); || async move { - match resolver.resolve(&did).await { - Ok(did_doc) => { + let t0 = Instant::now(); + let (res, success) = match resolver.resolve(&did).await { + Ok(did_doc) if did_doc.id != did.to_string() => ( // TODO: fix in atrium: should verify id is did - if did_doc.id != did.to_string() { - return Err(IdentityError::BadDidDoc( - "did doc's id did not match did".to_string(), - )); - } - let mini_doc = did_doc.try_into().map_err(IdentityError::BadDidDoc)?; - Ok(IdentityVal(UtcDateTime::now(), IdentityData::Doc(mini_doc))) - } - Err(atrium_identity::Error::NotFound) => { - Ok(IdentityVal(UtcDateTime::now(), IdentityData::NotFound)) - } - Err(other) => Err(IdentityError::ResolutionFailed(other)), - } + Err(IdentityError::BadDidDoc( + "did doc's id did not match did".to_string(), + )), + "false", + ), + Ok(did_doc) => match did_doc.try_into() { + Ok(mini_doc) => ( + Ok(IdentityVal(UtcDateTime::now(), IdentityData::Doc(mini_doc))), + "true", + ), + Err(e) => (Err(IdentityError::BadDidDoc(e)), "false"), + }, + Err(atrium_identity::Error::NotFound) => ( + Ok(IdentityVal(UtcDateTime::now(), IdentityData::NotFound)), + "false", + ), + Err(other) => (Err(IdentityError::ResolutionFailed(other)), "false"), + }; + metrics::histogram!("slingshot_fetch_did_doc", "success" => success) + .record(t0.elapsed()); + res } }) .await?; diff --git a/slingshot/src/server.rs b/slingshot/src/server.rs index b793731..41f014f 100644 --- a/slingshot/src/server.rs +++ b/slingshot/src/server.rs @@ -9,6 +9,7 @@ use serde::Serialize; use std::path::PathBuf; use std::str::FromStr; use std::sync::Arc; +use std::time::Instant; use tokio_util::sync::CancellationToken; use poem::{ @@ -609,12 +610,20 @@ impl Xrpc { let at_uri = format!("at://{}/{}/{}", &*did, &*collection, &*rkey); + metrics::counter!("slingshot_get_record").increment(1); let fr = self .cache .get_or_fetch(&at_uri, { let cid = cid.clone(); let repo_api = self.repo.clone(); - || async move { repo_api.get_record(&did, &collection, &rkey, &cid).await } + || async move { + let t0 = Instant::now(); + let res = repo_api.get_record(&did, &collection, &rkey, &cid).await; + let success = if res.is_ok() { "true" } else { "false" }; + metrics::histogram!("slingshot_fetch_record", "success" => success) + .record(t0.elapsed()); + res + } }) .await; @@ -690,7 +699,6 @@ impl Xrpc { } // TODO - // #[oai(path = "/com.atproto.identity.resolveHandle", method = "get")] // #[oai(path = "/com.atproto.identity.resolveDid", method = "get")] // but these are both not specified to do bidirectional validation, which is what we want to offer // com.atproto.identity.resolveIdentity seems right, but requires returning the full did-doc @@ -699,8 +707,9 @@ impl Xrpc { // handle -> verified did + pds url // // we could do horrible things and implement resolveIdentity with only a stripped-down fake did doc - // but this will *definitely* cause problems because eg. we're not currently storing pubkeys and - // those are a little bit important + // but this will *definitely* cause problems probably + // + // resolveMiniDoc gets most of this well enough. } #[derive(Debug, Clone, Serialize)] diff --git a/slingshot/static/index.html b/slingshot/static/index.html index 74b0a3d..a1f79d0 100644 --- a/slingshot/static/index.html +++ b/slingshot/static/index.html @@ -43,12 +43,12 @@

- TODO: thing + get atproto records and identities faster