Skip to content
Blog DevOps

Bagaimana Saya Memberi Dua Mesin Satu Sumber Kebenaran yang Sama-Sama Dipercaya

By Romi Fadli, S.Kom. 24 min read Storage & Data
On this page
A laptop and a Colab runtime reaching one bucket: the laptop through a mount, drawn as a solid line, and Colab through the S3 API, drawn as a dashed line

Pendahuluan

A. Laptop yang menyiapkan, dan GPU yang dipinjam

Laptop yang saya pakai bekerja punya RTX 3050 Ti dengan video memory 4 GB. Itu cukup untuk desktop, untuk encoder video, untuk model yang kecil. Tidak cukup untuk SDXL yang membuat gambar beresolusi penuh, maupun untuk model text-to-speech yang ingin menguasai hampir seluruh T4 sendirian. Jadi separuh berat dari beberapa project saya berjalan di tempat lain: runtime Google Colab dengan T4 dan sekitar 15 GB VRAM, dipinjam selama sesinya bertahan.

Memindahkan komputasinya bagian yang gampang. Yang tak bisa dipindahkan seenaknya adalah datanya. Laptop tempat pekerjaan disiapkan — naskah dipotong jadi adegan, prompt ditulis, gambar rujukan dipilih — dan runtime tempat persiapan itu diubah jadi gambar dan audio. Hasilnya lalu kembali ke laptop untuk ditinjau, dan kadang dikirim balik untuk dikerjakan ulang. File yang sama dibaca dan ditulis dari dua mesin yang tak pernah berbicara langsung satu sama lain, dan jarang menyala bersamaan. Runtime-nya pun tak bertahan: ia dimatikan ketika sesinya habis, lalu yang baru dinyalakan dari nol, tanpa ingatan atas apa pun yang pernah ditulis di disknya sendiri.

Tantangannya: memberi dua mesin satu sumber kebenaran yang bisa dipercaya keduanya.

Kata yang memikul beban di kalimat itu adalah dipercaya. Menaruh file di tempat yang bisa dijangkau dua mesin itu persoalan yang sudah selesai; produknya ada selusin. Yang ternyata sulit adalah tahu, di sisi mana pun, bahwa yang dibaca memang yang ditulis — bahwa file yang tampak utuh memang utuh, bahwa upload yang melapor sukses memang mendarat, bahwa folder yang tampak seperti penyimpanan bersama bukan folder lokal yang akan lenyap begitu runtime berhenti.

Gampang dirumuskan, canggung dibangun. Hampir semua hambatan di bawah berbentuk sama: ada yang melapor sukses, dan suksesnya palsu. Bentuk kegagalan seperti ini lebih mahal ketimbang crash, sebab crash berhenti di tempatnya dan menunjuk sebabnya, sedangkan sukses palsu berjalan terus sampai jauh ke hilir.

B. Tiga pembaca, dua mesin

Ada gunanya persis soal siapa saja yang menyentuh penyimpanan ini, sebab tiap pembaca gagal dengan caranya sendiri, dan perbaikan untuk satu pembaca tak otomatis melindungi pembaca lainnya.

Di laptop, pekerjaan persiapan dikerjakan oleh script dan oleh agent AI yang bekerja di dalam source tree. Keduanya membaca dan menulis penyimpanan itu sebagai folder lokal. Masih di laptop, sebuah antarmuka web berjalan di browser, dan membuka project di sana berarti mengambil seluruh record-nya langsung dari penyimpanan. Di Colab, notebook worker membaca pekerjaan yang sudah disiapkan, menjalankan modelnya, lalu menulis gambar dan audio kembali.

Tiga pembaca, dua mesin, satu bucket. Bucket itu tinggal di server MinIO yang saya jalankan sendiri, dan bisa dijangkau dari mana saja. MinIO adalah object store: ia menyimpan file sebagai objek bernama di dalam bucket, lalu menyajikannya lewat protokol S3 — antarmuka HTTP yang dulu diterbitkan Amazon untuk layanan storage-nya dan kini dipakai kebanyakan object store. Pilihan itu datang belakangan; project-project ini dimulai di Google Drive, dan hambatan pertama menjelaskan kenapa mereka pergi dari sana.

