Layanan:

Pindah dari Laravel Forge ke Laravel Cloud: Masalah Nyata yang Perlu Diantisipasi

Pindah dari Laravel Forge ke Laravel Cloud: Masalah Nyata yang Perlu Diantisipasi

Migrasi Forge ke Laravel Cloud pada aplikasi Pinkary membuktikan satu hal: cutover lebih dari sekadar mengubah target deploy. Namun lingkungan baru mengekspos shortcut yang SQLite dan disk lokal tolerir lebih dari setahun. Jadi UUID string hidup di kolom integer. Query thread tidak deterministik. Selanjutnya ratusan panggilan Storage::disk('public') mengabaikan default disk.

Pinkary adalah platform link-in-bio open source (Laravel + Livewire). Awalnya aplikasi ini hidup di droplet DigitalOcean lewat Forge. Lalu database memakai file SQLite; upload disimpan lokal.

Tim inti memindahkannya ke Laravel Cloud pada Juli 2026. Kemudian latihan live stream dan cutover final offline memakan lebih dari lima jam. Berikut rangkuman isu teknis dari tulisan resmi Laravel tentang migrasi Pinkary. Batas antara fakta kasus Pinkary dan pola audit umum tetap kami jaga.

Ringkasan cepat: migrasi Pinkary menekankan schema, query, storage, dan tooling copy data.

Pindah dari Laravel Forge ke Laravel Cloud: Masalah Nyata yang Perlu Diantisipasi

  • Cloud mendorong MySQL terkelola dan bucket object storage; SQLite plus upload lokal tidak lagi layak sebagai system of record pada arsitektur instance tanpa storage persisten antar deploy.
  • Perbedaan tipe, collation, dan ONLY_FULL_GROUP_BY memaksa perbaikan migration hashtag, kolom parent_id/root_id, dan feed query berbasis ROW_NUMBER().
  • Presisi timestamp detik plus urutan kolom fisik membuat test yang mengandalkan urutan implisit gagal—bukan karena MySQL “lebih cepat”, melainkan asumsi test yang rapuh.
  • Abstraksi filesystem tidak gratis di S3: exists() jadi round-trip jaringan; gambar harus di-encode ke stream sebelum Storage::put().
  • Dump SQL generik gagal untuk UUID dan cache serialized; command Artisan khusus dengan verifikasi row count lebih aman untuk aplikasi kecil seperti Pinkary.
  • Forge tetap valid bila Anda butuh kontrol penuh server; Cloud menarik bila runtime dan resource terkelola digabung—pilihan bergantung tim, biaya, dan kebutuhan kontrol.

Konteks Pinkary: Migrasi Forge ke Laravel Cloud Lebih dari Ganti Host

Forge memberi kontrol penuh atas server yang Anda provision. Setup itu cocok untuk Pinkary fase awal. Meski demikian, alasan pindah ke Cloud di kasus ini lebih organisasi: pemilik proyek ingin semua side project di satu platform dengan runtime dan resource terkelola bersama.

Lantaran traffic kecil, cutover sekitar pukul 01.00 tanpa maintenance window formal memungkinkan. Skala kecil juga berarti masalah di post mortem Laravel lebih ke bentuk kode dan schema, bukan beban traffic. Laravel Cloud, menurut narasi migrasi Pinkary, tidak menyediakan storage lokal permanen antar deployment dan restart. Jadi database harus pindah dari file SQLite ke MySQL terkelola, dan upload harus ke bucket kompatibel S3. Platform menyuntikkan credential resource lewat environment, tetapi kode tetap harus berhenti menganggap engine default SQLite dan disk default lokal.

SQLite ke MySQL: Ketika Tipe dan Collation Dijunjung

SQLite sengaja fleksibel soal tipe, namun MySQL menerapkan aturan berbeda saat Anda menjalankan ulang migration dan query. Ternyata Pinkary pernah menyimpan UUID string di kolom unsigned big integer lewat foreignId('parent_id') dan foreignId('root_id') pada tabel questions, padahal primary key model memakai UUID. SQLite membiarkan string “lewat”; tetap MySQL menolak atau memperlakukan data secara tidak kompatibel. Tim mendeklarasikan ulang kolom sesuai tipe UUID sebenarnya dan memperbaiki foreign key self-reference (migration parent dan root ID pada repositori Pinkary).

