LemmyNet/lemmy · error
err getting activity: {e:?}
Error message
err getting activity: {e:?} What it means
get_activity_cached loads a SentActivity by id through a moka cache (try_get_with); when the underlying read fails, the LemmyError (wrapped in Arc) is converted into this anyhow error. Holes in activity serial ids are normal in PostgreSQL, so a missing id surfaces as this error too.
Source
Thrown at crates/apub/send/src/util.rs:193
}
type CachedActivityInfo = Option<Arc<SentActivity>>;
/// activities are immutable so cache does not need to have TTL
/// May return None if the corresponding id does not exist or is a received activity.
/// Holes in serials are expected behaviour in postgresql
/// todo: cache size should probably be configurable / dependent on desired memory usage
pub(crate) async fn get_activity_cached(
pool: &mut DbPool<'_>,
activity_id: ActivityId,
) -> Result<CachedActivityInfo> {
static ACTIVITIES: LazyLock<Cache<ActivityId, CachedActivityInfo>> =
LazyLock::new(|| Cache::builder().max_capacity(10000).build());
ACTIVITIES
.try_get_with(activity_id, async {
Ok(Some(Arc::new(SentActivity::read(pool, activity_id).await?)))
})
.await
.map_err(|e: Arc<LemmyError>| anyhow::anyhow!("err getting activity: {e:?}"))
}
/// return the most current activity id (with 1 second cache)
pub(crate) async fn get_latest_activity_id(pool: &mut DbPool<'_>) -> Result<Option<ActivityId>> {
static CACHE: LazyLock<Cache<(), Option<ActivityId>>> = LazyLock::new(|| {
Cache::builder()
.time_to_live(*CACHE_DURATION_LATEST_ID)
.build()
});
CACHE
.try_get_with((), async {
use lemmy_db_schema_file::schema::sent_activity::dsl::{id, sent_activity};
let conn = &mut get_conn(pool).await?;
let latest_id: Option<ActivityId> = sent_activity
.select(diesel::dsl::max(id))
.get_result(conn)
.await?;
anyhow::Result::<_, anyhow::Error>::Ok(latest_id)View on GitHub (pinned to 439734dd63)
Solutions
- Confirm whether the id is a normal PostgreSQL sequence hole — treat as benign and skip
- Check DB health/logs for the underlying query failure
- Retry once the database is reachable; the cache will re-fetch
- If gaps recur and block the queue, adjust the loop to skip missing ids instead of failing
Example fix
// before
let activity = get_activity_cached(pool, activity_id).await?; // aborts on gap
// after
let activity = match get_activity_cached(pool, activity_id).await {
Ok(a) => a,
Err(e) => { warn!("skipping activity {activity_id}: {e:?}"); continue; }
}; Defensive patterns
Strategy: try-catch
Try / catch
match get_activity_cached(pool, id).await {
Ok(a) => process(a),
Err(e) => { warn!("activity {id} unavailable (maybe serial gap): {e:?}"); skip(id); }
} Prevention
- Treat missing serial ids as expected PostgreSQL behavior and skip them
- Cache failures are not permanent — retry on transient DB errors
- Monitor sent_activities for abnormal gap growth
When it happens
Trigger: spawn_send_if_needed calls get_activity_cached with an activity_id that does not exist in sent_activities (serial gap) or the DB read fails (connection/query error).
Common situations: PostgreSQL sequence gaps after rollbacks/crashes cause the queue to reference ids that were never committed; transient DB outages during federation; read replicas lagging.
Understand the failure class
Background: Record Not Found Errors: "not found", RecordNotFound, and "was not found" — what they mean and how to fix them — this error's family across 28 libraries.
Related errors
- err getting actor {actor_type:?} {actor_apub_id}: {e:?}
- err getting id: {e:?}
- activity is from before lemmy 0.19
- {}: next id to send is not as expected: {:?} != {:?}
- err getting local site: {e:?}
AI-assisted analysis of LemmyNet/lemmy@439734dd63 (2026-09-06).
Data as JSON: /api/errors/e192313ad2cf0ac3.
Report an issue: GitHub.