Read preference menentukan dari anggota mana kita membaca; read concern menentukan versi data mana yang boleh dilihat. Di atas replica set, keduanya membentuk satu kesatuan: baca dari secondary yang tertinggal memakai local bisa menampilkan data yang belum diakui mayoritas, sementara majority menjamin hanya data yang pasti bertahan dari kegagalan node. Chapter ini membedah empat level read concern, mengujinya melalui runCommand pada rs0, lalu mengaitkannya dengan pola read your own writes dan konsistensi kausal.

Empat Level Read Concern #

LevelJaminanCatatan
localData terbaru pada node yang melayaniDefault; bisa berisi write yang belum diakui mayoritas
majorityHanya data yang telah diakui mayoritas anggotaTahan terhadap rollback; pilihan aman lintas failover
linearizableHasil baca yang pasti paling baru di seluruh clusterHanya untuk baca satu document di primary; biaya tertinggi
snapshotTitik waktu konsisten di seluruh operasiDiperuntukkan transaksi; mengembalikan atClusterTime

Level-level ini tersusun dari yang paling murah (local) hingga yang paling ketat (linearizable). Pilihannya menentukan jawaban satu pertanyaan: seberapa besar kemungkinan hasil baca kita berisi data yang kelak digulung karena kegagalan primary — fenomena yang dibayangkan di Chapter Elections dan Failover sebagai write yang tidak sempat direplikasi mayoritas.

Membedah readConcern lewat runCommand #

Read concern dikirim sebagai field readConcern pada dokumen perintah — bentuk paling eksplisit yang bisa kita amati lewat db.runCommand(). Kita mulai dengan defaultnya:

js
use latihan_replika
db.dropDatabase()
db.pesanan.insertOne({ kode: "PSN-001", pelanggan: "Budi", buku: "Laut Bercerita", jumlah: 2 })

db.runCommand({ find: "pesanan", filter: {}, projection: { _id: 0 }, readConcern: { level: "local" } })

Output yang Dihasilkan:

text
switched to db latihan_replika
{ ok: 1, dropped: 'latihan_replika' }
{ acknowledged: true, insertedId: ObjectId('6ad3d6d27a6b48cf6d9ffb25') }
{
  cursor: {
    firstBatch: [
      {
        kode: 'PSN-001',
        pelanggan: 'Budi',
        buku: 'Laut Bercerita',
        jumlah: 2
      }
    ],
    id: Long('0'),
    ns: 'latihan_replika.pesanan'
  },
  ok: 1,
  '$clusterTime': {
    clusterTime: Timestamp({ t: 1792267986, i: 2 }),
    signature: {
      hash: Binary.createFromBase64('AAAAAAAAAAAAAAAAAAAAAAAAAAA=', 0),
      keyId: Long('0')
    }
  },
  operationTime: Timestamp({ t: 1792267986, i: 2 })
}

Sekarang level majority pada collection yang sama:

js
db.runCommand({ find: "pesanan", filter: {}, projection: { _id: 0 }, readConcern: { level: "majority" } })

Output yang Dihasilkan:

text
{
  cursor: {
    firstBatch: [
      {
        kode: 'PSN-001',
        pelanggan: 'Budi',
        buku: 'Laut Bercerita',
        jumlah: 2
      }
    ],
    id: Long('0'),
    ns: 'latihan_replika.pesanan'
  },
  ok: 1,
  '$clusterTime': {
    clusterTime: Timestamp({ t: 1792267986, i: 2 }),
    signature: {
      hash: Binary.createFromBase64('AAAAAAAAAAAAAAAAAAAAAAAAAAA=', 0),
      keyId: Long('0')
    }
  },
  operationTime: Timestamp({ t: 1792267986, i: 2 })
}

Dua hal terbaca dari pasangan keluaran ini. Pertama, readConcern hanyalah satu field pada dokumen perintah — levelnya diganti tanpa mengubah bentuk permintaan. Kedua, hasilnya identik karena pada rs0 primary tunggal sekaligus adalah mayoritas: setiap write yang diterima primary otomatis telah diakui mayoritas. Di cluster nyata keduanya bisa berbeda — baca local dari primary yang baru saja menerima write namun belum direplikasi akan menampilkannya, sedangkan baca majority menahannya sampai benar-benar aman.

