Skip to content
Clone

feat(avatar): update avatar URL caching strategy and TTL for improved performance

ZhenYi committed
parent d5186b7 commit 148b57b Browse files

5 files changed

+54 −23

This change is very large, so some diffs are not shown. Clone the repository to see the full change.

File tree 5
Modified lib/service/space/src/handlers/get_space_image_file.rs View lib/service/space/src/handlers/get_space_image_file.rs
@@ -21,7 +21,7 @@
2121 get,
2222 path = "/spaces/{space_name}/images/{kind}/file",
2323 tag = "space",
24 description = "重定向到 S3 预签名空间图片链接(7 天有效, Redis 缓存)。默认返回压缩后的预览图,`?variant=original` 返回上传原图。私有社区对非成员返回 404。",
24 description = "重定向到 S3 预签名空间图片链接(1 小时有效, Redis 缓存 48 分钟)。默认返回压缩后的预览图,`?variant=original` 返回上传原图。私有社区对非成员返回 404。",
2525 params(SpaceImageKindPath, SpaceImageVariantQuery),
2626 responses(
2727 (status = 302, description = "redirect to presigned s3 space image url"),
Modified lib/service/space/src/services/images.rs View lib/service/space/src/services/images.rs
@@ -34,8 +34,11 @@
3434
3535pub const MAX_SPACE_IMAGE_BYTES: usize = 5 * 1024 * 1024;
3636pub const SPACE_IMAGE_PREFIX: &str = "space-images";
37pub const SPACE_IMAGE_URL_TTL_SECS: u64 = 7 * 24 * 60 * 60;
38pub const SPACE_IMAGE_URL_CACHE_PREFIX: &str = "space:image:url";
37pub const SPACE_IMAGE_URL_TTL_SECS: u64 = 60 * 60;
38/// Strictly shorter than the signature lifetime, so a cache hit can never hand
39/// out a URL that is about to expire.
40pub const SPACE_IMAGE_URL_CACHE_TTL_SECS: u64 = SPACE_IMAGE_URL_TTL_SECS / 5 * 4;
41pub const SPACE_IMAGE_URL_CACHE_PREFIX: &str = "space:image:url:v2";
3942
4043#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, ToSchema)]
4144#[serde(rename_all = "lowercase")]
@@ -108,9 +111,19 @@
108111 }
109112}
110113
111fn space_image_url_cache_key(space_id: Uuid, kind: SpaceImageKind) -> String {
114/// Keyed by variant too: the preview and the original are different objects, so
115/// sharing one key would pin whichever representation was requested first.
116fn space_image_url_cache_key(
117 space_id: Uuid,
118 kind: SpaceImageKind,
119 variant: ImageVariant,
120) -> String {
121 let variant = match variant {
122 ImageVariant::Preview => "preview",
123 ImageVariant::Original => "original",
124 };
112125 format!(
113 "{SPACE_IMAGE_URL_CACHE_PREFIX}:{space_id}:{}",
126 "{SPACE_IMAGE_URL_CACHE_PREFIX}:{space_id}:{}:{variant}",
114127 kind.as_str()
115128 )
116129}
@@ -387,9 +400,10 @@
387400 &self,
388401 space_id: Uuid,
389402 kind: SpaceImageKind,
403 variant: ImageVariant,
390404 stored_url: &str,
391405 ) -> Result<String, SpaceError> {
392 let cache_key = space_image_url_cache_key(space_id, kind);
406 let cache_key = space_image_url_cache_key(space_id, kind, variant);
393407 match cache::client::get_string(&self.redis, &cache_key).await {
394408 Ok(Some(url)) => return Ok(url),
395409 Ok(None) => {}
@@ -402,14 +416,34 @@
402416 .storage
403417 .as_ref()
404418 .presign_get(&key, SPACE_IMAGE_URL_TTL_SECS as u32)?;
405 if let Err(error) =
406 cache::client::set_string(&self.redis, &cache_key, &url, SPACE_IMAGE_URL_TTL_SECS).await
419 if let Err(error) = cache::client::set_string(
420 &self.redis,
421 &cache_key,
422 &url,
423 SPACE_IMAGE_URL_CACHE_TTL_SECS,
424 )
425 .await
407426 {
408427 tracing::warn!(%error, "failed to cache space image presigned url");
409428 }
410429 Ok(url)
411430 }
412431
432 /// Drop the cached URL of both representations; keys are variant-scoped, so
433 /// one invalidation target is no longer enough.
434 async fn invalidate_space_image_url_cache(&self, space_id: Uuid, kind: SpaceImageKind) {
435 for variant in [ImageVariant::Preview, ImageVariant::Original] {
436 if let Err(error) = cache::client::delete(
437 &self.redis,
438 &space_image_url_cache_key(space_id, kind, variant),
439 )
440 .await
441 {
442 tracing::warn!(%error, "failed to invalidate space image url cache");
443 }
444 }
445 }
446
413447 /// Member-visible space row for an image read, mirroring `get_space`:
414448 /// private spaces are a 404 for strangers rather than a 403, so the image
415449 /// URLs leak nothing about which spaces exist.
@@ -501,11 +535,7 @@
501535 )
502536 .await?;
503537
504 if let Err(error) =
505 cache::client::delete(&self.redis, &space_image_url_cache_key(space_id, kind)).await
506 {
507 tracing::warn!(%error, "failed to invalidate space image url cache");
508 }
538 self.invalidate_space_image_url_cache(space_id, kind).await;
509539
510540 // The derivative is built here so the very next page load already gets
511541 // the small object; the same extension may have been in use before, so
@@ -581,11 +611,7 @@
581611
582612 write_space_image(&self.access, space_id, kind, None, None, None).await?;
583613
584 if let Err(error) =
585 cache::client::delete(&self.redis, &space_image_url_cache_key(space_id, kind)).await
586 {
587 tracing::warn!(%error, "failed to invalidate space image url cache");
588 }
614 self.invalidate_space_image_url_cache(space_id, kind).await;
589615
590616 if let Some(key) = old_url.as_deref().and_then(space_image_object_key) {
591617 if let Err(error) = self.storage.as_ref().delete(&key).await {
@@ -686,6 +712,7 @@
686712 .await
687713 }
688714 };
689 self.presign_space_image_url(space.id, kind, &locator).await
715 self.presign_space_image_url(space.id, kind, variant, &locator)
716 .await
690717 }
691718}
Modified lib/service/user/src/handlers/get_user_avatar.rs View lib/service/user/src/handlers/get_user_avatar.rs
@@ -21,7 +21,7 @@
2121 get,
2222 path = "/users/{user_id}/avatar",
2323 tag = "users",
24 description = "重定向到 S3 预签名头像链接(7 天有效, Redis 缓存)。默认返回压缩后的预览图,`?variant=original` 返回上传原图。头像不受资料可见性限制:聊天与成员列表依赖它辨认发言人。",
24 description = "重定向到 S3 预签名头像链接(24 小时有效, Redis 缓存)。默认返回压缩后的预览图,`?variant=original` 返回上传原图。头像不受资料可见性限制:聊天与成员列表依赖它辨认发言人。",
2525 params(("user_id" = i64, Path, description = "user id"), AvatarVariantQuery),
2626 responses(
2727 (status = 302, description = "redirect to presigned s3 avatar url"),
Modified lib/service/user/src/services/avatar.rs View lib/service/user/src/services/avatar.rs
@@ -14,8 +14,11 @@
1414
1515pub const MAX_AVATAR_BYTES: usize = 5 * 1024 * 1024;
1616pub const AVATAR_PREFIX: &str = "avatars";
17pub const AVATAR_URL_TTL_SECS: u64 = 7 * 24 * 60 * 60;
18pub const AVATAR_URL_CACHE_PREFIX: &str = "user:avatar:url";
17pub const AVATAR_URL_TTL_SECS: u64 = 24 * 60 * 60;
18/// Strictly shorter than the signature lifetime, so a cache hit can never hand
19/// out a URL that is about to expire.
20pub const AVATAR_URL_CACHE_TTL_SECS: u64 = AVATAR_URL_TTL_SECS / 5 * 4;
21pub const AVATAR_URL_CACHE_PREFIX: &str = "user:avatar:url:v2";
1922
2023fn avatar_url_cache_key(uid: i64) -> String {
2124 format!("{AVATAR_URL_CACHE_PREFIX}:{uid}")
@@ -150,7 +153,8 @@
150153 .presign_get(&key, AVATAR_URL_TTL_SECS as u32)
151154 .map_err(UserError::from)?;
152155 if let Err(error) =
153 cache::client::set_string(&self.redis, &cache_key, &url, AVATAR_URL_TTL_SECS).await
156 cache::client::set_string(&self.redis, &cache_key, &url, AVATAR_URL_CACHE_TTL_SECS)
157 .await
154158 {
155159 tracing::warn!(%error, "failed to cache avatar presigned url");
156160 }
Modified openapi.json View openapi.json

Diff too large to display.