Skip to content

9.1 Storage Produksi

Tujuan bab

Setelah bab ini kamu bisa:

  • Menyebutkan sembilan domain storage Mastra dan apa yang disimpan masing-masing
  • Memilih backend berdasarkan pola akses tiap domain
  • Merutekan domain berbeda ke backend berbeda dengan composite storage
  • Menjelaskan kenapa libSQL berbasis file tidak memadai untuk produksi multi-proses
  • Mengenali gejala adapter yang tidak mendukung domain tertentu

Prasyarat

Storage bukan satu hal

Sejak 3.1 kamu memakai satu baris konfigurasi storage dan tidak memikirkannya lagi. Untuk pengembangan lokal itu tepat. Untuk produksi, ada satu hal yang perlu dibongkar.

Storage Mastra terbagi menjadi domain. Tiap domain memiliki satu jenis data runtime, dan sebuah adapter mengimplementasikan satu atau beberapa domain.

DomainYang disimpan
memoryThread, pesan, resource, working memory, dan state memory agent lainnya
workflowsSnapshot workflow untuk suspend dan resume
observabilityTrace, span, metrik, log, dan feedback
scoresCatatan skor hasil evaluasi
datasetsDataset dan itemnya, dipakai eval dan eksperimen
experimentsRun eksperimen dan hasil per item
backgroundTasksCatatan dan state eksekusi background task
schedulesDefinisi schedule dan riwayat pemicunya
threadStateState task, goal, dan thread yang durable

Daftar itu menjelaskan banyak hal yang kamu temui sebelumnya. Ingat peringatan di 7.2 bahwa schedules memerlukan adapter yang mendukung domain schedules — sekarang alasannya jelas.

Dukungan adapter berbeda per domain

Dokumentasi menyatakan bahwa dukungan adapter bervariasi menurut domain. Sebuah adapter yang bagus untuk memory belum tentu mengimplementasikan schedules atau observability.

Gejalanya sering membingungkan: fitur yang kamu pakai tiba-tiba tidak bekerja padahal storage "sudah dikonfigurasi". Periksa dukungan domain sebelum memilih.

Memilih backend berdasarkan pola akses

Inilah inti bab ini. Tiap domain menulis dan membaca dengan cara yang berbeda, dan backend yang tepat mengikuti pola itu.

DomainPola aksesnyaBackend yang cocok
memoryBaca-tulis baris di setiap panggilan agent yang diingatBasis data transaksional: libSQL, PostgreSQL, MongoDB
observabilityMenulis telemetri bervolume tinggi, sering dikueri secara agregatStore observability khusus, atau OLAP seperti ClickHouse atau DuckDB
workflowsSnapshot durable yang harus tersedia saat run dilanjutkanBasis data persisten yang andal
scores, datasets, experimentsData evaluasi berfrekuensi rendah, dibaca belakangan untuk analisisBasis data biasa
schedulesDefinisi schedule dan riwayat pemicuAdapter yang mengimplementasikan domain schedules

Perhatikan baris kedua. Data observability punya sifat yang sangat berbeda dari data memory: volumenya jauh lebih besar, ditulis sekali dan jarang diubah, dan dibaca sebagai agregat. Basis data transaksional bisa menyimpannya, tapi bukan yang terbaik untuknya.

Composite storage — satu domain, satu backend

Karena domain punya kebutuhan berbeda, Mastra menyediakan composite storage untuk merutekan tiap domain ke backend yang tepat.

Ini contoh nyata dari proyek Mastra standar:

ts
// src/mastra/index.ts
import { Mastra } from '@mastra/core/mastra'
import { MastraCompositeStore } from '@mastra/core/storage'
import { LibSQLStore } from '@mastra/libsql'
import { DuckDBStore } from '@mastra/duckdb'

export const mastra = new Mastra({
  storage: new MastraCompositeStore({
    id: 'composite-storage',
    default: new LibSQLStore({
      id: 'mastra-storage',
      url: process.env.TURSO_DATABASE_URL || 'file:./mastra.db',
      authToken: process.env.TURSO_AUTH_TOKEN || undefined,
    }),
    domains: {
      observability: await new DuckDBStore().getStore('observability'),
    },
  }),
})