Satu catatan cakupan. Ceritanya sebagian besar dituturkan lewat satu project — pipeline yang mengubah bab tertulis jadi video slideshow bernarasi — sebab riwayatnya paling lengkap: tiap perubahan di-commit bersama gejalanya, penyebabnya, dan penalaran yang mengisolasinya. Dua project lain, pengubah PDF ke Markdown dan editor gambar berbasis AI, berbagi riwayat awal yang sama dan muncul di tempat mereka menambah sesuatu. Ketiganya dimulai dari keputusan yang sama, dan ketiganya menabrak batas yang sama.

C. Sepuluh hambatan yang menghalangi

Google Drive kehabisan ruang dan kecepatan. Drive penyimpanan pertama yang paling jelas, sebab Colab me-mount-nya cukup dengan satu baris. Ia berhenti memadai begitu penyimpanannya harus memuat bobot model bergiga-giga dan ratusan gambar hasil generate, dan begitu saya sudah punya server S3 sendiri yang sanggup menampung semuanya di satu tempat.

Notebook-nya ditulis terhadap path, bukan terhadap API. Tiap fungsi membaca dan menulis file di bawah satu folder. Menulis ulang semuanya terhadap object storage adalah perbaikan yang jujur sekaligus diff yang besar; me-mount bucket supaya tampak seperti folder, persis seperti Drive, adalah yang kecil. Saya memilih yang kecil, dan tiga hambatan berikutnya adalah ongkosnya.

Mount yang mati menjawab dengan error yang keliru. Ketika proses yang melayani sebuah mount mati, foldernya tidak lenyap. Ia tetap ada, dan menggagalkan tiap operasi dengan error yang tampak seperti kerusakan disk ketimbang "tidak ada di sini".

403 yang bukan soal kunci. Kredensialnya benar, konfigurasinya identik di kedua mesin, dan server tetap menolak tiap request dengan error signature.

Exit code nol yang berarti "diantrekan", bukan "sudah sampai". Menyalin ke dalam mount langsung melapor sukses, file-file tampak lengkap, dan upload-nya dibatalkan ketika mount dilepas.

Mesin pinjaman punya aturannya sendiri. Colab memperingatkan saya soal mount itu lalu menolak menjalankan runtime-nya. Lapisan yang menyebabkan tiga hambatan sebelumnya harus disingkirkan seluruhnya.

Orang yang men-debug perlu melihat apa yang sedang ditulis worker, selagi ia menulis. Di laptop, mount sejenis tetap dipertahankan — dengan alasan yang sama sekali lain.

Dua salinan kebenaran akan saling menjauh. Folder project di repository dan project yang sama di bucket sudah menyimpang ketika akhirnya ada yang memeriksa.

Pembacaan yang jarang gagal jadi sering gagal ketika satu tindakan berarti 326 pembacaan. Membuka project di browser berarti ratusan request, dan satu saja yang gagal menggagalkan seluruh pembukaan.

Retry yang tak pernah mengulang. Request yang terputus di tengah kembali sebagai file yang sukses tapi lebih pendek, jadi retry yang membungkusnya tak punya apa pun untuk ditangkap.

Urutannya mengikuti pembacanya. Hambatan pertama menjelaskan kenapa penyimpanannya berganti sama sekali. Lima berikutnya milik sisi Colab, dan diakhiri dengan lapisan itu disingkirkan. Dua sesudahnya milik laptop, dan dua terakhir milik browser. Tujuh dari sepuluh adalah sukses palsu, dan itulah benang yang menjahit semuanya. Urutan per pembaca ini sengaja dipilih ketimbang urutan waktu, sebab di dalam riwayatnya ketiga sisi saling bersilangan dan membingungkan bila diceritakan menurut tanggal.

Metode

A. Meninggalkan Drive

Inilah jawaban atas hambatan pertama. Drive tempat project-project ini bermula, sebab di Colab ongkosnya satu baris:

python
drive.mount("/content/drive")
DRIVE_ROOT = Path("/content/drive/MyDrive/colabs/<app>")
MODELS_DRIVE = Path("/content/drive/MyDrive/colabs/models")