Level linearizable dikirim dengan cara yang sama — ganti field menjadi { level: "linearizable" }. Batasannya penting: hanya berlaku untuk baca satu document (query dengan filter yang menunjuk satu document pasti, biasanya _id), dilayani primary, dan menunggu konfirmasi bahwa tidak ada write lain yang lebih baru — latensinya paling tinggi di antara semua level.

Snapshot dan atClusterTime #

Level snapshot membaca seolah dunia berhenti pada satu titik waktu. Di luar transaksi, MongoDB 9.0 tetap menerima snapshot pada operasi find dan melaporkan titik waktunya:

js
db.runCommand({ find: "pesanan", filter: {}, projection: { _id: 0 }, readConcern: { level: "snapshot" } })

Output yang Dihasilkan:

text
{
  cursor: {
    firstBatch: [
      {
        kode: 'PSN-001',
        pelanggan: 'Budi',
        buku: 'Laut Bercerita',
        jumlah: 2
      }
    ],
    id: Long('0'),
    ns: 'latihan_replika.pesanan',
    atClusterTime: Timestamp({ t: 1792267986, i: 2 })
  },
  ok: 1,
  '$clusterTime': {
    clusterTime: Timestamp({ t: 1792267986, i: 2 }),
    signature: {
      hash: Binary.createFromBase64('AAAAAAAAAAAAAAAAAAAAAAAAAAA=', 0),
      keyId: Long('0')
    }
  },
  operationTime: Timestamp({ t: 1792267986, i: 2 })
}

Perhatikan field baru atClusterTime — penanda titik waktu snapshot yang diambil. Ranah utama snapshot memang transaksi multi-document, tempat seluruh operasi baca dalam satu transaksi menempel pada titik waktu yang sama:

js
const s = db.getMongo().startSession()
s.startTransaction({ readConcern: { level: "snapshot" }, writeConcern: { w: "majority" } })
const dbx = s.getDatabase("latihan_replika")
dbx.pesanan.updateOne({ kode: "PSN-001" }, { $inc: { jumlah: 1 } })
s.commitTransaction()
s.endSession()

db.pesanan.findOne({ kode: "PSN-001" }, { _id: 0 })

Output yang Dihasilkan:

text
{ acknowledged: true, insertedId: null, matchedCount: 1, modifiedCount: 1, upsertedCount: 0 }
{
  kode: 'PSN-001',
  pelanggan: 'Budi',
  buku: 'Laut Bercerita',
  jumlah: 3
}

Transaksi di atas dipasang readConcern: "snapshot" plus writeConcern: "majority" — kombinasi yang dianjurkan untuk transaksi: baca melihat titik waktu konsisten, dan commit bertahan dari kegagalan node. Mekanika penuh transaksi sudah kita bahas di Chapter Multi-Document Transactions; yang baru di sini adalah kesadarannya pada level read concern.

Read Your Own Writes dan Konsistensi Kausal #

Pola yang paling sering menuntut read concern adalah read your own writes: pengguna mengubah profil, lalu halaman profil harus menampilkan perubahan itu. Baca local dari primary umumnya cukup; tetapi bila baca jatuh ke secondary — lewat read preference — profil bisa tampil versi lama. Dua jalan keluarnya:

  1. Baca write-sendiri dari primary — kombinasi readPreference: "primary" dan write concern apa pun.
  2. Konsistensi kausal — pasangan write concern majority + read concern majority pada session yang sama, dengan cluster time yang dibawa klien. Session menjamin operasi berikutnya melihat efek operasi sebelumnya, walau pindah anggota.

mongosh dan driver membuat session secara otomatis dan session itu causally consistent secara bawaan — perilaku yang kita manfaatkan tanpa sadar setiap kali dua perintah berurutan di REPL. Desain pilihan konsistensi yang lebih luas sudah dibahas di Chapter Perancangan Konsistensi.

Rangkuman #

  • Read concern (local, majority, linearizable, snapshot) mengatur versi data yang boleh dilihat; dikirim sebagai field readConcern pada dokumen perintah.
  • local adalah default yang paling cepat; majority hanya menampilkan data yang diakui mayoritas — aman dari rollback lintas failover.
  • linearizable berlaku untuk baca satu document dengan jaminan terbaru secara global; snapshot dipakai transaksi dan melaporkan atClusterTime.
  • Read your own writes dijamin dari primary, atau lewat konsistensi kausal: write majority + read majority pada satu session.

Jaminan baca dan tulis sudah lengkap — tinggal satu ukuran kesehatan yang belum kita pegang: seberapa jauh secondary tertinggal. Chapter Replication Lag mengukurnya.