Bacalah konfigurasi itu sebagai keputusan desain, bukan sekadar sintaks:

  • default menangani semua domain yang tidak disebut secara khusus — memory, workflows, scores, dan seterusnya — memakai libSQL
  • domains.observability merutekan telemetri ke DuckDB, sebuah basis data analitik yang memang dirancang untuk pola tulis-banyak-baca-agregat

Satu baris domains itu adalah penerapan langsung tabel pola akses di atas.

Pola url yang layak ditiru

Perhatikan juga baris ini:

ts
url: process.env.TURSO_DATABASE_URL || 'file:./mastra.db',
authToken: process.env.TURSO_AUTH_TOKEN || undefined,

Ini pola yang rapi: berkas lokal saat pengembangan, Turso saat produksi, tanpa mengubah satu baris kode. Cukup mengisi variabel lingkungan.

Turso adalah libSQL yang di-host, sehingga adapter yang sama bekerja untuk keduanya — kamu tidak berpindah adapter, hanya berpindah tujuan.

Kenapa file:./mastra.db tidak cukup untuk produksi

libSQL berbasis file sempurna untuk pengembangan: tidak butuh server terpisah, dan datanya bertahan antar restart. Tapi ia punya batas yang menjadi masalah begitu masuk produksi.

Satu berkas hanya hidup di satu mesin. Begitu kamu menjalankan dua instance aplikasi — untuk penskalaan atau ketersediaan — keduanya memakai berkas yang berbeda. Pengguna yang permintaannya mendarat di instance A tidak akan melihat ingatan yang tersimpan di instance B.

Dokumentasi menyebut kriteria yang jelas untuk kapan kamu butuh storage persisten sungguhan:

  • Agent mengingat pesan atau fakta pengguna
  • Workflow suspend dan resume setelah restart
  • Trace, metrik, log, skor, atau feedback perlu tetap tersedia untuk analisis
  • Schedule dan background task berlanjut melewati deployment
  • Beberapa proses runtime membaca dan menulis state yang sama

Poin terakhir yang mematikan libSQL berbasis file.

Pengembangan                    Produksi (2 instance)
┌──────────────┐                ┌──────────┐  ┌──────────┐
│  app         │                │  app A   │  │  app B   │
│    ↓         │                │    ↓     │  │    ↓     │
│  mastra.db   │                │  file A  │  │  file B  │  ← state terpecah
└──────────────┘                └──────────┘  └──────────┘

                                Yang benar:
                                ┌──────────┐  ┌──────────┐
                                │  app A   │  │  app B   │
                                └────┬─────┘  └────┬─────┘
                                     └──────┬──────┘
                                       ┌────▼─────┐
                                       │ Postgres │
                                       └──────────┘

Pilihan backend

BackendPaketCatatan
libSQL / Turso@mastra/libsqlSama untuk lokal dan produksi terkelola
PostgreSQL@mastra/pgPilihan aman kalau kamu sudah memakai Postgres
MongoDB@mastra/mongodbUntuk yang sudah memakai MongoDB
DuckDB@mastra/duckdbAnalitik — cocok untuk domain observability
ClickHouse@mastra/clickhouseOLAP untuk telemetri bervolume sangat tinggi
Redis, Upstash@mastra/redis, @mastra/upstashUntuk kebutuhan tertentu

Saran praktis: pilih yang sudah kamu operasikan. Basis data yang timmu sudah tahu cara memantau, mencadangkan, dan memulihkannya lebih berharga daripada yang secara teori sedikit lebih cepat.

Retensi

Satu hal yang sering baru disadari setelah tagihan datang: data observability tumbuh terus. Trace dari tiga bulan lalu jarang berguna, tapi tetap memakan tempat.

Mastra menyediakan konfigurasi retensi untuk ini. Tetapkan kebijakannya sebelum produksi, bukan setelah basis datamu penuh — karena menghapus data lama dari tabel yang sudah membengkak jauh lebih merepotkan daripada tidak pernah menyimpannya terlalu lama.

Kesalahan umum