Editor gambarnya masih bekerja dengan cara ini, dan pengubah PDF-nya dulu begitu di bentuk pertamanya. Sebagai permulaan itu wajar. Ia berhenti wajar ketika penyimpanannya harus memuat beberapa giga bobot model ditambah ratusan gambar hasil generate per project, sebab kuota Drive dan throughput-nya dari runtime Colab sama-sama jadi leher botol.

Yang memutuskan: penyimpanan yang lebih baik sudah ada. Saya menjalankan server MinIO untuk pekerjaan lain, dan ia berbicara S3 — artinya bisa dijangkau dari notebook, dari laptop, dan dari browser, dengan satu perangkat alat dan tanpa akun Google di tengahnya. Menyatukan semuanya di sana justru mengurangi satu ketergantungan, bukan menambah.

B. Me-mount bucket seperti dulu Drive di-mount

Ini menjawab hambatan kedua. Notebook-nya mengandaikan folder, jadi migrasinya memberinya folder: bucket di-mount lewat rclone — tool yang menampilkan cloud storage sebagai folder biasa — dan FUSE, di path yang bisa diperlakukan notebook persis seperti Drive dulu. Code sinkronisasinya tak disentuh sama sekali — cuma path tujuannya yang berganti.

Rencana itu bersandar pada satu andaian yang pantas disebut. Drive di-mount di Colab lewat integrasi milik Google sendiri, dan itu tak berkata apa-apa soal boleh tidaknya filesystem FUSE generik di dalam container. Jadi sebuah probe dijalankan sebelum apa pun dibangun di atasnya: periksa /dev/fuse, pasang rclone, validasi kredensial, mount, lalu jalankan pola I/O yang persis dipakai aplikasi dan pastikan objeknya benar-benar muncul di server. Butuh tiga versi sampai lolos — kegagalan pertama karena flag daemon, yang kedua karena paket fuse3 yang belum terpasang.

Mount itu juga membawa satu bentuk kegagalan yang harus dijaga code sinkronisasinya:

python
if not is_mounted(S3_MOUNT, deep=False):
    # mount yang mati meninggalkan folder biasa: rsync ke sana
    # sukses, lalu datanya lenyap saat runtime berhenti
    return
rsync(proj_dir(name), PROJECTS_REMOTE / name, delete=True)

Kalau rclone mati, mountpoint kembali jadi folder biasa di disk runtime. Menyalin ke sana sukses. Tak ada yang sampai ke server, dan salinannya lenyap bersama runtime. Itulah sukses palsu yang pertama, tertangkap sebelum sempat terjadi.

C. Error yang tampak seperti disk rusak

Ini menjawab hambatan ketiga. Jalan sungguhan pertama sesudah migrasi crash pada baris yang menyiapkan mount:

python
OSError: [Errno 5] Input/output error: '/content/s3'

Probe sebelumnya meninggalkan mount yang basi. Proses rclone yang melayaninya sudah mati, tapi kernel masih mengarahkan path itu ke FUSE, jadi tiap system call padanya mengembalikan EIO, bukan ENOENT seperti folder yang memang tak ada. Perbaikannya mengenali tiga keadaan, bukan dua — belum di-mount, hidup, dan basi — sebab masing-masing butuh tindakan berbeda, dan mount basi wajib dilepas paksa dulu sebelum mount baru bisa dipasang di atasnya.

Yang sebenarnya menggagalkan jalan itu lebih memberi pelajaran ketimbang mount basinya sendiri:

python
mp.mkdir(parents=True, exist_ok=True)
if any(mp.iterdir()):
    print(...)   # "folder tidak kosong" — murni informasi

Peringatan kosmetik, bukan prasyarat, ditaruh tanpa pelindung — sehingga membaca isi mount yang mati melempar error, dan peringatan itu membatalkan remount tepat ketika remount paling dibutuhkan. Versi perbaikannya membuat peringatan itu tak sanggup gagal:

python
def warn_if_not_empty(mp) -> None:
    try:
        if any(mp.iterdir()):
            print(...)
    except OSError:
        pass

D. Error signature dengan kunci yang benar