foreignIdFor(Question::class) pada Blueprint Laravel dapat menginfer kolom UUID bila model memakai UUID key. Bug spesifik Pinkary ada pada pemanggilan eksplisit foreignId('parent_id'), bukan pada helper generik itu sendiri.

Kasus hashtag menunjukkan collation bukan detail kosmetik. Migration awal membuat unique index case-sensitive pada name. Index NOCASE terpisah melayani autocomplete. Identitas tag dan pencarian sengaja dibedakan.

Percobaan MySQL pertama hampir mengubah perilaku itu. Review PR #765 mendorong kolom unique memakai utf8mb4_bin. Tim memindahkan case folding ke query autocomplete. Putuskan dulu bagaimana identitas vs pencarian menangani huruf besar/kecil, lalu encode keduanya eksplisit.

Sebelum cutover, jalankan migration dan query representatif di engine tujuan. Jangan hanya mengandalkan SQLite CI lama. Untuk cadangan dan impor MySQL pada stack lain (WordPress, Joomla), workflow backup terstruktur tetap relevan; lihat panduan backup dan impor database MySQL sebagai lapisan persiapan terpisah dari perbaikan schema Pinkary.

Rewrite Query Thread dan ONLY_FULL_GROUP_BY

Pinkary tidak punya tabel answers terpisah: jawaban ada di baris question, thread dihubungkan root_id dan parent_id. Query feed lama memilih kolom question, lalu mengelompokkan dengan IFNULL(root_id, id) dan mengurutkan per thread berdasarkan nilai maksimum updated_at. SQLite menerima bentuk itu; namun MySQL dengan ONLY_FULL_GROUP_BY menolak kolom non-aggregated (id, root_id, parent_id) tanpa definisi grup yang jelas, dan query lama tidak pernah menyatakan baris mana yang mewakili thread di feed.

Jadi solusi production: ranking per partisi thread dengan ROW_NUMBER(), lalu joinSub() untuk baris pemenang. Tie-breaker eksplisit id DESC menangani timestamp sama. Tim menerapkan pola yang sama di feed recent, following, dan daftar question profil (RecentQuestionsFeed).

-- Pola konseptual (setara deskripsi Laravel blog Pinkary):
-- 1) Subquery: ROW_NUMBER() OVER (PARTITION BY thread_key ORDER BY updated_at DESC, id DESC) AS rn
-- 2) Outer query: JOIN subquery WHERE rn = 1

Test gagal setelah pindah engine karena beberapa hal sekaligus. Aturan grouping MySQL menolak bentuk lama. Timestamp presisi detik bentrok antar factory. Urutan key array Eloquent berubah karena MySQL menerapkan modifier after() saat alter; SQLite di setup test lama mengabaikannya.

Test yang membandingkan urutan exact array_keys($model->toArray()) tim ganti menjadi assertion keberadaan key plus jumlah. Test waktu memakai clock helper Laravel. Regression test menambahkan dua baris dalam thread dengan timestamp identik agar ordering deterministik terjaga.

Object Storage dan Asumsi Disk Lokal

Meski menyambungkan bucket S3-compatible di Cloud relatif mudah di layer infrastruktur, pekerjaan berat ada di aplikasi.

Pinkary pernah men-hardcode Storage::disk('public') di banyak titik. Daftarnya mencakup avatar, gambar post, job cleanup, upload Livewire, dan test. Maka environment FILESYSTEM_DISK tidak pernah benar-benar mengendalikan perilaku. Refactor menuju disk default terkonfigurasi tercatat di commit migrasi Cloud.

Parser Markdown memeriksa exists() setiap gambar sebelum emit tag img. Pola itu masuk akal di disk lokal. Di object storage setiap cek jadi request remote. Perbaikannya membangun URL object dan membiarkan browser menangani gambar yang gagal load (ImageProviderParsable).

Job avatar beralih dari mutasi path lokal GD ke Intervention Image + Imagick. Bytes ter-encode ditulis lewat API storage. Post GIF ~5 MB pernah membengkak ~35 MB saat setiap frame decode dan resize ulang. Strategi akhir: simpan GIF apa adanya; format lain resize lalu stream ke Storage::put().