Gejala: Setelah menskalakan aplikasi ke beberapa instance, agent kadang mengingat percakapan dan kadang tidak — tergantung permintaan mendarat di mana. Penyebab: Storage memakai libSQL berbasis file. Tiap instance punya berkasnya sendiri, sehingga state terpecah. Perbaikan: Pindah ke backend yang bisa diakses bersama — PostgreSQL, MongoDB, atau libSQL terkelola seperti Turso. Kalau kamu sudah memakai pola process.env.TURSO_DATABASE_URL || 'file:./mastra.db', perpindahannya hanya mengisi variabel lingkungan.

Gejala: Sebuah fitur — schedules, misalnya — tidak bekerja di produksi meskipun storage jelas terkonfigurasi dan fitur lain berjalan normal. Penyebab: Adapter yang kamu pakai tidak mengimplementasikan domain yang dibutuhkan fitur itu. Dukungan adapter berbeda-beda per domain. Perbaikan: Periksa dukungan domain adapter tersebut. Kalau tidak mendukung, pakai composite storage dan rutekan domain itu ke adapter lain yang mendukungnya.

Coba sendiri

Tantangan: Ubah salah satu aplikasi yang kamu bangun di bagian sebelumnya menjadi konfigurasi storage yang siap produksi, dan buktikan perbedaannya.

Ketentuan:

  1. Ganti konfigurasi storage tunggal menjadi MastraCompositeStore dengan default untuk sebagian besar domain dan satu domain dirutekan terpisah
  2. Rutekan observability ke DuckDBStore, dan jelaskan alasannya dengan merujuk pola aksesnya
  3. Pakai pola process.env.X || 'file:./mastra.db' untuk default, sehingga berkas lokal dipakai bila variabel tidak diisi
  4. Buktikan masalah state terpecah: jalankan dua instance aplikasi di port berbeda, keduanya memakai libSQL berbasis file. Simpan sesuatu ke memory lewat instance A, lalu coba baca lewat instance B
  5. Arahkan keduanya ke satu basis data bersama, ulangi langkah 4, dan bandingkan
  6. Daftar domain mana saja yang aplikasimu benar-benar pakai, dan periksa apakah adapter pilihanmu mendukung semuanya

Checklist penerimaan:

  • [ ] Composite storage berjalan, dan kamu bisa menunjukkan domain mana yang dirutekan ke mana
  • [ ] Langkah 4 memperlihatkan state terpecah secara nyata — instance B tidak melihat data instance A
  • [ ] Langkah 5 memperbaikinya, dan kamu tidak mengubah kode untuk itu, hanya konfigurasi
  • [ ] Kamu punya daftar domain yang dipakai aplikasimu, bukan tebakan
  • [ ] Kamu bisa menjelaskan kenapa observability layak backend terpisah sementara memory tidak

Petunjuk: Langkah 4 adalah bagian yang mengubah pemahaman. Banyak orang tahu secara teori bahwa berkas lokal tidak cocok untuk multi-instance, tapi melihat sendiri instance B kehilangan ingatan yang jelas tersimpan di instance A membuat kesimpulannya melekat. Lakukan sebelum kamu benar-benar menskalakan — bukan sesudahnya, saat penggunamu yang menemukannya.

Ikhtisar

  • Storage Mastra terbagi menjadi sembilan domain, dan dukungan adapter berbeda-beda per domain. Periksa sebelum memilih.
  • Pilih backend berdasarkan pola akses: transaksional untuk memory, analitik (DuckDB, ClickHouse) untuk observability.
  • MastraCompositeStore merutekan tiap domain ke backend yang tepat, lewat default dan domains.
  • Pola process.env.TURSO_DATABASE_URL || 'file:./mastra.db' memberi berkas lokal saat pengembangan, layanan terkelola saat produksi tanpa mengubah kode.
  • libSQL berbasis file tidak memadai untuk multi-proses — tiap instance punya berkasnya sendiri, dan state terpecah.
  • Tetapkan kebijakan retensi sebelum produksi, bukan setelah basis data membengkak.
  • Pilih backend yang timmu sudah tahu cara mengoperasikannya.

Lanjut ke mana

Datamu sudah punya rumah yang layak. Sekarang bagaimana dunia luar berbicara dengan agent-mu: 9.2 Server & Custom Routes.

Materi belajar mandiri. Bukan dokumentasi resmi Mastra.