Ini menjawab hambatan keempat, yang datang pada jalan kedua. Tiap listing ditolak dengan 403 SignatureDoesNotMatch selama enam puluh detik, lalu notebook menyerah dengan "mount tidak siap dalam 60 detik" — pesan yang menunjuk arah keliru, sementara sebab sebenarnya tercetak di log tepat di atasnya.

Dua langkah harus terjadi. Pertama, berhenti menebak — dan itu berarti mengembalikan pemeriksaan yang sengaja saya buang: cek kredensial sebelum me-mount. Saya membuangnya demi menghemat satu round trip. Hemat satu detik, bayar enam puluh, ditambah pesan yang menyesatkan.

python
rc, out = run(["rclone", "lsd", f"{REMOTE_NAME}:{BUCKET}",
               "--low-level-retries", "1", "--retries", "1"],
              env=env, timeout=timeout)
if rc != 0:
    raise MountError(explain_s3_error(out))

Retry ditekan ke satu, jadi kegagalan kredensial muncul dalam sekitar dua detik, dipetakan ke sebabnya, bukan diulang-ulang sampai timeout.

Kedua, menemukan sebabnya. Dugaan yang paling jelas — Colab membaca konfigurasi lama — masuk akal, sebab Colab tidak menimpa file bernama sama yang diunggah ulang; ia membuat config (1).json. Karena itu kedua mesin mencetak sidik jari kredensialnya, delapan karakter hex dari sebuah hash yang tak bisa dikembalikan jadi kuncinya:

python
raw = (cfg["access_key"] + "\x00" + cfg["secret_key"]).encode("utf-8")
return hashlib.sha256(raw).hexdigest()[:8]

Sidik jarinya cocok, jadi dugaan itu gugur. 403-nya lalu berhasil direproduksi di laptop, artinya bisa dipersempit tanpa bolak-balik ke Colab lagi:

bash
# tiap variasi dicoba di laptop, server sama, kunci sama
provider Minio / Other / Ceph      -> 403
region empty / us-east-1 / unset   -> 403
explicit :443 on the endpoint      -> 403
credentials from a config file     -> 403
V2_AUTH=true                       -> OK

Dan uji silang dengan klien lain membalik kesimpulan yang tampak paling jelas:

bash
boto3  : SigV4 OK ,  SigV2 403
rclone : SigV4 403,  SigV2 OK

Server menerima kedua versi signature. Tiap klien cuma berhasil dengan satu. Itulah ciri reverse proxy di depan server yang mengubah bagian yang ikut ditandatangani selama request lewat — dan itu berarti perbaikan yang benar adalah bernegosiasi, bukan mematok: coba V4, turun ke V2 cuma bila errornya soal signature, dan biarkan error lain lewat apa adanya.

E. Sukses yang berarti "diantrekan"

Ini menjawab hambatan kelima. Bobot model di-cache di bucket supaya runtime baru bisa menyalinnya ketimbang mengunduh dari nol. Satu jalan berikutnya gagal memuat model — cache-nya melapor ditemukan, file-filenya tidak ada. Memeriksa bucket memperlihatkan cache-nya terpotong: satu model cuma memuat sekitar 0,25 GB dari kira-kira 7 GB-nya.

Pemeriksaan yang meloloskannya melihat satu file saja:

python
if (remote_dir / marker).exists():
    rsync(remote_dir, local_dir)
    return local_dir

File penanda itu kebetulan file kecil yang sempat selesai terunggah. Sebabnya berhasil direproduksi di laptop: menyalin ke mount FUSE mengembalikan exit code nol seketika, dan file-filenya tampak lengkap di mountpoint, sementara rclone baru mengantrekan upload-nya di cache. Ketika mount dilepas, antreannya dibatalkan — vfs cache: upload canceled — dan tak ada yang memberi kabar kepada program yang melakukan penyalinan.

Menguji perbaikannya memunculkan persoalan kedua, dan yang ini murni salah konfigurasi saya sendiri. Upload di atas 5 MB ditolak dengan 413 yang badannya HTML, bukan XML milik S3: batas ukuran upload di sisi storage disetel terlalu rendah, dan belum pernah ada yang cukup besar untuk menyentuhnya sampai gambar hasil generate datang. Menaikkan batasnya menyelesaikannya. Yang membuat salah konfigurasi biasa itu jadi mahal adalah cara ia gagal. Tiap gambar hasil generate di atas 5 MB gagal terunggah dalam diam, dengan penyalinan tetap mengembalikan nol dan antarmuka tetap menampilkan gambarnya dari disk runtime.

