Praktik terbaik Cloud Datastore

Anda dapat menggunakan praktik terbaik yang tercantum di sini sebagai referensi cepat tentang hal-hal yang perlu diingat saat membangun aplikasi yang menggunakan Datastore. Jika Anda baru mulai menggunakan Datastore, halaman ini mungkin bukan tempat terbaik untuk memulai, karena tidak mengajarkan dasar-dasar cara menggunakan Datastore. Jika Anda adalah pengguna baru, sebaiknya mulai dengan Memulai Datastore.

Umum

  • Selalu gunakan karakter UTF-8 untuk nama namespace, nama jenis, nama properti, dan nama kunci kustom. Karakter non-UTF-8 yang digunakan dalam nama ini dapat mengganggu fungsi Datastore. Misalnya, karakter non-UTF-8 dalam nama properti dapat mencegah pembuatan indeks yang menggunakan properti tersebut.
  • Jangan gunakan garis miring (/) dalam nama jenis atau nama kunci kustom. Garis miring depan dalam nama ini dapat mengganggu fungsi di masa mendatang.
  • Hindari menyimpan informasi sensitif di Cloud Project ID. Project ID Cloud mungkin dipertahankan di luar masa berlaku project Anda.
  • Sebagai praktik terbaik kepatuhan data, sebaiknya jangan simpan informasi sensitif dalam nama entity Datastore atau nama properti entity.

Panggilan API

  • Gunakan operasi batch untuk operasi baca, tulis, dan hapus, bukan operasi tunggal. Operasi batch lebih efisien karena melakukan beberapa operasi dengan overhead yang sama seperti operasi tunggal.
  • Jika transaksi gagal, pastikan Anda mencoba melakukan rollback transaksi. Rollback akan meminimalkan latensi percobaan ulang untuk permintaan lain yang bersaing untuk resource yang sama dalam transaksi. Perhatikan bahwa rollback itu sendiri mungkin gagal, sehingga rollback hanya boleh berupa upaya terbaik.
  • Gunakan panggilan asinkron jika bisa, bukan panggilan sinkron. Panggilan asinkron meminimalkan dampak latensi. Misalnya, pertimbangkan aplikasi yang memerlukan hasil lookup() sinkron dan hasil kueri sebelum dapat merender respons. Jika lookup() dan kueri tidak memiliki dependensi data, tidak perlu menunggu secara sinkron hingga lookup() selesai sebelum memulai kueri.

Entity

  • Mengelompokkan data yang sangat terkait dalam grup entity. Entity group memungkinkan kueri ancestor, yang menampilkan hasil yang sangat konsisten. Kueri ancestor juga memindai entity group dengan cepat dengan I/O minimal karena entity dalam entity group disimpan di tempat yang secara fisik berdekatan di server Datastore.
  • Hindari penulisan ke entity group lebih dari sekali per detik. Menulis dengan kecepatan berkelanjutan di atas batas tersebut akan membuat operasi baca yang konsisten tertunda pada akhirnya lebih lama, menyebabkan waktu tunggu habis untuk operasi baca yang sangat konsisten, dan menghasilkan performa keseluruhan aplikasi yang lebih lambat. Penulisan batch atau transaksional ke entity group dihitung sebagai satu penulisan saja terhadap batas ini.
  • Jangan sertakan entitas yang sama (menurut kunci) beberapa kali dalam commit yang sama. Menyertakan entitas yang sama beberapa kali dalam commit yang sama dapat memengaruhi latensi Datastore.