Memanggil save() tanpa destination pada image in-memory tidak menulis ke S3. Stream encoded bytes yang membuat write remote nyata (path upload Livewire Create).

Memindahkan Data dan File tanpa Dump Generik

Jalur pertama dump SQL plus konversi gagal: nilai mirip UUID rusak, entry cache serialized tidak selamat. Akhirnya tim mengganti pendekatan dengan command Artisan one-off yang membuka koneksi source SQLite dan target MySQL, memverifikasi koneksi, opsional migrate schema, mencocokkan kolom, menolak target yang sudah berisi data aplikasi, copy chunk dalam transaksi target, reset auto-increment MySQL, dan mencetak row count per tabel (PR #836).

Row count berguna tetapi tidak menggantikan verifikasi relasi. Walau transaksi di target tidak membekukan source SQLite, dokumentasi Pinkary menekankan snapshot konsisten, freeze write singkat, atau delta sync terverifikasi untuk sistem lebih besar.

Tim juga menyalin tabel cache dan cache_locks untuk continuity deduplication view (array ID 120 menit). View count durable tetap di kolom views pada user dan question, bukan hanya di cache.

Command file terpisah stream folder avatars dan images. File remote yang sudah cocok diskip kecuali flag overwrite; ukuran tidak match dianggap error. Pola migrasi konten bertahap di Laravel—meski topiknya editor—menunjukkan audit surface area sebelum cutover; migrasi CKEditor 4 ke Editor.js di Laravel bisa jadi analogi proses, bukan solusi infrastruktur.

Checklist Audit Sebelum Cutover Forge → Cloud

Gabungkan lesson Pinkary dengan checklist praktis: provision target, deploy setelah audit hardcode disk, test staging dengan engine dan disk production-like, backup source, migrasi DB dengan charset/collation dan foreign key dicek, sync file object storage, rencana freeze write atau sync incremental, aliasing traffic, validasi auth/CRUD/search/upload/queue/webhook/transaksi, plus rollback path.

Setelah cutover, bandingkan error rate, latency, kegagalan queue, error DB, dan error storage. Pinkary melaporkan sensasi lebih cepat pasca pindah. Namun perubahan simultan ke MySQL dan S3 tanpa benchmark terkontrol; anggap itu observasi tim, bukan SLA terukur.

Environment: audit APP_KEY, kredensial DB, mail, API pihak ketiga, driver queue, dan variabel storage. Salin pola dari Forge; jangan menempel secret dari artikel atau stream publik.

Static analysis sebelum refactor besar mengurangi regresi tipe dan nullability. PHPStan untuk Laravel dan plugin WordPress relevan bila codebase Anda juga menyentuh WordPress packages.

Forge vs Laravel Cloud: Kapan Menjadi Pilihan Tepat

migrasi Forge ke Laravel Cloud

Forge unggul saat Anda ingin SSH, paket sistem, dan kontrol provisioning sendiri—cocok untuk tim yang nyaman mengelola droplet atau VPS. Sementara itu, Laravel Cloud menarik bila Anda menginginkan platform managed untuk runtime PHP, database, dan object storage dalam satu alur deploy, dan rela menyesuaikan aplikasi yang mengandalkan state lokal persisten. Tidak ada pemenang universal: aplikasi WordPress-heavy mungkin tetap di CMS + hosting klasik; custom Laravel dengan dependency stateful perlu audit seperti Pinkary.

Perbandingan framework (Laravel vs CodeIgniter 4) membantu keputusan greenfield. Sedangkan artikel ini fokus pada debt operasional saat engine dan filesystem berubah.

Engineering migrasi Forge ke Laravel Cloud terjadi di codebase. Jalankan migration di MySQL. Buat ordering query deterministik. Perlakukan storage remote sebagai I/O jaringan. Encode output gambar sebelum write remote. Ganti dump generik dengan tooling copy yang memverifikasi shape data aplikasi Anda. Akhirnya Pinkary hidup di Cloud dengan MySQL dan object storage setelah pekerjaan itu—Forge tidak obsolete; shortcut lama yang obsolete.

Artikel Terkait

Artikel & Informasi Seputar Bisnis & Website

→
baca semua artikel

Copyright © 2026 CodeF Web Developer. All rights reserved colaborated with Pajuda.com