Cache model di bucket akhirnya dibuang seluruhnya. Pengukuran yang memutuskan: dengan 2–4 MB/s ke bucket, mengunggah sepuluh giga bobot jauh lebih lama ketimbang mengunduhnya ulang. Yang menggantikan pemeriksaan file penanda adalah prinsip yang bertahan lebih lama darinya — periksa file yang akan dibuka, bukan file yang mewakilinya.

F. Aturan mesin yang dipinjam

Ini menjawab hambatan keenam. Colab memperingatkan saya soal menjalankan rclone di dalam runtime, lalu menolak menjalankannya. Mesin pinjaman dipinjam dengan syarat pemiliknya, dan me-mount filesystem ternyata berada di luar syarat itu.

Di situlah mount di sisi Colab berakhir. Worker ditulis ulang supaya berbicara S3 langsung lewat boto3, library Python untuk berkomunikasi dengan S3, dan seluruh tumpukan mount ikut pergi — dua puluh satu fungsi, dan bagian cell persiapan yang sempat tumbuh jadi 40 KB menyusut ke 29 KB.

python
S3 = boto3.client(
    "s3",
    endpoint_url=CFG["endpoint"],
    config=BotoConfig(
        s3={"addressing_style": CFG["addressing_style"]},
        signature_version="s3v4",
        retries={"max_attempts": 5, "mode": "standard"},
    ),
)

Yang mencolok adalah berapa banyak yang ikut pergi bersamanya. Negosiasi V4 ke V2 ada semata karena rclone; boto3 sejak awal bekerja dengan V4 terhadap server yang sama. Mount basi yang menjawab EIO mustahil terjadi tanpa mount. Upload yang dibatalkan saat mount dilepas mustahil terjadi tanpa cache di antara program dan jaringan. Tiga hambatan tidak diperbaiki satu per satu — ketiganya lenyap bersamaan, sebab ketiganya tinggal di satu lapisan.

Peringatan itu pemicunya. Pelajarannya tetap layak dipetik seandainya peringatan itu tak pernah datang: abstraksi yang membuat jaringan tampak seperti disk menepati janji disk di antarmukanya dan mengingkarinya di perilakunya.

G. Jendela, bukan alur kerja

Ini menjawab hambatan ketujuh, dan sekilas tampak seperti kontradiksi. Laptop tetap memakai mount rclone atas bucket yang sama — malah dipasang sepuluh hari sesudah Colab membuangnya.

ini
ExecStart=/usr/bin/rclone mount minio:projects /home/<user>/cloud/s3.example-client.co.id \
    --vfs-cache-mode=writes \
    --dir-cache-time=30s \
    --poll-interval=1m
Restart=on-failure

Alasannya terletak pada untuk apa mount itu ada. Di laptop, ia sebuah jendela. Ia membuat bucket jarak jauh tampak sebagai path biasa, sehingga project bisa diisi dan dikosongkan dengan alat biasa, dan — bagian yang paling berarti — sehingga agent AI yang bekerja di source tree bisa langsung melihat apa yang baru saja ditulis worker Colab, selagi worker itu di-debug. Ketika sesuatu di runtime berulah, buktinya sudah tergeletak di laptop, terbaca dengan alat yang sama seperti file lokal mana pun.

Setelannya mengikuti peran itu. --dir-cache-time=30s dibuat pendek supaya objek baru yang ditulis worker kelihatan dalam setengah menit. Mount-nya berupa user service yang menyalakan ulang dirinya sendiri, di mesin milik saya yang takkan direbut di tengah sesi. Bentuk kegagalan yang menenggelamkan mount di Colab terikat pada runtime yang terus-menerus mati dan hidup; di laptop yang menyala berminggu-minggu dan melepas mount dengan rapi, bentuk kegagalan itu tak muncul.

Alat yang sama, ditolak di satu mesin dan dipilih di mesin lain, dan tak satu pun keputusannya keliru. Bedanya ada di antara mount sebagai jalur yang digantungi pekerjaan, dan mount sebagai cara menontonnya.