Kunci

  • Nama kunci dibuat secara otomatis jika tidak diberikan saat pembuatan entity. Kunci tersebut dialokasikan agar didistribusikan secara merata di ruang kunci.
  • Untuk kunci yang menggunakan nama kustom, selalu gunakan karakter UTF-8 kecuali garis miring (/). Karakter non-UTF-8 mengganggu berbagai proses seperti mengimpor cadangan Datastore ke Google BigQuery. Garis miring depan dapat mengganggu fungsi di masa mendatang.
  • Untuk kunci yang menggunakan ID numerik:
    • Jangan gunakan angka negatif untuk ID. ID negatif dapat mengganggu pengurutan.
    • Jangan gunakan nilai 0(nol) untuk ID. Jika melakukannya, Anda akan mendapatkan ID yang dialokasikan secara otomatis.
    • Jika Anda ingin menetapkan ID numerik Anda sendiri secara manual ke entitas yang Anda buat, minta aplikasi Anda untuk mendapatkan blok ID dengan metode allocateIds(). Tindakan ini akan mencegah Datastore menetapkan salah satu ID numerik manual Anda ke entity lain.
  • Jika Anda menetapkan ID numerik manual atau nama kustom Anda sendiri ke entity yang Anda buat, jangan gunakan nilai yang meningkat secara monoton seperti:

    1, 2, 3, ,
    "Customer1", "Customer2", "Customer3", .
    "Product 1", "Product 2", "Product 3", .
    

    Jika aplikasi menghasilkan traffic yang besar, penomoran berurutan tersebut dapat menyebabkan hotspot yang memengaruhi latensi Datastore. Untuk menghindari masalah ID numerik berurutan, dapatkan ID numerik dari metode allocateIds(). Metode allocateIds() menghasilkan urutan ID numerik yang didistribusikan dengan baik.

  • Dengan menentukan kunci atau menyimpan nama yang dihasilkan, Anda nantinya dapat melakukan lookup() yang konsisten pada entity tersebut tanpa perlu mengeluarkan kueri untuk menemukan entity.

Indeks

  • Jika properti tidak akan pernah diperlukan untuk kueri, kecualikan properti dari indeks. Mengindeks properti yang tidak perlu dapat meningkatkan latensi untuk mencapai konsistensi, dan meningkatkan biaya penyimpanan entri indeks.
  • Hindari memiliki terlalu banyak indeks komposit. Penggunaan indeks komposit yang berlebihan dapat menyebabkan peningkatan latensi untuk mencapai konsistensi, dan peningkatan biaya penyimpanan entri indeks. Jika Anda perlu menjalankan kueri ad hoc pada set data besar tanpa indeks yang ditentukan sebelumnya, gunakan Google BigQuery.
  • Jangan mengindeks properti dengan nilai yang meningkat secara monoton (seperti stempel waktu NOW()). Mempertahankan indeks tersebut dapat menyebabkan hotspot yang memengaruhi latensi Datastore untuk aplikasi dengan tingkat baca dan tulis yang tinggi. Untuk panduan lebih lanjut tentang cara menangani properti monoton, lihat Rasio operasi baca/tulis yang tinggi untuk rentang kunci yang sempit di bawah.

Properti

  • Selalu gunakan karakter UTF-8 untuk properti berjenis string. Karakter non-UTF-8 dalam properti jenis string dapat mengganggu kueri. Jika Anda perlu menyimpan data dengan karakter non-UTF-8, gunakan string byte.
  • Jangan gunakan titik dalam nama properti. Titik dalam nama properti mengganggu pengindeksan properti entity tersemat.

Kueri

  • Jika Anda hanya perlu mengakses kunci dari hasil kueri, gunakan kueri khusus kunci. Kueri khusus kunci menampilkan hasil dengan latensi dan biaya yang lebih rendah daripada mengambil seluruh entity.
  • Jika Anda hanya perlu mengakses properti tertentu dari entity, gunakan kueri proyeksi. Kueri proyeksi menampilkan hasil dengan latensi dan biaya yang lebih rendah daripada mengambil seluruh entity.
  • Demikian pula, jika Anda hanya perlu mengakses properti yang disertakan dalam filter kueri (misalnya, yang tercantum dalam klausa order by), gunakan kueri proyeksi.
  • Jangan gunakan offset. Sebagai gantinya, gunakan kursor. Penggunaan offset hanya akan menghindari ditampilkannya entity yang dilewati ke aplikasi Anda, tetapi entity ini masih diambil secara internal. Entity yang dilewati memengaruhi latensi kueri, dan aplikasi Anda akan dikenai biaya untuk operasi baca yang diperlukan untuk mengambilnya.
  • Jika Anda memerlukan konsistensi yang kuat untuk kueri, gunakan kueri ancestor. (Untuk menggunakan kueri ancestor, Anda harus menyusun data untuk konsistensi yang kuat terlebih dahulu.) Kueri ancestor menampilkan hasil yang sangat konsisten. Perhatikan bahwa kueri khusus kunci non-ancestor yang diikuti dengan lookup() tidak menampilkan hasil yang kuat, karena kueri khusus kunci non-ancestor dapat mendapatkan hasil dari indeks yang tidak konsisten pada saat kueri.

