Saat mengoptimalkan performa aplikasi, pertimbangkan penggunaan NDB-nya. Misalnya, jika aplikasi membaca nilai yang tidak ada dalam cache, proses baca tersebut akan memakan waktu cukup lama. Anda mungkin dapat mempercepat aplikasi dengan melakukan tindakan Datastore secara paralel dengan hal-hal lain, atau melakukan beberapa tindakan Datastore secara paralel.
Library Klien NDB menyediakan banyak fungsi asinkron ("async").
Masing-masing fungsi ini
memungkinkan aplikasi mengirim permintaan ke Datastore. Fungsi ini segera
ditampilkan, yang menampilkan
objek
Future
. Aplikasi dapat melakukan hal-hal lain saat Datastore menangani
permintaan.
Setelah Datastore menangani permintaan, aplikasi dapat memperoleh hasil
dari objek Future
.
Pengantar
Misalkan salah satu pengendali permintaan aplikasi Anda perlu menggunakan NDB untuk menulis sesuatu, mungkin untuk merekam permintaan tersebut. Aplikasi tersebut juga perlu melakukan beberapa operasi NDB lainnya, mungkin untuk mengambil beberapa data.
Dengan mengganti panggilan ke put()
dengan panggilan ke
put_async()
yang setara asinkron, aplikasi
dapat langsung melakukan hal-hal lain, bukan memblokir put()
.
Hal ini memungkinkan fungsi NDB dan rendering template lainnya terjadi saat Datastore menulis data. Aplikasi tidak akan melakukan pemblokiran pada Datastore hingga mendapatkan data dari Datastore.
Dalam contoh ini, kurang sesuai jika kita memanggil future.get_result
:
aplikasi tersebut tidak pernah menggunakan hasil dari NDB. Kode tersebut
ada untuk memastikan bahwa pengendali permintaan tidak keluar sebelum
put
NDB selesai; jika pengendali permintaan keluar terlalu awal,
penangguhan tidak akan pernah terjadi. Untuk memudahkan, Anda dapat mendekorasi pengendali
permintaan dengan @ndb.toplevel
. Atribut ini memberi tahu pengendali agar tidak
keluar hingga permintaan asinkronnya selesai. Dengan demikian, Anda dapat
mengirimkan permintaan dan tidak perlu mengkhawatirkan hasilnya.
Anda dapat menentukan keseluruhan WSGIApplication
sebagai
ndb.toplevel
. Hal ini akan memastikan bahwa setiap pengendali
WSGIApplication
menunggu semua permintaan asinkron
sebelum ditampilkan.
(Tidak melakukan "level teratas" semua pengendali WSGIApplication
.)
Menggunakan aplikasi toplevel
lebih mudah daripada
semua fungsi pengendalinya. Namun, jika metode pengendali menggunakan yield
,
metode tersebut masih harus digabungkan dalam dekorator lain,
@ndb.synctasklet
; jika tidak, eksekusi akan berhenti di
yield
dan tidak selesai.
Menggunakan Async API dan Future
Hampir setiap fungsi NDB sinkron memiliki pasangan _async
. Misalnya, put()
memiliki put_async()
.
Argumen fungsi asinkron selalu sama dengan argumen
versi sinkron.
Nilai hasil metode asinkron selalu berupa
Future
atau daftar Future
(untuk fungsi "multi").
Future adalah objek yang mempertahankan status untuk operasi
yang telah dimulai, tetapi mungkin belum selesai; semua API asinkron menampilkan satu atau beberapa Futures
.
Anda dapat memanggil fungsi get_result()
Future
untuk menanyakan hasil operasinya;
Future akan memblokir, jika perlu, sampai hasilnya tersedia,
kemudian memberikannya kepada Anda.
get_result()
menampilkan nilai yang akan ditampilkan oleh API versi sinkron.
Catatan:
Jika telah menggunakan Future dalam bahasa pemrograman tertentu lainnya, Anda mungkin berpikir
dapat menggunakan Future sebagai hasilnya secara langsung. Hal tersebut tidak bisa dilakukan di sini.
Bahasa tersebut menggunakan future implisit; NDB menggunakan future eksplisit.
Panggil get_result()
untuk mendapatkan hasil Future
NDB.
Bagaimana jika operasi memunculkan pengecualian? Hal ini bergantung pada kapan
pengecualian terjadi. Jika NDB menemukan masalah saat membuat permintaan
(mungkin argumen dari jenis yang salah), metode _async()
akan memunculkan pengecualian. Tetapi, jika pengecualian terdeteksi oleh, misalnya,
server Datastore,_async()
akan menampilkan
Future
, dan pengecualian akan diterapkan saat aplikasi Anda
memanggil get_result()
-nya. Jangan terlalu khawatir tentang hal ini, semuanya
akhirnya berperilaku cukup alami; mungkin perbedaan terbesarnya adalah
jika traceback dicetak, Anda akan melihat beberapa mesin asinkron
tingkat rendah terekspos.
Misalnya, Anda menulis aplikasi buku tamu. Jika pengguna login, Anda ingin menampilkan halaman yang menampilkan postingan buku tamu terbaru. Halaman ini juga harus menampilkan nama panggilan pengguna. Aplikasi memerlukan dua jenis informasi: informasi akun pengguna yang login dan isi postingan buku tamu. Versi "sinkron" aplikasi ini mungkin terlihat seperti ini:
Ada dua tindakan I/O independen di sini: mendapatkan
entity Account
dan mengambil entity Guestbook
terbaru. API sinkron digunakan secara berurutan;
kami menunggu untuk menerima informasi akun sebelum mengambil
entity buku tamu. Namun aplikasi tidak membutuhkan informasi
akun itu segera. Kita dapat memanfaatkan ini dan menggunakan API asinkron:
Versi kode ini pertama-tama membuat dua Futures
(acct_future
dan recent_entries_future
),
lalu menunggunya. Server bekerja pada kedua permintaan secara paralel.
Setiap panggilan fungsi _async()
membuat objek Future
dan mengirim permintaan ke server Datastore. Server dapat segera mulai
menangani permintaan. Respons server dapat kembali
dalam urutan arbitrer; link objek Future merespons
permintaannya yang sesuai.
Total waktu (nyata) yang dihabiskan dalam versi asinkron kira-kira sama dengan waktu maksimum di seluruh operasi. Total waktu yang dihabiskan dalam versi sinkron melebihi jumlah waktu operasi. Jika Anda dapat menjalankan lebih banyak operasi secara paralel, operasi asinkron akan lebih membantu.
Untuk melihat durasi kueri aplikasi Anda atau jumlah operasi I/O yang dilakukan per permintaan, pertimbangkan untuk menggunakan Appstats. Alat ini dapat menampilkan diagram yang mirip dengan gambar di atas berdasarkan instrumentasi aplikasi aktif.
Menggunakan Tasklet
Tasklet NDB adalah potongan kode yang dapat berjalan serentak dengan
kode lainnya. Jika Anda menulis tasklet, aplikasi dapat menggunakannya seperti
menggunakan fungsi NDB asinkron: aplikasi memanggil tasklet, yang menampilkan
Future
; kemudian, memanggil metode get_result()
Future
akan mendapatkan hasil.
Tasklet adalah cara untuk menulis fungsi serentak tanpa
rangkaian pesan; tasklet dijalankan oleh loop peristiwa dan dapat menangguhkan
pemblokirannya sendiri untuk I/O atau beberapa operasi lain menggunakan
pernyataan hasil. Gagasan tentang operasi pemblokiran diabstraksikan ke dalam class
Future
,
tetapi tasklet juga dapat yield
RPC untuk menunggu RPC tersebut selesai.
Ketika tasklet memberikan hasil, tasklet akan meng-raise
pengecualian ndb.Return
; NDB kemudian mengaitkan hasilnya
dengan hasil operasi Future
yang di-yield
sebelumnya.
Saat menulis tasklet NDB, Anda menggunakan yield
dan
raise
dengan cara yang tidak biasa. Oleh karena itu, jika mencari contoh
cara menggunakannya, Anda mungkin tidak akan menemukan kode seperti tasklet NDB.
Untuk mengubah fungsi menjadi tasklet NDB:
- dekorasi fungsi dengan
@ndb.tasklet
, - ganti semua panggilan datastore sinkron dengan
yield
panggilan datastore asinkron, - buat fungsi "menampilkan" nilai yang ditampilkan dengan
raise ndb.Return(retval)
(tidak diperlukan jika fungsi tidak menampilkan apa pun).
Aplikasi bisa menggunakan tasklet untuk kontrol yang lebih baik atas API asinkron. Sebagai contoh, perhatikan skema berikut:
...
Saat menampilkan pesan, sebaiknya tampilkan nama panggilan penulis. Cara "sinkron" untuk mengambil data guna menampilkan daftar pesan mungkin terlihat seperti ini:
Sayangnya, pendekatan ini tidak efisien. Jika melihatnya di Appstats, Anda akan melihat bahwa permintaan "Get" berada dalam rangkaian. Anda mungkin melihat pola "tangga" berikut.
Bagian program ini akan lebih cepat jika "Get" itu bisa tumpang tindih.
Anda dapat menulis ulang kode untuk menggunakan get_async
, tetapi akan
sulit untuk melacak permintaan dan pesan asinkron mana yang saling terkait.
Aplikasi dapat menentukan fungsi "async"-nya sendiri dengan menjadikannya sebagai tasklet. Ini memungkinkan Anda untuk mengatur kode dengan cara yang tidak terlalu membingungkan.
Selain itu, daripada menggunakan acct = key.get()
atau acct = key.get_async().get_result()
, fungsi tersebut harus menggunakan acct = yield key.get_async()
.
yield
ini memberi tahu NDB bahwa ini adalah tempat yang tepat untuk menangguhkan
tasklet ini dan memungkinkan tasklet lainnya berjalan.
Mendekorasi fungsi generator dengan @ndb.tasklet
akan membuat fungsi tersebut menampilkan Future
, bukan
objek generator. Dalam tasklet, yield
mana pun dari
Future
akan menunggu dan menampilkan hasil Future
.
Contoh:
Perhatikan bahwa meskipun get_async()
menampilkan
Future
, kerangka kerja tasklet menyebabkan ekspresi yield
menampilkan hasil Future
ke variabel
acct
.
map()
memanggil callback()
beberapa kali.
Namun, yield ..._async()
di callback()
memungkinkan penjadwal NDB mengirim banyak permintaan asinkron sebelum menunggu
salah satunya selesai.
Jika melihat hal ini di Appstats, Anda mungkin terkejut melihat beberapa "Get" ini tidak hanya tumpang-tindih, tetapi semuanya berfungsi dalam permintaan yang sama. NDB akan menerapkan "autobatcher". Autobatcher memaketkan beberapa permintaan dalam satu batch RPC ke server; hal ini dilakukan sedemikian rupa agar selama masih ada banyak pekerjaan yang harus dilakukan (callback lain dapat berjalan), pengumpulan kunci tetap bisa dilakukan. Segera setelah salah satu hasil diperlukan, autobatcher akan mengirimkan batch RPC. Tidak seperti kebanyakan permintaan, kueri tidak "dikelompokkan".
Saat berjalan, tasklet akan mendapatkan namespace defaultnya dari apa pun default-nya saat tasklet muncul, atau apa pun yang diubah tasklet saat berjalan. Dengan kata lain, namespace default tidak terkait dengan atau disimpan dalam Konteks, dan mengubah namespace default dalam satu tasklet tidak memengaruhi namespace default di tasklet lainnya, kecuali yang dihasilkannya.
Tasklet, Kueri Paralel, Hasil Paralel
Anda dapat menggunakan tasklet agar beberapa kueri mengambil data secara bersamaan. Misalnya, aplikasi Anda memiliki sebuah halaman yang menampilkan isi keranjang belanja dan daftar penawaran spesial. Skemanya mungkin terlihat seperti ini:
Fungsi "sinkron" yang mendapatkan item keranjang belanja dan penawaran spesial mungkin terlihat seperti berikut:
Contoh ini menggunakan kueri untuk mengambil daftar item keranjang dan penawaran; kemudian, kode ini akan
mengambil detail tentang item inventaris dengan get_multi()
.
(Fungsi ini tidak menggunakan
nilai yang ditampilkan get_multi()
secara langsung. API ini memanggil
get_multi()
untuk mengambil semua detail inventaris ke dalam
cache sehingga dapat dibaca dengan cepat nanti.) get_multi
menggabungkan banyak "Get" menjadi satu permintaan. Namun, pengambilan kueri terjadi satu
per satu. Agar pengambilan tersebut terjadi secara bersamaan, tumpangkan kedua kueri tersebut:
Panggilan get_multi()
masih terpisah: bergantung pada hasil kueri, sehingga Anda
tidak dapat menggabungkannya dengan kueri.
Misalkan, aplikasi ini terkadang memerlukan keranjang, terkadang penawaran, dan terkadang keduanya. Anda ingin mengatur kode sehingga ada fungsi untuk mendapatkan keranjang dan fungsi untuk mendapatkan penawaran. Jika aplikasi Anda memanggil fungsi-fungsi tersebut secara bersamaan, idealnya kuerinya bisa "tumpang tindih". Untuk melakukannya, buat fungsi ini menjadi tasklet:
yield x, y
itu penting
tetapi mudah diabaikan. Jika ada dua pernyataan yield
yang berbeda, keduanya akan terjadi secara berurutan. Namun, meng-yield
sebuah tuple
tasklet berarti hasil paralel: tasklet yang dapat berjalan secara paralel
dan yield
menunggu sampai semuanya selesai dan
menampilkan hasilnya. (Dalam beberapa bahasa pemrograman, hal ini dikenal sebagai
penghalang.)
Jika satu potongan kode menjadi tasklet, Anda mungkin ingin
segera melakukan lebih banyak hal. Jika Anda melihat kode "sinkron" yang dapat berjalan secara paralel
dengan tasklet, sebaiknya jadikan juga sebagai tasklet.
Nantinya Anda dapat memparalelkannya dengan yield
paralel.
Jika Anda menulis fungsi permintaan (fungsi permintaan webapp2, fungsi tampilan Django, dll.) menjadi tasklet, fungsi tersebut tidak akan melakukan apa yang Anda inginkan: fungsi tersebut menghasilkan tetapi kemudian berhenti berjalan. Dalam situasi ini, Anda ingin mendekorasi fungsi dengan @ndb.synctasklet
.
@ndb.synctasklet
mirip dengan @ndb.tasklet
tetapi
diubah untuk memanggil get_result()
di tasklet.
Tindakan ini mengubah tasklet menjadi fungsi
yang menampilkan hasilnya dengan cara biasa.
Iterator Kueri di Tasklet
Untuk melakukan iterasi atas hasil kueri dalam tasklet, gunakan pola berikut:
Ini adalah tugas yang cocok dengan tasklet yang setara dengan berikut ini:
Tiga baris tebal di versi pertama adalah versi yang cocok untuk tugas
yang setara dengan satu baris tebal di versi kedua.
Tasklet hanya dapat ditangguhkan dengan kata kunci yield
.
Loop for yang tidak memiliki yield
tidak mengizinkan tasklet lainnya berjalan.
Anda mungkin bertanya-tanya mengapa kode ini malah menggunakan iterator kueri,
bukan mengambil semua entity menggunakan qry.fetch_async()
.
Aplikasi mungkin memiliki begitu banyak entity yang tidak muat di dalam RAM.
Mungkin Anda sedang mencari entity dan dapat berhenti melakukan iterasi setelah
menemukannya; tetapi Anda tidak dapat mengekspresikan kriteria pencarian Anda hanya dengan bahasa
kueri. Anda dapat menggunakan iterator untuk memuat entity yang akan diperiksa, lalu
keluar dari loop saat menemukan apa yang Anda inginkan.
Urlfetch Asinkron dengan NDB
Context
NDB memiliki fungsi
urlfetch()
asinkron yang diparalelkan dengan baik dengan tasklet NDB, misalnya:
Layanan URL-fetch memiliki API permintaan asinkronnya sendiri. Tindakan ini diperbolehkan, tetapi tidak selalu mudah digunakan dengan tasklet NDB.
Menggunakan Transaksi Asinkron
Transaksi juga dapat dilakukan secara asinkron. Anda dapat meneruskan fungsi yang ada ke ndb.transaction_async()
, atau menggunakan dekorator @ndb.transactional_async
.
Seperti fungsi asinkron lainnya, fungsi ini akan menampilkan Future
NDB:
Transaksi juga berfungsi dengan tasklet. Misalnya, kita dapat mengubah kode
update_counter
menjadi yield
saat menunggu pemblokiran
RPC:
Menggunakan Future.wait_any()
Terkadang, Anda ingin membuat beberapa permintaan asinkron
dan kembali setiap kali permintaan pertama selesai.
Anda dapat melakukannya menggunakan metode class ndb.Future.wait_any()
:
Sayangnya, tidak ada cara mudah untuk mengubahnya menjadi tasklet;
yield
paralel menunggu semua Future
selesai, termasuk yang tidak ingin Anda tunggu.