OCI Object Storage Lifecycle: Tiering, Versioning, and Automated Cost Optimization

Object Storage lifecycle policies automate the cost optimization work nobody does manually: moving objects to cheaper tiers as they age, expiring old versions, and deleting objects that have passed retention. Without lifecycle policies, objects accumulate in the Standard tier indefinitely and storage costs grow without bound. This post covers designing lifecycle policies for multiple object classes with Terraform and configuring versioning correctly.

Step 1: Buckets with Versioning

resource "oci_objectstorage_bucket" "application_data" {
  compartment_id = var.compartment_id
  namespace      = var.tenancy_namespace
  name           = "application-data"
  access_type    = "NoPublicAccess"
  storage_tier   = "Standard"
  versioning     = "Enabled"
  kms_key_id     = var.vault_key_id
  defined_tags   = { "Operations.Environment" = "production", "Operations.ManagedBy" = "terraform" }
}

resource "oci_objectstorage_bucket" "raw_data_lake" {
  compartment_id = var.compartment_id
  namespace      = var.tenancy_namespace
  name           = "raw-data-lake"
  access_type    = "NoPublicAccess"
  storage_tier   = "Standard"
  versioning     = "Disabled"
}

resource "oci_objectstorage_bucket" "build_artifacts" {
  compartment_id = var.compartment_id
  namespace      = var.tenancy_namespace
  name           = "build-artifacts"
  access_type    = "NoPublicAccess"
  storage_tier   = "Standard"
  versioning     = "Disabled"
}

Step 2: Lifecycle Policies per Object Class

resource "oci_objectstorage_object_lifecycle_policy" "app_data_lifecycle" {
  bucket    = oci_objectstorage_bucket.application_data.name
  namespace = var.tenancy_namespace

  rules {
    name        = "processed-to-infrequent-60d"
    action      = "INFREQUENT_ACCESS"
    is_enabled  = true
    time_amount = 60
    time_unit   = "DAYS"
    object_name_filter { inclusion_prefixes = ["processed/"] }
  }

  rules {
    name        = "processed-to-archive-180d"
    action      = "ARCHIVE"
    is_enabled  = true
    time_amount = 180
    time_unit   = "DAYS"
    object_name_filter { inclusion_prefixes = ["processed/"] }
  }

  rules {
    name        = "delete-old-noncurrent-versions"
    action      = "DELETE"
    is_enabled  = true
    time_amount = 30
    time_unit   = "DAYS"
    target      = "previousObjectVersions"
  }
}

resource "oci_objectstorage_object_lifecycle_policy" "raw_lake_lifecycle" {
  bucket    = oci_objectstorage_bucket.raw_data_lake.name
  namespace = var.tenancy_namespace

  rules {
    name        = "raw-to-infrequent-30d"
    action      = "INFREQUENT_ACCESS"
    is_enabled  = true
    time_amount = 30
    time_unit   = "DAYS"
    object_name_filter { inclusion_prefixes = ["raw/"] }
  }

  rules {
    name        = "raw-to-archive-90d"
    action      = "ARCHIVE"
    is_enabled  = true
    time_amount = 90
    time_unit   = "DAYS"
    object_name_filter { inclusion_prefixes = ["raw/"] }
  }
}

resource "oci_objectstorage_object_lifecycle_policy" "builds_lifecycle" {
  bucket    = oci_objectstorage_bucket.build_artifacts.name
  namespace = var.tenancy_namespace

  rules {
    name        = "delete-old-builds-90d"
    action      = "DELETE"
    is_enabled  = true
    time_amount = 90
    time_unit   = "DAYS"
  }
}

Step 3: Storage Growth Alarm

resource "oci_monitoring_alarm" "storage_object_count_high" {
  compartment_id        = var.compartment_id
  display_name          = "object-storage-growth-alert"
  is_enabled            = true
  metric_compartment_id = var.compartment_id
  namespace             = "oci_objectstorage"
  query                 = "StoredObjectCount[1d]{bucketName = 'raw-data-lake'}.max() > 5000000"
  severity              = "WARNING"
  pending_duration      = "PT24H"
  destinations          = [var.ops_notification_topic_id]
  body                  = "Object count in raw-data-lake exceeds 5M. Review lifecycle policy coverage and prefix filter completeness."
}

Operational Notes

Lifecycle rules apply based on object creation date, not last-accessed date. An object created 31 days ago and read yesterday still satisfies a 30-day tiering rule. Object Storage does not track per-object last-access time. Design lifecycle rules around age from creation, and use separate bucket prefixes for objects with different access patterns if you need different tiering schedules.

Noncurrent version deletion is separate from current object deletion. Without a noncurrent version delete rule, every overwrite on a versioned bucket retains the previous version forever. A bucket with daily overwrites accumulates 365 versions per year of each object. Add a noncurrent version delete rule with a 30 to 90 day retention to keep recent versions for recovery while preventing unbounded storage growth.

Regards,
Osama

#OCI #OracleCloud #ObjectStorage #Lifecycle #Terraform #IaC #TechBlog #Oracle #FinOps #CloudStorage #StorageOptimization #OracleCloudInfrastructure #Versioning #DataRetention #ArchiveStorage #InfrequentAccess #CostManagement #DataLake #CloudNative #PlatformEngineering

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.