Mendesain untuk penskalaan

Pembaruan untuk satu grup entity

Satu entity group di Datastore tidak boleh diperbarui terlalu cepat.

Jika Anda menggunakan Datastore, Google merekomendasikan agar Anda mendesain aplikasi sehingga tidak perlu memperbarui grup entity lebih dari sekali per detik. Ingat bahwa entity tanpa induk dan tanpa turunan adalah grup entity-nya sendiri. Jika Anda memperbarui grup entity terlalu cepat, penulisan Datastore Anda akan memiliki latensi yang lebih tinggi, waktu tunggu habis, dan jenis error lainnya. Hal ini dikenal sebagai persaingan.

Kecepatan operasi tulis datastore ke satu grup entity terkadang dapat melebihi batas satu penulisan per detik, sehingga pengujian beban mungkin tidak menunjukkan masalah ini.

Kecepatan baca/tulis yang tinggi untuk rentang kunci yang sempit

Hindari kecepatan baca atau tulis yang tinggi ke kunci Datastore yang mirip secara leksikografis.

Datastore dibuat berdasarkan database NoSQL Google, Bigtable, dan tunduk pada karakteristik performa Bigtable. Bigtable diskalakan dengan melakukan sharding baris ke tablet terpisah, dan baris ini diurutkan secara leksikografis menurut kunci.

Jika menggunakan Datastore, Anda bisa mendapatkan penulisan yang lambat karena tablet panas jika Anda mengalami peningkatan mendadak dalam kecepatan penulisan ke rentang kunci kecil yang melebihi kapasitas satu server tablet. Bigtable pada akhirnya akan membagi ruang kunci untuk mendukung beban yang tinggi.

Batas untuk operasi baca biasanya jauh lebih tinggi daripada operasi tulis, kecuali jika Anda membaca dari satu kunci dengan kecepatan tinggi. Bigtable tidak dapat memisahkan satu kunci ke lebih dari satu tablet.

Tablet panas dapat diterapkan ke rentang kunci yang digunakan oleh kunci entity dan indeks.

Dalam beberapa kasus, hotspot Datastore dapat memiliki dampak yang lebih luas terhadap aplikasi daripada mencegah pembacaan atau penulisan ke rentang kunci yang kecil. Misalnya, tombol pintas mungkin dibaca atau ditulis selama startup instance, sehingga menyebabkan permintaan pemuatan gagal.

Secara default, Datastore mengalokasikan kunci menggunakan algoritma tersebar. Dengan demikian, Anda biasanya tidak akan mengalami hotspot pada penulisan Datastore jika membuat entity baru dengan kecepatan penulisan tinggi menggunakan kebijakan alokasi ID default. Ada beberapa kasus ekstrem saat Anda dapat mengalami masalah ini:

  • Jika Anda membuat entitas baru dengan kecepatan yang sangat tinggi menggunakan kebijakan alokasi ID sekuens lama.

  • Jika Anda membuat entitas baru dengan kecepatan yang sangat tinggi dan mengalokasikan ID Anda sendiri yang meningkat secara monoton.

  • Jika Anda membuat entity baru dengan kecepatan yang sangat tinggi untuk jenis yang sebelumnya memiliki sangat sedikit entity yang ada. Bigtable akan dimulai dengan semua entitas di server tablet yang sama dan akan memerlukan waktu beberapa saat untuk memisahkan rentang kunci ke server tablet terpisah.

  • Anda juga akan melihat masalah ini jika membuat entity baru dengan kecepatan tinggi dengan properti yang diindeks secara monoton seperti stempel waktu, karena properti ini adalah kunci untuk baris dalam tabel indeks di Bigtable.

  • Datastore menambahkan namespace dan jenis grup entity root ke kunci baris Bigtable. Anda dapat mencapai hotspot jika mulai menulis ke namespace atau jenis baru tanpa meningkatkan traffic secara bertahap.

Jika Anda memiliki kunci atau properti yang diindeks yang akan meningkat secara monoton, Anda dapat menambahkan hash acak di awal untuk memastikan kunci di-shard ke beberapa tablet.

