Replies: 2 comments 1 reply
-
|
위 내용에서 한가지를 정정합니다. presigned url 은 최초 생성한 url을 편집할 수가 없어 variant(파일 사이즈)를 임의로 클라이언트에서 붙일 수 없습니다. 따라서 url 을 만들어서 내려줄 때 여러 url 을 동시에 반환하거나(presigned-url을 2개 만들어서), api 에 따라 다르게 내려줘야 합니다. 가령 채팅의 경우 medium 과 large 이미지를 보유한다고 가정하겠습니다. medium 은 사진에 대한 미리보기를 제공하고, large 는 전체 화면을 가정하고 정의하였습니다. 처음 클라이언트가 채팅 로그를 요청하고, 해당 채팅 로그에 사진이 존재한다면 해당 이미지 url 을 medium 에 대해 presigned-url 로 만들어 반환합니다. 클라이언트를 이를 통해 바로 미리보기 용 이미지를 받을 수 있습니다. 만약 사용자가 해당 미리보기 이미지를 클릭하여 전체화면을 원한다면 클라이언트가 서버에게 large 용 url 을 요청하고, 이를 반환하여 다시 클라이언트는 이를 기반으로 large 이미지를 가져옵니다. 클라이언트는 이미 medium 을 가지고 있으므로 요청 및 렌더링 동안 medium 을 전체화면으로 띄우고 large 가 완전히 로딩되면 이를 보여줌으로 UX 적으로 괜찮을 듯 합니다. |
Beta Was this translation helpful? Give feedback.
-
|
전반적으로 동의합니다. 추후 구현하면서 더 좋은 개선 방향이 보이면 공유하겠습니다. |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
-
해당 논의는 이미지 처리 방법에 대해 정의하고, 이를 어떻게 저장할 것인지에 대한 내용입니다.
논의가 필요한 부분은 다음과 같습니다.
이미지 및 파일 분리 방안
이미지 파일 타입 및 크기 제한
클라이언트로부터 이미지를 받아 저장하는 플로우, API 설계
이미지 리사이징 전략
이미지 수정/삭제/조회 방법 정의
이미지 단건/다건 조회 분리 여부 및 전략
이미지 파일 이름 규칙 정의 & 유일성 보장 정의
메타데이터에 대한 정의
그 외...
제안
이미지 및 파일 분리
S3 및 Minio 는 이미지 및 파일 등 전반에 걸쳐 저장할 수 있습니다. 파일의 경우 클라이언트로부터 파생된 것이 아닌, 여러 로그, 오래된 데이터 등을 의미합니다.
이미지는 크게 본다면 파일에 속하므로, 최상위 추상 클래스
AbstractFileManager를 두고 기본적인 Minio 저장/갱신/삭제 로직 등을 구현합니다. 추후,ImageFileManager,LogFileManager,JsonFileManager등의 구현체를 사용하도록 합니다.각 구체 클래스는 그 사용 용도가 명확하고, 동시에 사용될 여지가 존재하지 않으므로 전략 패턴은 필요 없다고 생각됩니다.
각 구체 클래스는 저장할 데이터의 최대 크기, 이름 생성 규칙, 메타데이터 생성 규칙, 파일의 생명 주기, 파일 타입 등을 정의하여야 합니다.
또한 이미지의 경우는 리사이징 전략, versioning 등을 정의해야 합니다.
MinIO 와 S3 의 migration 을 위해 적절한 adapter pattern 을 사용하도록 하며, 이는
AbstractFileManager에서 사용될 수 있습니다. 추후 확장성을 고려하여 구체 클래스에서 어떤 저장소를 사용할껀지 override 할 수 있도록 구현하면 좋을 것 같습니다.이미지 파일 타입 및 크기 제한
파일 타입은 jpg, png, jpeg, png, gif, webp, svg 정도로 보면 될 듯 합니다. 단, 이는 직접 커스텀이 가능해야 하며, 가령 profile 의 경우 imageFileManager 를 extends 하여, fileType 을 결정하는 메서드를 override 하여 구현하도록 하면 좋을 듯 합니다.
단, 상기 명시된 타입 외를 override 한 메서드에서 정의하는 것을 막기 위해, 각 파일 타입을 ImageFileManager 에서 내부 protected enum 으로 관리하여 정의된 값만 재정의할 수 있도록 두면 좋을 듯 합니다.
위 코드는 제안 중 일부입니다. (문법 상 올바른지 확인되지 않음)
이미지의 크기 제한 역시,
getMaxSize를 통해 override 하도록 하며 해당 메서드의 반환 값은 가로 및 세로에 대한 크기를 포함하여야 합니다. 이 역시 profileImage 등, 특정 도메인에 맞게끔 extends 된 manager 에서 재정의 할 수 있으며, minSize 에 대한 정의가 필요할지는 추가적인 논의가 필요해 보입니다.생각하는 최대 크기는 2048 x 2048 입니다. 단 각 도메인 별로 최대 크기를 지정할 수 있어야 하며 채팅시 공유되는 이미지를 제외하고는 대부분 최대 크기가 1024 정도로 제한하면 될 듯 합니다.
위는 1024x1024 사이즈로서, 충분히 선명해 보입니다.
클라이언트로부터 오는 데이터가 1024가 넘는다 하더라도, 적절한 비율로 1024 크기로 수정하여야 합니다. 단, 이는 클라이언트에서 1차적으로 수행해야 하며, 백엔드에서는 혹시 모를 상황(api 를 따서 큰 이미지를 저장하는 경우)을 대비하기 위해 한번 더 검사하는 로직을 추가합니다.
클라이언트로부터 오는 이미지를 저장 / API 설계
앞서 말했던 것처럼 최대 크기를 초과하는 경우, 서버에서 한번 더 검사하는 로직을 추가하도록 합니다.
각 도메인에 따라 필요한 메타데이터, 이미지 확장자 및 크기 제한이 전부 다를 수 있으므로 이미지 저장 요청 API 는 도메인 별로 분리하여야 합니다.
이미지를 저장 후, 저장된 이미지의 url 을 클라이언트로 반환하여야 합니다.
이미지 리사이징 전략
ImageFileManager는 enum 을 통해small,medium,large로 관리하며, 이는 역시나 extends 된 도메인 메니저에서 관리될 수 있도록 합니다.해당 코드는 예시이며 문법 적합 여부는 확인하지 않았습니다.
해당 코드와 같이, enum 및 각 enum에 대응되는 라시이징되는 사이즈를 list로 관리한 뒤, 각 list 의 원소대로 저장하도록 하면 될 듯 합니다.
이때, 리사이징된 파일 이름은 가령,
2026010612341234.png라는 파일에 대해2026010612341234-small.png등으로 합니다. 실제 프로필 이미지가 저장되는 경로가 /prod/image/profile/{userId}/2026010612341234-small.png 등이 올 때(small 말고 large, medium 도 존재) 클라이언트로는/prod/image/profile/{userId}/2026010612341234.png만 주고, 클라이언트에서 필요한 이미지를 직접 -small, -large 등의 postfix 를 붙여서 요청하도록 하면 좋을 것 같습니다.이미지 수정/삭제/조회 방법 정의
이미지 조회의 경우, 클라이언트가 url 을 통해 minio / S3 에게 직접 이미지를 요청하는 방식을 차용하고 있습니다. 이때, 앞서 말했듯이 각 사이즈에 대해 -small, -medium, -large 등의 접미사를 붙여서 상황에 맞는 이미지를 직접 선택하도록 합니다.
단, 각 이미지의 성격에 맞추어 presigned-url / public url 로 나누어 관리하여야 합니다. presigned-url 은 서버(스프링)에서 그때그때 url 을 생성하여 반환하고, 클라이언트는 해당 url 을 통해 이미지/파일을 요청할 수 있습니다. presigned url 은 만료 기한이 존재하여, 특정 시간이 지나면 유효하지 않게 됩니다. 따라서 채팅 및 비공개 리소스에서 사용되는 이미지 등에 적합합니다.
가령, 특정 채팅방에서 공유되는 이미지의 경우, 채팅방에서 탈퇴한 유저는 열람할 권한이 없습니다. 따라서 탈퇴한 유저가 해당 이미지에 대해 열람을 시도할 때 서버에서 presigned url 을 내려주기 전 권한을 검증하도록 합니다.
그러나 프로필 이미지 등 공개된 이미지는 public url 을 내려주어 하나의 url 로 계속 관리하면 됩니다.
이를 위해
FileAccessPolicy인터페이스를 두어 클라이언트에 내려줄 url 을 조립 및 저장 시 사용할 문자열을 조립하는 메서드를 정의합니다. 가령,resolveUrl`` 를 둡니다. 그리고 해당 인터페이스에 대한 두 구현체,PublicUrlFileAccessPolicy,PresignedUrlFileAccessPolicy` 를 두어, 적절히 구현합니다.AbstractFileManager는 다음과 같이 구현합니다.후, 이에 대한 구현 클래스는 생성자를 통해 어떤 url 방식을 사용할건지 정의합니다.
또한, 각 access policy 의 두 메서드는
resolveUrl의 경우 presigned url 이 오면 이를 조립하여 반환하는 메서드, public url 인 경우 고정 url 을 만들어서 반환합니다. 고정 url 은 단순히 문자열을 합치면 되고, presigned url 은 서명 과정이 필요합니다.수정 및 삭제의 경우 메타데이터를 통해 권한을 확인하는 과정을 비지니스 로직에 추가하면 될 듯 합니다.
이미지 단건/다건 조회 분리 여부 및 전략
url 을 통한 이미지 요청 자체는 단건 조회만 합니다.
이미지 파일 이름 규칙 정의 & 유일성 보장 정의
추후 배치 서버를 통해 오래된 데이터를 삭제하기 위해 이미지에는 연도-월-일 에 대한 날짜 데이터 + 필요 시 분/초 데이터가 들어가면 좋을 듯 합니다. 단 이는 채팅 이미지와 같이 하나의 단위에 여러 파일이 묶인 경우이며, 가령 image/chat/yyyy/mm/dd/roomId/chatId-variant.ext 방식으로 저장합니다.
프로필 이미지와 같은 경우는 image/profile/userId/yyyymmddhhtt-variant.ext 등을 생각하고 있긴 합니다.
위 채팅 이미지를 저렇게 하는 까닭은 오래된 사진을 한번에 삭제하기 위해서 입니다. (접두사로 필터링 후 배치 삭제 가능)
유일성 보장의 경우 chat 이미지의 경우 chatId 자체가 유일성을 가지며, 프로필 이미지는 유저당 하나의 사진 파일만 가질 수 있으므로 유일성 보장이 필요하지 않습니다.따라서 유저 당 프로필 이미지 1개 / userId 가 유일성을 가집니다.
메타데이터
메타데이터는 해당 파일에 대한 정보입니다. 정의 가능한 데이터는 다음과 같습니다.
경로 및 파일 이름
타입(image, log, json...)
domain (string / profile, chat, group profile, chat file ...)
source id(group profile 인 경우 groupId, chat message 인 경우 chatRoomId..., String)
createdDate
만료 시각(데이터를 언제까지 보관해야되는지(채팅의 경우 2주 등))
원본 파일 이름
버전
저장 중인 variant 종류(사이즈, 비트마스킹)
프리사인 여부(PUBLIC, PRESIGNED...)
extra
extra 는 JSON 이며 json 으로 구성.
필요에 따라 checksum, width/height, mimetype, fileSize 등
위 내용을 보다 "문서화"한 내용은 다음 위키에 두었으니 참고해주시면 될 듯 합니다.
파일 저장 프레임워크 문서
Beta Was this translation helpful? Give feedback.
All reactions