Chapter Object Storage pada section Data Lake dan Lakehouse telah memperkenalkan semantik dasar layanan ini: object ditulis utuh, dikenal lewat key, dan diakses lewat operasi put, get, delete, dan list. Ketika data TokoKita pindah ke layanan cloud seperti Amazon S3, Google Cloud Storage, atau Azure Blob Storage, ada tiga mekanika tambahan yang langsung memengaruhi biaya dan desain pipeline: storage class yang menentukan harga simpan per gigabyte, lifecycle policy yang memindahkan dan menghapus object secara otomatis, serta model konsistensi yang menentukan perilaku pipeline setelah penulisan. Chapter ini membedah ketiganya lewat simulasi yang berjalan lokal. Seluruh demo memakai kamus Python murni sebagai pengganti layanan sungguhan, sehingga kita bisa mengamati perilakunya tanpa akun cloud.

Bucket, Key, dan Prefix #

Bucket adalah wadah paling atas milik satu proyek atau satu lingkungan; di Amazon S3 namanya harus unik secara global di seluruh region penyedia. Di dalam bucket, setiap object dikenal lewat key — string datar tanpa struktur direktori sungguhan. Tiga penyedia besar memakai pola URI yang serupa untuk merujuk object:

LayananContoh URI
Amazon S3s3://data-toko/raw/pesanan/2026-10-19.parquet
Google Cloud Storagegs://data-toko/raw/pesanan/2026-10-19.parquet
Azure Blob Storageabfs://[email protected]/raw/pesanan/2026-10-19.parquet

Karakter / pada key hanyalah pemisah yang enak dibaca; layanan menampilkan ilusi folder hanya sebagai kenyamanan visual, sebagaimana sudah dibahas pada chapter Object Storage di section Data Lake dan Lakehouse. Operasi listing memakai prefix — filter startswith pada string key — sehingga konvensi penamaan seperti data/toko/2026/raw/pesanan/ sekaligus menjadi cara memilih subset data secara murah.

Storage Class: Menukar Harga dengan Waktu Akses #

Penyedia menawarkan beberapa storage class dengan kombinasi harga simpan dan waktu akses yang berbeda. Daftar kelas Amazon S3 berikut menggambarkan pola yang juga berlaku di penyedia lain:

Storage classWaktu aksesSyarat minimalHarga simpan relatif
Standardmilidetiktidak ada1× (basis)
Standard-IAmilidetikobjek 128 KB, retensi 30 harisekitar 0,5×
Glacier Instant Retrievalmilidetikretensi 90 harisekitar 0,2×
Glacier Flexible Retrievalmenit hingga jamretensi 90 harisekitar 0,15×
Deep Archivesekitar 12 jamretensi 180 haridi bawah 0,05×

Harga per gigabyte turun drastis di kelas dingin, tetapi dua biaya lain naik: retrieval (mengambil data dari Glacier dikenai biaya per GB dan tidak instan) dan penalti penghapusan awal bila object dihapus sebelum retensi minimal. Pedoman praktisnya: data yang dibaca ulang setiap minggu tetap di kelas panas, arsip yang hanya dibaca saat audit tahunan boleh turun ke Glacier atau Deep Archive. Rincian resmi setiap kelas tersedia di https://docs.aws.amazon.com/AmazonS3/latest/userguide/storage-class-intro.html .

Lifecycle Policy: Transisi dan Kedaluwarsa Otomatis #

Lifecycle policy adalah aturan yang menempel pada bucket: setelah object berumur N hari, layanan memindahkannya ke storage class lain atau menghapusnya. Aturan bisa difilter per prefix, sehingga data klik dan data pesanan TokoKita bisa punya jalur retensi berbeda dalam satu bucket. Simulasi berikut menerapkan tiga aturan pada key yang sudah kita kenal:

python
"""Simulasi lifecycle policy dan storage class object storage TokoKita.

Key disimpan sebagai string datar di satu kamus supaya terlihat jelas bahwa
"folder" hanyalah ilusi dari karakter '/' di dalam key.
"""
from datetime import date

HARI_INI = date(2026, 10, 20)

# Harga per GB-bulan (ilustrasi, basis S3 Standard USD 0.023/GB).
HARGA = {"STANDARD": 0.023, "STANDARD_IA": 0.0125,
         "GLACIER_IR": 0.004, "GLACIER": 0.0036}

