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 @@