Demikian pula, jika Anda perlu membuat kueri pada properti yang meningkat (atau menurun) secara monoton menggunakan pengurutan atau pemfilteran, Anda dapat mengindeks pada properti baru, yang nilai monotonnya diawali dengan nilai yang memiliki kardinalitas tinggi di seluruh set data, tetapi umum untuk semua entity dalam cakupan kueri yang ingin Anda lakukan. Misalnya, jika Anda ingin membuat kueri untuk entri berdasarkan stempel waktu, tetapi hanya perlu menampilkan hasil untuk satu pengguna dalam satu waktu, Anda dapat menambahkan awalan stempel waktu dengan ID pengguna dan mengindeks properti baru tersebut. Hal ini masih akan mengizinkan kueri dan hasil yang diurutkan untuk pengguna tersebut, tetapi keberadaan ID pengguna akan memastikan indeks itu sendiri di-shard dengan baik.

Untuk penjelasan yang lebih mendetail tentang masalah ini, lihat postingan blog Ikai Lan tentang menyimpan nilai yang meningkat secara monoton di Datastore.

Meningkatkan traffic

Tingkatkan traffic secara bertahap ke jenis Datastore baru atau bagian ruang kunci.

Anda harus meningkatkan traffic ke jenis Datastore baru secara bertahap agar Bigtable memiliki cukup waktu untuk memisahkan tablet seiring traffic yang meningkat. Sebaiknya gunakan maksimum 500 operasi per detik ke jenis Datastore baru, lalu tingkatkan traffic sebesar 50% setiap 5 menit. Secara teori, Anda dapat meningkatkan kapasitas hingga 740 ribu operasi per detik setelah 90 menit menggunakan jadwal peningkatan ini. Pastikan operasi tulis didistribusikan secara relatif merata di seluruh rentang kunci. SRE kami menyebutnya sebagai aturan "500/50/5".

Pola peningkatan bertahap ini sangat penting jika Anda mengubah kode untuk berhenti menggunakan jenis A dan menggunakan jenis B. Cara sederhana untuk menangani migrasi ini adalah dengan mengubah kode Anda untuk membaca jenis B, dan jika tidak ada, baca jenis A. Namun, hal ini dapat menyebabkan peningkatan traffic secara tiba-tiba ke jenis baru dengan sebagian ruang kunci yang sangat kecil. Bigtable mungkin tidak dapat membagi tablet secara efisien jika ruang kunci jarang.

Masalah yang sama juga dapat terjadi jika Anda memigrasikan entitas untuk menggunakan rentang kunci yang berbeda dalam jenis yang sama.

Strategi yang Anda gunakan untuk memigrasikan entity ke jenis atau kunci baru akan bergantung pada model data Anda. Di bawah ini adalah contoh strategi, yang dikenal sebagai "Parallel Reads". Anda perlu menentukan apakah strategi ini efektif untuk data Anda atau tidak. Pertimbangan penting adalah dampak biaya operasi paralel selama migrasi.

Baca dari entity atau kunci lama terlebih dahulu. Jika tidak ada, Anda dapat membaca dari entity atau kunci baru. Kecepatan baca yang tinggi untuk entity yang tidak ada dapat menyebabkan hotspotting, jadi Anda harus memastikan untuk secara bertahap meningkatkan beban. Strategi yang lebih baik adalah menyalin entity lama ke yang baru, lalu menghapus yang lama. Tingkatkan operasi baca paralel secara bertahap untuk memastikan bahwa ruang kunci baru dibagi dengan baik.

Strategi yang memungkinkan untuk secara bertahap meningkatkan operasi baca atau tulis pada jenis baru adalah dengan menggunakan hash deterministik ID pengguna untuk mendapatkan persentase acak pengguna yang menulis entity baru. Pastikan bahwa hasil hash ID pengguna tidak condong ke pengguna tertentu karena fungsi acak Anda atau perilaku pengguna.

Sementara itu, jalankan tugas Dataflow untuk menyalin semua data Anda dari entitas atau kunci lama ke entitas atau kunci baru. Tugas batch Anda harus menghindari penulisan ke kunci berurutan untuk mencegah hotspot Bigtable. Setelah tugas batch selesai, Anda hanya dapat membaca dari lokasi baru.

