Object Storage Service (OSS) looks like S3 closely enough that teams migrating from AWS tend to treat it as a drop-in and move on. Mostly true — but a few patterns are worth setting up deliberately rather than discovering you need them after a storage bill or an access-control incident.

Lifecycle rules from day one

Uploaded objects that never expire are the single most common source of storage cost creep. Set lifecycle rules at bucket creation, not after the bill shows up:

aliyun oss lifecycle --method put oss://my-bucket lifecycle.xml
<LifecycleConfiguration>
  <Rule>
    <ID>archive-old-logs</ID>
    <Prefix>logs/</Prefix>
    <Status>Enabled</Status>
    <Transition>
      <Days>30</Days>
      <StorageClass>IA</StorageClass>
    </Transition>
    <Transition>
      <Days>90</Days>
      <StorageClass>Archive</StorageClass>
    </Transition>
    <Expiration>
      <Days>365</Days>
    </Expiration>
  </Rule>
</LifecycleConfiguration>

Three storage tiers do the heavy lifting: Standard for anything actively read, IA (Infrequent Access) for data touched occasionally, and Archive for compliance retention you hope never gets read again. Moving logs and backups through this tiering automatically is usually a 40-60% storage cost reduction with zero application changes.

Signed URLs instead of public buckets

The fastest way to leak data on any object storage platform is a bucket set to public-read because it was easier during development. OSS signed URLs solve the actual problem — temporary, scoped access — without ever making the bucket public:

import oss2

auth = oss2.Auth(access_key_id, access_key_secret)
bucket = oss2.Bucket(auth, endpoint, bucket_name)

# URL valid for 10 minutes, read-only
url = bucket.sign_url('GET', 'private/report.pdf', 600)

Pair this with a bucket policy that denies all public access explicitly, so a future misconfiguration can't silently re-open it:

{
  "Version": "1",
  "Statement": [
    {
      "Effect": "Deny",
      "Principal": "*",
      "Action": "oss:GetObject",
      "Resource": "acs:oss:*:*:my-bucket/*",
      "Condition": {
        "StringNotEquals": {
          "acs:SourceVpc": "vpc-xxxxxxxxxxxx"
        }
      }
    }
  ]
}

Cross-region replication for disaster recovery

For anything where losing a region's worth of data isn't acceptable, enable Cross-Region Replication (CRR) rather than building your own sync job:

aliyun oss crr --method put oss://source-bucket \
  --target-bucket dr-bucket \
  --target-region ap-southeast-2

Replication is asynchronous — it's a recovery-point-objective tool, not a synchronous mirror. If your RPO tolerance is measured in seconds rather than minutes, CRR alone isn't sufficient and you need application-level dual-writes instead.

Multipart upload for anything over 100MB

Single-request uploads fail more often than they should on large files over unreliable connections. OSS multipart upload splits the file and lets failed parts retry independently:

upload_id = bucket.init_multipart_upload('large-file.zip').upload_id
parts = []
for i, chunk in enumerate(split_file(file_path, part_size=10 * 1024 * 1024)):
    result = bucket.upload_part('large-file.zip', upload_id, i + 1, chunk)
    parts.append(oss2.models.PartInfo(i + 1, result.etag))
bucket.complete_multipart_upload('large-file.zip', upload_id, parts)

The checklist for a new bucket

  • Public access denied by default, signed URLs for anything shared externally
  • Lifecycle rules set before the first object lands, not after
  • Versioning enabled if the bucket holds anything you can't afford to overwrite by mistake
  • Cross-region replication for disaster-recovery-critical data
  • Server-side encryption enabled — OSS supports both platform-managed and KMS-managed keys

None of this is exotic; it's the same discipline S3 or Azure Blob needs. The difference is entirely in remembering to apply it, since OSS's console defaults are permissive enough to let you skip all five and still ship something that works — until it doesn't.