# key datar -> (ukuran GB, tanggal dibuat).
OBJEK = {
    "data/toko/2026/raw/pesanan/2026-04-10.parquet": (24.0, date(2026, 4, 10)),
    "data/toko/2026/raw/pesanan/2026-07-18.parquet": (22.0, date(2026, 7, 18)),
    "data/toko/2026/raw/pesanan/2026-08-30.parquet": (25.0, date(2026, 8, 30)),
    "data/toko/2026/raw/pesanan/2026-09-12.parquet": (23.0, date(2026, 9, 12)),
    "data/toko/2026/raw/pesanan/2026-10-01.parquet": (24.0, date(2026, 10, 1)),
    "data/toko/2026/raw/pesanan/2026-10-19.parquet": (24.0, date(2026, 10, 19)),
    "data/toko/2026/raw/klik/2026-04-20.parquet":    (70.0, date(2026, 4, 20)),
    "data/toko/2026/raw/klik/2026-08-20.parquet":    (76.0, date(2026, 8, 20)),
    "data/toko/2026/raw/klik/2026-09-20.parquet":    (79.0, date(2026, 9, 20)),
    "data/toko/2026/raw/klik/2026-10-20.parquet":    (84.0, date(2026, 10, 20)),
    "data/toko/2026/curated/penjualan.parquet":      (120.0, date(2026, 10, 15)),
    "data/toko/2026/laporan/gmv_bulanan.csv":        (0.2, date(2026, 10, 17)),
    "log/etl/2026-10-20/jalankan.txt":               (0.01, date(2026, 10, 20)),
    "data/toko/2025/raw/pesanan/2025-03-10.parquet": (22.0, date(2025, 3, 10)),
}

# Lifecycle policy: aturan pertama yang prefix-nya cocok dengan key yang dipakai.
# Setiap aturan: (setelah_hari, aksi) dengan aksi = storage class atau "HAPUS".
POLICY = [
    ("data/toko/2026/raw/klik/",      [(30, "GLACIER_IR")]),
    ("data/toko/2026/raw/pesanan/",   [(30, "STANDARD_IA"), (90, "GLACIER"), (365, "HAPUS")]),
    ("data/toko/",                    [(365, "HAPUS")]),
]

def daftar_prefix(prefix: str) -> list:
    return [k for k in sorted(OBJEK) if k.startswith(prefix)]

def umur(kunci: str) -> int:
    return (HARI_INI - OBJEK[kunci][1]).days

def terapkan(kunci: str) -> str:
    """Kembalikan storage class akhir, atau 'HAPUS' bila kedaluwarsa."""
    for prefix, aturan in POLICY:
        if kunci.startswith(prefix):
            for hari, aksi in aturan:
                if aksi == "HAPUS" and umur(kunci) >= hari:
                    return "HAPUS"
            kelas = "STANDARD"
            for hari, aksi in aturan:
                if aksi != "HAPUS" and umur(kunci) >= hari:
                    kelas = aksi
            return kelas
    return "STANDARD"

print("Semua key (namespace datar):")
for kunci in sorted(OBJEK):
    print(" ", kunci)
print()
print("Prefix 'data/toko/2026/raw/pesanan/' :", len(daftar_prefix("data/toko/2026/raw/pesanan/")), "objek")
print("Prefix 'data/toko/2026/'             :", len(daftar_prefix("data/toko/2026/")), "objek")
print("Prefix 'log/'                        :", len(daftar_prefix("log/")), "objek")
print()
print("Lifecycle policy per {:}:".format(HARI_INI.isoformat()))
print()
print("{:<46}{:>5}  {}".format("Key", "Umur", "Hasil"))
hasil = {}
for kunci in sorted(OBJEK):
    hasil[kunci] = terapkan(kunci)
    print("{:<46}{:>5}  {}".format(kunci, umur(kunci), hasil[kunci]))

sebelum = sum(ukuran * HARGA["STANDARD"] for ukuran, _ in OBJEK.values())
sesudah = sum(ukuran * HARGA[hasil[k]]
              for k, (ukuran, _) in OBJEK.items() if hasil[k] != "HAPUS")
print()
print("Biaya simpan sebulan (semua STANDARD vs setelah policy):")
print("  Sebelum : ${:.2f}".format(sebelum))
print("  Sesudah : ${:.2f}".format(sesudah))
print("  Hemat   : {:.1f}%".format((1 - sesudah / sebelum) * 100))

Output yang Dihasilkan:

text
Semua key (namespace datar):
  data/toko/2025/raw/pesanan/2025-03-10.parquet
  data/toko/2026/curated/penjualan.parquet
  data/toko/2026/laporan/gmv_bulanan.csv
  data/toko/2026/raw/klik/2026-04-20.parquet
  data/toko/2026/raw/klik/2026-08-20.parquet
  data/toko/2026/raw/klik/2026-09-20.parquet
  data/toko/2026/raw/klik/2026-10-20.parquet
  data/toko/2026/raw/pesanan/2026-04-10.parquet
  data/toko/2026/raw/pesanan/2026-07-18.parquet
  data/toko/2026/raw/pesanan/2026-08-30.parquet
  data/toko/2026/raw/pesanan/2026-09-12.parquet
  data/toko/2026/raw/pesanan/2026-10-01.parquet
  data/toko/2026/raw/pesanan/2026-10-19.parquet
  log/etl/2026-10-20/jalankan.txt