H. Menghapus salinan kedua

Ini menjawab hambatan kedelapan. Repository masih menyimpan folder project dari sebelum migrasi, dan bucket menyimpan project yang sama. Dua salinan, dan keduanya sudah menyimpang: bab ketiga dan seterusnya sudah ditulis ulang di bucket sementara repository masih memegang teks lama.

Perbaikannya: cukup satu salinan. Sumber kebenaran pindah ke mount di laptop, dan folder di repository dihapus serta tak boleh dibuat lagi. Penalarannya sederhana sekaligus mutlak: salinan lokal langsung basi begitu worker menulis ke bucket, dan salinan yang mungkin basi lebih buruk ketimbang tanpa salinan, sebab ia akan dibaca.

Yang pantas dicatat adalah apa yang terjadi sesaat sebelum penghapusan. Folder repository ternyata memuat 156 file yang tak ada di tempat lain dan tak dilacak git. Itu diangkat lebih dulu sebelum apa pun dihapus, dikonfirmasi memang tak dipakai, dan arsipnya disimpan sebagai cadangan terakhir. Menetapkan satu sumber kebenaran adalah tindakan yang merusak, dan ia pantas memeriksa dulu apa yang hendak dirusaknya.

I. Ketika satu tindakan adalah tiga ratus request

Ini menjawab hambatan kesembilan. Membuka project di browser gagal total dengan checksum mismatch. Datanya sehat — tiap satu dari 326 file JSON cocok dengan checksum yang tersimpan. Pesan milik SDK-nya sendiri menyesatkan: label "expected" dan "received"-nya tertukar, jadi badannya tiba utuh dan header-nya yang tidak. Nilai yang meleset itu tak cocok dengan objek mana pun di project; ia berasal dari respons lain, entah di mana di lapisan proxy.

Alasan kekeliruan langka itu berarti adalah aritmetika. Membuka project berarti mengambil seluruh record sekaligus, dan satu kegagalan menggagalkan seluruh pembukaan:

python
>>> 1 - (1 - 0.003) ** 326
0.6245

Dengan tingkat kegagalan 0,3% per request, satu kali membuka project lebih sering gagal ketimbang berhasil. Kesalahannya tak bisa direproduksi — ratusan request lewat HTTP/1.1 maupun HTTP/2 menghasilkan nol kegagalan — jadi perbaikannya menoleransi, bukan mengejar: satu pembungkus retry di sekeliling satu fungsi yang dilewati semua pembacaan, tiga percobaan dengan jeda yang makin panjang.

J. Retry yang tak pernah mengulang

Ini menjawab hambatan kesepuluh, seminggu kemudian. Membuka project gagal lagi, kali ini dengan error parse JSON di posisi 3122. File-nya 4.698 byte, valid, dan ukurannya identik di server maupun di mount laptop. Browser menerima 66% darinya.

Retry dari perbaikan sebelumnya sudah terpasang, dan ia tak pernah menyala. Ia mengulang bila fungsi yang dibungkusnya melempar, sedangkan transformToString() tidak melempar ketika koneksi putus di tengah — ia mengembalikan apa yang sempat terbaca, sebagai sukses. Parse-nya gagal belakangan, di luar pembungkus, di tempat tak ada yang bisa mengulangnya. Jadi pemeriksaannya dipindah ke dalam:

typescript
function assertComplete(text: string, contentLength?: number): void {
  if (contentLength == null) return
  const got = new TextEncoder().encode(text).byteLength
  if (got < contentLength) throw new Error(...)
}

Satu rincian menentukan bekerja tidaknya ini sama sekali. ContentLength menghitung byte, sedangkan String.length menghitung karakter UTF-16. Untuk teks mana pun yang memuat karakter non-ASCII, keduanya berbeda — jadi membandingkan dengan text.length akan menuduh file yang utuh sebagai terpotong lalu mengulangnya tiga kali sebelum gagal. Pembandingnya wajib dalam byte. Ujinya dibuktikan lewat sabotase: mencabut pemeriksaan panjang menggagalkan uji pemotongan, dan menukar byte dengan karakter menggagalkan uji non-ASCII.

