uploads: Allow uploads to set storage class.

Uploads are well-positioned to use S3's "intelligent tiering" storage
class.  Add a setting to let uploaded files to declare their desired
storage class at upload time, and document how to move existing files
to the same storage class.
This commit is contained in:
Alex Vandiver 2023-07-19 02:27:03 +00:00 committed by Tim Abbott
parent ba7492a314
commit d957559371
5 changed files with 57 additions and 2 deletions

View File

@ -206,3 +206,38 @@ Congratulations! Your uploaded files are now migrated to S3.
**Caveat**: The current version of this tool does not migrate an **Caveat**: The current version of this tool does not migrate an
uploaded organization avatar or logo. uploaded organization avatar or logo.
## S3 data storage class
In general, uploaded files in Zulip are accessed frequently at first, and then
age out of frequent access. The S3 backend provides the [S3
Intelligent-Tiering][s3-it] [storage class][s3-storage-class] which provides
cheaper storage for less frequently accessed objects, and may provide overall
cost savings for large deployments.
You can configure Zulip to store uploaded files using Intelligent-Tiering by
setting `S3_UPLOADS_STORAGE_CLASS` to `INTELLIGENT_TIERING` in `settings.py`.
This setting can take any of the following [storage class
value][s3-storage-class-constant] values:
- `STANDARD`
- `STANDARD_IA`
- `ONEZONE_IA`
- `REDUCED_REDUNDANCY`
- `GLACIER_IR`
- `INTELLIGENT_TIERING`
Setting `S3_UPLOADS_STORAGE_CLASS` does not affect the storage class of existing
objects. In order to change those, for example to `INTELLIGENT_TIERING`, perform
an in-place copy:
aws s3 cp --storage-class INTELLIGENT_TIERING --recursive \
s3://your-bucket-name/ s3://your-bucket-name/
Note that changing the lifecycle of existing objects will incur a [one-time
lifecycle transition cost][s3-pricing].
[s3-it]: https://aws.amazon.com/s3/storage-classes/intelligent-tiering/
[s3-storage-class]: https://aws.amazon.com/s3/storage-classes/
[s3-storage-class-constant]: https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutObject.html#AmazonS3-PutObject-request-header-StorageClass
[s3-pricing]: https://aws.amazon.com/s3/pricing/

View File

@ -65,6 +65,7 @@ def _transfer_message_files_to_s3(attachment: Attachment) -> None:
guessed_type, guessed_type,
attachment.owner, attachment.owner,
f.read(), f.read(),
settings.S3_UPLOADS_STORAGE_CLASS,
) )
logging.info("Uploaded message file in path %s", file_path) logging.info("Uploaded message file in path %s", file_path)
except FileNotFoundError: # nocoverage except FileNotFoundError: # nocoverage

View File

@ -4,7 +4,7 @@ import secrets
import urllib import urllib
from datetime import datetime from datetime import datetime
from mimetypes import guess_type from mimetypes import guess_type
from typing import IO, Any, BinaryIO, Callable, Iterator, List, Optional, Tuple from typing import IO, Any, BinaryIO, Callable, Iterator, List, Literal, Optional, Tuple
import boto3 import boto3
import botocore import botocore
@ -71,6 +71,14 @@ def upload_image_to_s3(
content_type: Optional[str], content_type: Optional[str],
user_profile: UserProfile, user_profile: UserProfile,
contents: bytes, contents: bytes,
storage_class: Literal[
"GLACIER_IR",
"INTELLIGENT_TIERING",
"ONEZONE_IA",
"REDUCED_REDUNDANCY",
"STANDARD",
"STANDARD_IA",
] = "STANDARD",
) -> None: ) -> None:
key = bucket.Object(file_name) key = bucket.Object(file_name)
metadata = { metadata = {
@ -89,6 +97,7 @@ def upload_image_to_s3(
Metadata=metadata, Metadata=metadata,
ContentType=content_type, ContentType=content_type,
ContentDisposition=content_disposition, ContentDisposition=content_disposition,
StorageClass=storage_class,
) )
@ -224,6 +233,7 @@ class S3UploadBackend(ZulipUploadBackend):
content_type, content_type,
user_profile, user_profile,
file_data, file_data,
settings.S3_UPLOADS_STORAGE_CLASS,
) )
create_attachment( create_attachment(

View File

@ -1,6 +1,6 @@
import os import os
from email.headerregistry import Address from email.headerregistry import Address
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple from typing import TYPE_CHECKING, Any, Dict, List, Literal, Optional, Tuple
from scripts.lib.zulip_tools import deport from scripts.lib.zulip_tools import deport
from zproject.settings_types import JwtAuthKey, OIDCIdPConfigDict, SAMLIdPConfigDict from zproject.settings_types import JwtAuthKey, OIDCIdPConfigDict, SAMLIdPConfigDict
@ -145,6 +145,14 @@ S3_AUTH_UPLOADS_BUCKET = ""
S3_REGION: Optional[str] = None S3_REGION: Optional[str] = None
S3_ENDPOINT_URL: Optional[str] = None S3_ENDPOINT_URL: Optional[str] = None
S3_SKIP_PROXY = True S3_SKIP_PROXY = True
S3_UPLOADS_STORAGE_CLASS: Literal[
"GLACIER_IR",
"INTELLIGENT_TIERING",
"ONEZONE_IA",
"REDUCED_REDUNDANCY",
"STANDARD",
"STANDARD_IA",
] = "STANDARD"
LOCAL_UPLOADS_DIR: Optional[str] = None LOCAL_UPLOADS_DIR: Optional[str] = None
LOCAL_AVATARS_DIR: Optional[str] = None LOCAL_AVATARS_DIR: Optional[str] = None
LOCAL_FILES_DIR: Optional[str] = None LOCAL_FILES_DIR: Optional[str] = None

View File

@ -768,6 +768,7 @@ LOCAL_UPLOADS_DIR = "/home/zulip/uploads"
# S3_REGION = None # S3_REGION = None
# S3_ENDPOINT_URL = None # S3_ENDPOINT_URL = None
# S3_SKIP_PROXY = True # S3_SKIP_PROXY = True
# S3_UPLOADS_STORAGE_CLASS = "STANDARD"
## Maximum allowed size of uploaded files, in megabytes. This value is ## Maximum allowed size of uploaded files, in megabytes. This value is
## capped at 80MB in the nginx configuration, because the file upload ## capped at 80MB in the nginx configuration, because the file upload