Prefix 'data/toko/2026/raw/pesanan/' : 6 objek
Prefix 'data/toko/2026/'             : 12 objek
Prefix 'log/'                        : 1 objek

Lifecycle policy per 2026-10-20:

Key                                            Umur  Hasil
data/toko/2025/raw/pesanan/2025-03-10.parquet   589  HAPUS
data/toko/2026/curated/penjualan.parquet          5  STANDARD
data/toko/2026/laporan/gmv_bulanan.csv            3  STANDARD
data/toko/2026/raw/klik/2026-04-20.parquet      183  GLACIER_IR
data/toko/2026/raw/klik/2026-08-20.parquet       61  GLACIER_IR
data/toko/2026/raw/klik/2026-09-20.parquet       30  GLACIER_IR
data/toko/2026/raw/klik/2026-10-20.parquet        0  STANDARD
data/toko/2026/raw/pesanan/2026-04-10.parquet   193  GLACIER
data/toko/2026/raw/pesanan/2026-07-18.parquet    94  GLACIER
data/toko/2026/raw/pesanan/2026-08-30.parquet    51  STANDARD_IA
data/toko/2026/raw/pesanan/2026-09-12.parquet    38  STANDARD_IA
data/toko/2026/raw/pesanan/2026-10-01.parquet    19  STANDARD
data/toko/2026/raw/pesanan/2026-10-19.parquet     1  STANDARD
log/etl/2026-10-20/jalankan.txt                   0  STANDARD

Biaya simpan sebulan (semua STANDARD vs setelah policy):
  Sebelum : $13.64
  Sesudah : $7.47
  Hemat   : 45.3%

Mari kita telaah baris-baris kode di atas:

  1. Baris 11-12 (HARGA): harga per GB-bulan untuk empat kelas — angka ilustratif dengan basis Standard USD 0,023/GB.
  2. Baris 15-30 (OBJEK): key datar dipetakan ke ukuran dan tanggal dibuat; tidak ada struktur direktori, hanya string dalam satu kamus.
  3. Baris 34-38 (POLICY): tiga grup aturan dengan filter prefix — klik turun ke Glacier Instant Retrieval setelah 30 hari, pesanan melalui tiga tahap, sisanya mengikuti aturan umum data/toko/.
  4. Baris 46-58 (terapkan): aturan HAPUS diperiksa lebih dulu; bila umur belum menyentuh ambang hapus, storage class diambil dari transisi dengan ambang terbesar yang sudah terlampaui.
  5. Baris 76-78 (sebelum, sesudah): biaya sebelum menghitung semua object sebagai Standard; biaya sesudah memakai kelas hasil policy dan membuang object yang dihapus.

Bacaan hasilnya: data klik berumur 30 hari atau lebih turun ke Glacier Instant Retrieval sehingga analitik mingguan tetap bisa membacanya dalam hitungan milidetik; pesanan lama turun bertahap ke Glacier; dan arsip 2025 berumur 589 hari terhapus oleh aturan umum. Total tagihan simpan menyusut 45,3% tanpa satu pun object dipindahkan manual.

Konsistensi Baca-Tulis #

Sejak Desember 2020 Amazon S3 memberikan strong consistency: hasil PUT dan DELETE langsung terlihat oleh permintaan baca berikutnya tanpa masa tunggu replikasi. Google Cloud Storage juga konsisten kuat untuk penulisan object. Bagi pipeline, perilaku ini menyederhanakan orchestration — langkah extract yang menulis raw/pesanan/2026-10-20.parquet bisa langsung diikuti langkah transform yang membacanya, tanpa perulangan menunggu object muncul. Catatan pentingnya: konsistensi berlaku per object, bukan untuk operasi LIST sebagai katalog tabel — pencatatan tabel yang andal tetap membutuhkan table format seperti yang dibahas pada chapter Apache Iceberg di section Data Lake dan Lakehouse.

Dengan mekanika bucket, storage class, dan lifecycle yang dikuasai, TokoKita kini punya lapisan penyimpanan yang murah dan terkelola otomatis. Pertanyaan berikutnya: bagaimana mengolah data tersebut tanpa mengikat proses komputasi ke satu mesin penyimpanan — itu bahasan chapter Compute dan Storage.