Hasil dan Pembahasan

A. Apa yang dijumlahkan kesepuluh keputusan itu

Penyimpanannya kini memegang satu salinan tiap project, dijangkau tiga jalan: worker Colab lewat API S3, browser lewat API S3 dengan pemeriksaan atas tiap badan respons yang diterimanya, dan laptop lewat mount yang ada untuk dijadikan jendela.

KeputusanYang dibeliAlternatif yang ditolak
MinIO sendiri lewat S3satu penyimpanan terjangkau dari notebook, laptop, dan browserGoogle Drive — kuota dan throughput
probe sebelum me-mountandaiannya diuji sebelum ada yang dibangun di atasnyapercaya bahwa mount Drive membuktikan FUSE jalan
tiga keadaan mountmount basi dilepas, bukan ditumpukter-mount / tidak — EIO tak terjelaskan
cek kredensial lebih dulusekitar 2 detik dan sebab yang disebut namanya60 detik dan pesan timeout
negosiasi signatureV4 bila jalan, V2 cuma pada error signaturemematok satu versi
periksa file yang akan dibukapemotongan tertangkap saat dimuatsatu file penanda mewakili seluruhnya
boto3 langsung di Colabtiga hambatan lenyap sekaligusmempertahankan mount — ditolak Colab
mount laptop, cache folder 30 detikagent melihat keluaran worker sebagai file lokalmengunduh hasil manual untuk memeriksanya
satu salinan, folder repo dihapustak ada yang bisa menyimpangdua salinan disinkronkan lewat disiplin
retry plus cek panjang bytebadan terpotong jadi error yang diulangretry yang cuma melihat exception

B. Bentuk yang mereka bagi

Baca daftar hambatannya dari atas ke bawah, dan kejadian yang sama terus berulang. Penyalinan ke mount yang mati sukses. Penyalinan ke mount yang hidup sukses sebelum datanya berpindah. Cache melaporkan dirinya ada karena satu file kecilnya ada. Pembacaan mengembalikan dua pertiga file sebagai file yang utuh. Upload di atas 5 MB gagal dan penyalinannya tetap mengembalikan nol. Retry duduk mengelilingi pemanggilan yang tak pernah melaporkan kegagalan. Folder lokal tampak seperti project padahal salinan basinya.

Tak satu pun berupa crash. Crash bagian yang gampang — ia berhenti, dan ia menunjuk. Yang menyita waktu adalah sukses yang tidak benar, baru ketahuan di hilir, jauh dari sebabnya. Itulah arti dipercaya di judul dalam praktiknya: tiap pembaca entah memeriksa apa yang diterimanya, entah bergantung pada lapisan yang tak sanggup berbohong kepadanya dengan cara tersebut.

Versi umumnya pantas dinyatakan. Abstraksi yang menyembunyikan jaringan — mount yang tampak seperti disk, konversi string yang tampak seperti pembacaan utuh — paling berbahaya bukan ketika ia gagal, melainkan ketika ia sukses atas nama jaringan, dengan ketidakpastian jaringannya dibuang dari jawabannya.

C. Yang tidak dicakup

Ini penyimpanan untuk project satu orang, dengan satu penulis aktif pada satu waktu untuk record mana pun. Tak ada penguncian, dan tak ada perlindungan terhadap dua mesin yang menulis file yang sama pada saat bersamaan; alur kerjanya memang tak pernah melakukannya. Tim yang berbagi bucket yang sama akan butuh keduanya.

Ini juga tak berkata apa pun soal bagaimana kedua mesin saling memberi perintah. Penyimpanan memegang state-nya; perintah untuk memprosesnya lewat jalur lain, dengan bentuk kegagalannya sendiri — dan tak satu pun jaminan penyimpanan ini ikut berlaku di jalur itu.

Simpulan

GPU 4 GB mendorong pekerjaan berat beberapa project ke runtime pinjaman, dan itu membuat satu pertanyaan tak terhindarkan: di mana datanya tinggal, supaya laptop dan runtime sama-sama bisa bersandar padanya? Jawabannya melewati Google Drive, mount filesystem yang membuat bucket jarak jauh tampak lokal, dan akhirnya akses S3 langsung dari runtime dan browser, dengan mount dipertahankan cuma di laptop sebagai cara menonton.

