Developer frontend yang menggunakan PouchDB di web app-nya cepat atau lambat akan menemui permasalahan ini: PouchDB tak bisa sync ke lebih dari enam database. Mari kita lihat contoh sinkronisasi database PouchDB di bawah ini.

const names = ['satu', 'dua', 'tiga', 'empat', 'lima', 'enam', 'tujuh', 'delapan']

const db_local = {}
const db_remote = {}
for (const name of names) {
  db_local[name] = new PouchDB(`db-local-${name}`)
  db_remote[name] = new PouchDB(`http://localhost:5984/db-remote-${name}`)
}

const main = () => {
  for (const name of names) {
    PouchDB.sync(db_local[name], db_remote[name], {
      live: true,
      retry: true,
    })
  }
}

main()

Di situ saya memiliki delapan database dan saya melakukan sync local-remote pada delapan database tersebut. Selanjutnya, mari kita jalankan kode tersebut di browser dan kita inspect.

Ternyata eh ternyata, ada dua hal yang bisa kita sadari:

  • Live sync di PouchDB menggunakan longpolling. Itulah kenapa pada diagram waterfall di inspect itu garisnya panjang.
  • Koneksi pada database tujuh dan delapan pending dan artinya itu tak sync. Mau ada perubahan di remote database pun tak akan ter-sync ke local database.

Kenapa hal itu bisa terjadi?

Permasalahan

Pertama, karena PouchDB hanyalah sebuah client untuk CouchDB. Bisa dilihat di website CouchDB bahwa API dia via HTTP, dan hanya via HTTP. Sehingga, satu-satunya cara live sync/real time sync via HTTP adalah dengan longpolling. Cara kerja longpolling adalah dengan cara browser mengirim request ke server lalu server menahan pemberian response sampai ada response yang bisa diberikan. Dalam kasus PouchDB, response diberikan hanya ketika ada changes terjadi. Selama belum ada changes, CouchDB tak akan memberikan response dan koneksi dari browser akan persistent terus hidup selamanya (atau sampai timeout).

Sayangnya, browser memiliki limitasi dalam hal persistent connection ini, yaitu most modern browsers hanya membolehkan enam persistent connection per domain. Sehingga dari pada itu, hanya database satu sampai enam saja yang sync. Database tujuh dan delapan akan pending karena jatah browser untuk melakukan koneksi kepada remote database tujuh dan delapan telah habis dimakan persistent connection untuk database satu sampai enam.

Solusi

Solusi yang paling simpel adalah dengan serve CouchDB di lebih dari satu domain, semisal db-satu.server.com, db-dua.server.com, dan seterusnya. Hal ini akan menyelesaikan permasalahan tersebut karena limitasi enam maximum persistent connection hanya pada satu domain.

Tapi itu tak menyelesaikan permasalahan "yang sebenarnya". Permasalahan yang sebenarnya adalah: WTF longpolling. Live sync butuh push, lalu push-nya pakai longpolling? Ini bukan 2009 bung, ini 2020. Kalau ini masih 2009 tak apa lah, Facebook juga pakai longpolling di zaman itu. Tapi ini 2020 bung. Wikipedia saja menulis bahwa longpolling bukanlah true push. Itu hanyalah akal-akalan semata.

Jadi, di tulisan ini kita akan membawa PouchDB ke tahun 2020 bung. Let's move on. Di tahun 2020 ini ternyata sudah ada HTML5 bung. Push technology sudah didukung di HTML5 bung. Dengan cara apa? Dengan cara pakai WebSocket bung. WebSocket itu "can send messages to a server and receive event-driven responses without having to poll the server for a reply" bung. Selamat datang di 2020!

Cara Kerja Longpolling PouchDB-CouchDB

Sekarang, ayo kita cari cara supaya bisa membuat PouchDB menggunakan WebSocket. Dibaca-baca di API docs-nya PouchDB, ternyata kita bisa intercept or override the HTTP request dengan menambahkan option fetch saat instansiasi. Pertanda bagus nih, mari kita intercept.

const myCustomFetch = (...args) => {
  console.log(...args)
  return fetch(...args)
}

const db_local = new PouchDB(`db-local`)
const db_remote = new PouchDB(`http://localhost:5984/db-remote`, {
  fetch: myCustomFetch,
})

PouchDB.sync(db_local, db_remote, {
  live: true,
  retry: true,
})

Saat melihat console log-nya, kita akan menemui ini.

Bisa dilihat bahwa log yang terakhir itu adalah request yang longpoll. Tahu dari mana? Lihat saja di inspect network-nya. Dan juga di URL-nya itu ada parameter &feed=longpoll. Di API docs CouchDB dijelaskan bahwa parameter feed ini memang untuk bilang apakah pakai longpoll (keep connection) dan lainnya.

Sip, selanjutnya jikalau ada request yang URL-nya mengandung ini tinggal kita override saja agar menggunakan WebSocket.

PouchDB-CouchDB via WebSocket Proxy

Untungnya saya sudah buatkan di repo GitHub ini dan bisa install pakai npm. Bacalah readme untuk penggunaannya. Singkatnya, hanya dua step untuk menggunakannya.

Pertama, jalankan servernya pakai NodeJS.

const createServer = require('pouchdb-longpoll-ws-proxy/createServer')

const wsServer = createServer({
  host: HOST_OF_WS_PROXY,
  port: PORT_OF_WS_PROXY,
})

wsServer.on('listening', () => console.log('listening'))

Kedua, pasang custom fetch pada saat instansiasi PouchDB.

const myCustomFetch = createFetchWs(`ws://${HOST_OF_WS_PROXY}:${PORT_OF_WS_PROXY}`)

const db_local = new PouchDB(`db-local`)
const db_remote = new PouchDB(`http://localhost:5984/db-remote`, {
  fetch: myCustomFetch,
})

PouchDB.sync(db_local, db_remote, {
  live: true,
  retry: true,
})

Cara kerjanya simpel.

  • Ketika dipanggil, myCustomFetch mengecek apakah URL-nya mengandung feed=longpoll. Jika tidak, maka pakai fetch biasa langsung ke CouchDB. Jika ya, maka send message ke WebSocket proxy yang message-nya berisi URL dari request tersebut (beserta headers-nya juga, semisal di headers berisi hal-hal perihal authentication dan lain-lain).
  • WebSocket proxy menerima message tersebut, lalu dia melakukan request ke CouchDB via HTTP dengan URL dan headers yang tercantum pada message.
  • Ketika WebSocket proxy mendapatkan response dari CouchDB, dia mengirim message ke client yang message-nya ini berisi balasan dari CouchDB tersebut.
  • myCustomFetch akan menerima message ini lalu mem-format-nya sedemikian sehingga menjadi selayaknya return value dari fetch biasa. Setelah itu selanjutnya PouchDB menjalankan aktivitasnya seperti biasa.

Silakan coba sendiri, dan rasakan nikmatnya menjalankan sync PouchDB pada banyak database sekaligus dalam satu waktu.