Perbaikan strategi ini dapat berupa migrasi pengguna dalam batch kecil sekaligus. Tambahkan kolom ke entitas pengguna yang melacak status migrasi pengguna tersebut. Pilih sekelompok pengguna yang akan dimigrasikan berdasarkan hash ID pengguna. Tugas Mapreduce atau Dataflow akan memigrasikan kunci untuk kumpulan pengguna tersebut. Pengguna yang memiliki migrasi yang sedang berlangsung akan menggunakan pembacaan paralel.

Perhatikan bahwa Anda tidak dapat melakukan roll back dengan mudah, kecuali Anda melakukan operasi tulis ganda pada entity lama dan baru selama fase migrasi. Tindakan ini akan meningkatkan biaya Datastore yang dikeluarkan.

Penghapusan

Hindari menghapus entity Datastore dalam jumlah besar di rentang kunci yang kecil.

Bigtable secara berkala menulis ulang tabelnya untuk menghapus entri yang dihapus, dan mengatur ulang data Anda sehingga operasi baca dan tulis menjadi lebih efisien. Proses ini dikenal sebagai pemadatan.

Jika Anda menghapus sejumlah besar entity Datastore di seluruh rentang kunci yang kecil, kueri di bagian indeks ini akan lebih lambat hingga pemadatan selesai. Dalam kasus yang ekstrem, kueri Anda mungkin kehabisan waktu tunggu sebelum menampilkan hasil.

Menggunakan nilai stempel waktu untuk kolom yang diindeks untuk mewakili waktu habis masa berlaku entitas adalah anti-pola. Untuk mengambil entity yang telah berakhir masa berlakunya, Anda harus membuat kueri terhadap kolom yang diindeks ini, yang kemungkinan terletak di bagian ruang kunci yang tumpang-tindih dengan entri indeks untuk entity yang baru saja dihapus.

Anda dapat meningkatkan performa dengan "kueri sharding", yang menambahkan string dengan panjang tetap ke stempel waktu habis masa berlaku. Indeks diurutkan pada string lengkap, sehingga entity pada stempel waktu yang sama akan berada di seluruh rentang kunci indeks. Anda menjalankan beberapa kueri secara paralel untuk mengambil hasil dari setiap shard.

Solusi yang lebih lengkap untuk masalah stempel waktu habis masa berlaku adalah menggunakan "nomor generasi" yang merupakan penghitung global yang diperbarui secara berkala. Nomor pembuatan ditambahkan ke stempel waktu masa berlaku sehingga kueri diurutkan berdasarkan nomor pembuatan, lalu shard, lalu stempel waktu. Penghapusan entity lama terjadi pada generasi sebelumnya. Setiap entitas yang tidak dihapus harus memiliki nomor generasi yang ditingkatkan. Setelah penghapusan selesai, Anda akan melanjutkan ke generasi berikutnya. Kueri terhadap generasi lama akan berperforma buruk hingga pemadatan selesai. Anda mungkin perlu menunggu beberapa generasi selesai sebelum membuat kueri indeks untuk mendapatkan daftar entitas yang akan dihapus, guna mengurangi risiko hasil yang hilang karena konsistensi akhir.

Sharding dan replikasi

Gunakan sharding atau replikasi untuk kunci Datastore yang sering digunakan.

Anda dapat menggunakan replikasi jika perlu membaca sebagian rentang kunci dengan kecepatan yang lebih tinggi dari yang diizinkan Bigtable. Dengan menggunakan strategi ini, Anda akan menyimpan N salinan entity yang sama sehingga memungkinkan kecepatan pembacaan yang lebih tinggi N kali lipat daripada yang didukung oleh satu entity.

Anda dapat menggunakan sharding jika perlu menulis ke sebagian rentang kunci dengan kecepatan yang lebih tinggi daripada yang diizinkan Bigtable. Sharding membagi entitas menjadi bagian-bagian yang lebih kecil.

Beberapa kesalahan umum saat melakukan sharding meliputi:

  • Sharding menggunakan awalan waktu. Saat waktu beralih ke awalan berikutnya, bagian yang tidak terpecah baru akan menjadi hotspot. Sebagai gantinya, Anda harus secara bertahap melakukan rollover sebagian operasi tulis ke awalan baru.

  • Hanya melakukan sharding pada entitas terpanas. Jika Anda melakukan sharding pada sebagian kecil dari jumlah total entity, mungkin tidak ada baris yang memadai di antara entity hot untuk memastikan bahwa entity tersebut tetap berada di bagian yang berbeda.

Langkah selanjutnya