Hambatan di sepanjang jalan jarang soal menyimpan data. Hambatannya soal memercayainya — exit code nol yang berarti diantrekan, mount basi yang menjawab dengan error disk, respons yang terpotong di 66% dikembalikan sebagai sukses, satu kekeliruan langka yang menggagalkan satu dari dua kali pembukaan ketika pembukaan berarti 326 request. Langkah yang paling berguna adalah menyingkirkan lapisan yang melahirkan tiga di antaranya sekaligus, dan langkah itu dipicu sebuah peringatan, bukan sebuah wawasan.

Yang tersisa adalah aturan yang berlaku jauh melampaui susunan ini: tiap pembaca semestinya memeriksa apa yang diterimanya terhadap apa yang dijanjikan kepadanya, sebab tempat-tempat di mana jaringan dibuat tampak lebih sederhana dari aslinya adalah persis tempat ia bisa melapor sukses atas nama kita.

Istilah

rclone
Tool command-line untuk menyalin, menyinkronkan, dan me-mount cloud storage. Saat di-mount, bucket tampak seperti folder biasa, dan tiap operasi file diterjemahkan jadi request ke server storage.
FUSE
Filesystem in Userspace — mekanisme Linux yang membuat program biasa bisa menampilkan sesuatu, misalnya bucket jarak jauh, sebagai folder biasa. Program yang melayaninya wajib tetap hidup; bila ia mati, foldernya tetap ada tapi berhenti bekerja.
mount basi
Mountpoint yang proses pelayannya sudah mati. Kernel masih mengarahkan path itu ke sana, jadi operasi gagal dengan error input/output ketimbang melaporkan bahwa tak ada apa-apa.
SigV4 / SigV2
Dua versi skema yang dipakai klien S3 untuk menandatangani request. Server menghitung ulang signature-nya dan menolak request bila ada bagian bertanda tangan yang berubah di jalan.
VFS cache
Penyangga lokal milik rclone di antara program dan penyimpanan jarak jauh. Sebuah penulisan bisa selesai ke penyangga ini jauh sebelum sampai ke server.
ContentLength
Ukuran badan respons dalam byte, dikirim server. Membandingkannya dengan yang benar-benar tiba adalah cara paling sederhana mendeteksi pembacaan yang terpotong.
sumber kebenaran
Satu tempat yang salinan datanya berwenang. Salinan lain entah diturunkan darinya, entah sudah usang.

Technologies covered

  • Python
  • boto3
  • rclone
  • Google Colab
  • MinIO
  • S3

About the author

Photo of Romi Fadli

Romi Fadli, S.Kom.

Solution Architect · Backend Engineer · Embedded Systems

In my day job I work as a solution architect. I design the system, lead the team that builds it — quality assurance, backend developers, a UI developer, DevOps and DevSecOps — and write the most critical modules myself, such as a certificate authority, a long-term PDF signer, and the core API of a personnel system.

I work across the whole stack that a running system actually needs. On the hardware side that means PCB design, microcontrollers, and embedded firmware — including RISC-V boards running FreeRTOS and Embassy, speaking MQTT over mutually authenticated TLS on a microcontroller with 64 KB of SRAM.

Most of my day-to-day work is software: Rust backends and API modules built on Clean Architecture, an OAuth2/OpenID Connect authorization server written from scratch, PAdES document signing, and certificate authorities — root CAs, timestamping authorities, and OCSP responders. I also build the frontends that sit on top of them.

Running all of that means infrastructure: Docker Compose stacks, networking, Mikrotik and RADIUS, observability with OpenTelemetry, and a steady habit of writing command-line tools and utilities for Linux when the existing ones do not fit.

Share

Enjoyed this?

Get an email when a new article is published.

Related Articles

A terminal window listing passing checks, connected to a blue storage bucket with a padlock and a green storage bucket without one.

Satu script Python yang membuktikan storage kompatibel S3 siap sebelum aplikasi bergantung padanya, dengan memakai tiap bucket persis seperti client sungguhan dan menangkap kegagalan yang tetap menjawab HTTP 200.

12 min read Read more →