Catatan: Developer yang membuat aplikasi baru sangat dianjurkan untuk menggunakan Library Klien NDB, yang memiliki beberapa manfaat dibandingkan dengan library klien ini, seperti menyimpan entity dalam cache secara otomatis melalui Memcache API. Jika saat ini Anda menggunakan Library Klien DB versi lama, baca Panduan Migrasi DB ke NDB
Dokumen ini menjelaskan model data untuk objek yang disimpan di Datastore, cara kueri disusun menggunakan API, dan cara transaksi diproses.
Entity
Objek di Datastore dikenal sebagai entity. Entity memiliki satu atau beberapa properti yang telah diberi nama, yang masing-masing dapat memiliki satu atau beberapa nilai. Nilai properti dapat dimiliki oleh berbagai jenis data, termasuk bilangan bulat, angka floating point, string, tanggal, dan data biner. Kueri di properti dengan beberapa nilai menguji apakah salah satu nilai memenuhi kriteria kueri. Hal ini membuat properti tersebut berguna untuk pengujian keanggotaan.
Jenis, kunci, dan ID
Setiap entity Datastore memiliki jenis tertentu, yang mengategorikan entity untuk tujuan kueri; misalnya, aplikasi sumber daya manusia mungkin mewakili setiap karyawan di sebuah perusahaan dengan entity jenis Employee
. Selain itu, setiap entity memiliki kunci sendiri, yang mengidentifikasinya secara unik. Kunci ini terdiri dari komponen berikut:
- Jenis entity
- ID, yang bisa berupa
- string nama kunci
- ID bilangan bulat
- Jalur ancestor opsional yang mencari entity dalam hierarki Datastore
ID ditetapkan saat entity dibuat. Karena merupakan bagian dari kunci entity, maka entity akan dikaitkan secara permanen dengan entity dan tidak dapat diubah. Ini dapat ditetapkan dengan salah satu dari dua cara berikut:
- Aplikasi Anda dapat menentukan string nama kunci sendiri untuk entity.
- Anda dapat meminta Datastore menetapkan ID numerik bilangan bulat secara otomatis ke entity.
Jalur ancestor
Entity dalam Cloud Datastore membentuk ruang yang terstruktur secara hierarkis, yang menyerupai struktur direktori sistem file. Saat membuat entity, Anda dapat memilih untuk menetapkan entity lain sebagai parent; entity barunya adalah turunan dari parent entity tersebut (perlu diperhatikan bahwa tidak seperti dalam sistem file, parent entity tidak harus benar-benar ada). Entity tanpa parent adalah root entity. Kaitan entity dan induknya bersifat permanen, dan tidak dapat diubah setelah entity tersebut dibuat. Cloud Datastore tidak akan pernah menetapkan ID numerik yang sama ke dua entity dengan parent yang sama, atau ke dua root entity (yang tidak memiliki parent).
Induk, induk dari induk, dan seterusnya dari suatu entity secara rekursif adalah ancestor-nya; turunannya, turunan dari turunannya, dan seterusnya adalah turunannya. Root entity dan semua turunannya termasuk dalam entity group yang sama. Urutan entity yang dimulai dengan root entity dan berlanjut dari parent ke turunan, yang mengarah ke entity tertentu, merupakan jalur ancestor entity tersebut. Kunci lengkap yang mengidentifikasi entity terdiri dari urutan pasangan jenis-ID yang menentukan jalur ancestor-nya dan berakhir dengan entity itu sendiri:
[Person:GreatGrandpa, Person:Grandpa, Person:Dad, Person:Me]
Untuk root entity, jalur ancestor kosong dan kuncinya hanya terdiri dari jenis dan ID entity itu sendiri:
[Person:GreatGrandpa]
Konsep ini diilustrasikan oleh diagram berikut:
Kueri dan indeks
Selain mengambil entity dari Datastore secara langsung dengan kuncinya, aplikasi dapat menjalankan kueri untuk mengambilnya berdasarkan nilai propertinya ini. Kueri beroperasi di entity jenis tertentu; kueri ini dapat menentukan filter untuk nilai properti, kunci, dan ancestor entity, dan dapat menampilkan nol entity atau beberapa entity sebagai hasil. Kueri juga dapat menentukan urutan pengurutan untuk mengurutkan hasil berdasarkan nilai propertinya. Hasilnya mencakup semua entity yang memiliki setidaknya satu nilai (mungkin null) untuk setiap properti yang disebutkan dalam filter dan tata urutan, dan yang nilai propertinya memenuhi semua kriteria filter yang ditentukan. Kueri dapat menampilkan seluruh entity, proyeksi entitas, atau hanya entity kunci.
Kueri tipikal mencakup hal berikut:
- Jenis entity tempat kueri diterapkan
- Nol atau beberapa filter berdasarkan nilai properti, kunci, dan ancestor entity
- Nol atau beberapa pilah urutan untuk mengurutkan hasil
Catatan: Untuk menghemat memori dan meningkatkan performa, kueri harus, jika memungkinkan, menentukan batas jumlah hasil yang ditampilkan.
Kueri juga dapat menyertakan filter ancestor yang membatasi hasil hanya untuk entity group yang diturunkan dari ancestor yang ditentukan. Kueri semacam ini dikenal sebagai kueri ancestor. Secara default, kueri ancestor menampilkan hasil yang sangat konsisten, yang dijamin selalu diperbarui dengan perubahan terbaru dalam data. Sebaliknya, kueri non-ancestor tidak hanya dapat menampilkan satu entity group, namun seluruh Datastore, tetapi kueri bersifat konsisten yang tertunda dan dapat menampilkan hasil yang tidak berlaku lagi. Jika konsistensi yang kuat penting untuk aplikasi Anda, Anda mungkin perlu mempertimbangkan hal ini saat menyusun data, menempatkan entity terkait dalam entity group yang sama sehingga dapat diambil dengan ancestor, bukan kueri non-ancestor; lihat Penyusunan Data untuk Konsistensi Kuat untuk informasi selengkapnya.
App Engine menentukan terlebih dahulu indeks sederhana di setiap properti entity.
Aplikasi App Engine dapat menentukan indeks kustom lebih lanjut dalam
file konfigurasi
indeks bernama index.yaml
. Server pengembangan secara otomatis
menambahkan saran ke file ini karena menemukan kueri yang tidak dapat dijalankan dengan indeks yang ada.
Anda dapat menyesuaikan indeks secara manual dengan mengedit file sebelum mengupload aplikasi.
Catatan: Mekanisme kueri berbasis indeks mendukung berbagai kueri dan cocok untuk sebagian besar aplikasi. Namun, mekanisme ini tidak mendukung beberapa jenis kueri yang umum digunakan dalam teknologi database lainnya: khususnya, join dan kueri agregat tidak didukung dalam mesin kueri Datastore. Lihat halaman Kueri Datastore untuk mengetahui batasan pada kueri Datastore.
Transaksi
Setiap upaya untuk menyisipkan, memperbarui, atau menghapus entity terjadi dalam konteks transaksi. Satu transaksi dapat mencakup sejumlah operasi tersebut. Untuk menjaga konsistensi data, transaksi tersebut memastikan bahwa semua operasi yang ada di dalamnya diterapkan ke Datastore sebagai unit atau, jika salah satu operasi gagal, tidak ada satu pun operasi yang diterapkan.
Anda dapat melakukan beberapa tindakan terhadap sebuah entity dalam satu transaksi. Misalnya, untuk menambahkan kolom penghitung dalam objek, Anda perlu membaca nilai penghitung, menghitung nilai baru, lalu menyimpannya kembali. Tanpa transaksi, proses lain mungkin dapat menambah penghitung antara waktu Anda membaca nilai dan saat Anda memperbaruinya, sehingga aplikasi Anda menimpa nilai yang diperbarui. Melakukan pembacaan, perhitungan, dan penulisan dalam satu transaksi memastikan bahwa tidak ada proses lain yang dapat mengganggu proses penambahan.
Transaksi dan entity group
Hanya kueri ancestor yang diizinkan dalam transaksi: yaitu, setiap kueri transaksi harus dibatasi ke satu entity group. Transaksi itu sendiri dapat berlaku untuk beberapa entity, yang dapat dimiliki oleh satu grup entity atau (dalam kasus transaksi lintas grup) hingga maksimum dua puluh lima entity group yang berbeda.
Datastore menggunakan konkurensi optimis untuk mengelola transaksi. Jika dua transaksi atau lebih mencoba mengubah entity group yang sama secara bersamaan (baik memperbarui entity yang sudah ada atau membuat entity baru), transaksi pertama yang di-commit akan berhasil dan transaksi lainnya akan gagal saat di-commit. Transaksi lain ini kemudian dapat dicoba lagi dalam data yang diperbarui. Perlu diperhatikan bahwa hal ini akan membatasi jumlah penulisan serentak yang dapat Anda lakukan terhadap entity mana pun dalam entity group tertentu.
Transaksi lintas grup
Transaksi di entity yang termasuk dalam entity group yang berbeda disebut transaksi lintas grup (XG). Transaksi dapat diterapkan pada maksimum dua puluh lima entity group, dan akan berhasil selama tidak ada transaksi serentak yang menyentuh salah satu entity group tempat transaksi tersebut diterapkan. Hal ini memberikan lebih banyak fleksibilitas kepada Anda dalam mengatur data, karena Anda tidak dipaksa untuk menempatkan potongan data yang berbeda dalam ancestor yang sama hanya untuk melakukan penulisan atomik di data tersebut.
Seperti dalam transaksi grup tunggal, Anda tidak dapat melakukan kueri non-ancestor dalam transaksi XG. Namun, Anda dapat menjalankan kueri ancestor di entity group yang terpisah. Kueri nontransaksi (non-ancestor) dapat melihat semua, beberapa, atau tidak ada hasil dari transaksi yang sudah di-commit sebelumnya. (Untuk latar belakang tentang masalah ini, lihat Penulisan Datastore dan Visibilitas Data.) Namun, kueri non-transaksi seperti ini lebih cenderung melihat hasil dari transaksi XG yang di-commit sebagian daripada dari transaksi grup tunggal yang di-commit sebagian.
Transaksi XG yang hanya menyentuh satu entity group memiliki performa dan biaya yang sama persis dengan transaksi non-XG grup tunggal. Dalam transaksi XG yang menyentuh beberapa entity group, biaya operasi akan sama seperti jika dilakukan dalam transaksi non-XG, tetapi mungkin mengalami latensi yang lebih tinggi.
Penulisan dan visibilitas data Datastore
Data ditulis ke Datastore dalam dua fase:
- Dalam fase Commit, data entity dicatat dalam log transaksi dari sebagian besar replika, dan setiap replika yang tidak dicatat akan ditandai sebagai tidak memiliki log terbaru.
- Fase Penerapan terjadi secara independen di setiap replika, dan terdiri dari dua tindakan yang dilakukan secara paralel:
- Data entity ditulis dalam replika tersebut.
- Baris indeks untuk entity ditulis dalam replika tersebut. (Perhatikan bahwa ini bisa memakan waktu lebih lama daripada penulisan data itu sendiri.)
Operasi tulis segera ditampilkan setelah fase Commit, lalu fase Penerapan kemudian terjadi secara asinkron, mungkin pada waktu yang berbeda di setiap replika, dan mungkin dengan penundaan beberapa ratus milidetik atau lebih sejak penyelesaian fase Commit. Jika terjadi kegagalan selama fase Commit, ada percobaan ulang otomatis; tetapi jika kegagalan berlanjut, Datastore akan menampilkan pesan error yang diterima aplikasi Anda sebagai pengecualian. Jika fase Commit berhasil, tetapi Apply gagal dalam replika tertentu, Penerapan akan di-roll hingga selesai dalam replika tersebut jika salah satu hal berikut terjadi:
- Datastore berkala memeriksa tugas Commit yang belum selesai dan menerapkannya.
- Operasi tertentu (
get
,put
,delete
, dan kueri ancestor) yang menggunakan grup entity yang terpengaruh menyebabkan perubahan apa pun yang telah di-commit tetapi belum diterapkan untuk diselesaikan dalam replika tempatnya dieksekusi sebelum melanjutkan dengan operasi baru.
Perilaku tulis ini dapat memiliki beberapa implikasi tentang bagaimana dan kapan data terlihat oleh aplikasi Anda di berbagai bagian dari fase Commit dan Penerapan:
- Jika operasi tulis melaporkan error waktu tunggu, operasi tersebut tidak dapat ditentukan (tanpa mencoba membaca data) apakah operasi berhasil atau gagal.
- Karena Datastore mendapatkan dan kueri ancestor menerapkan modifikasi yang belum dilakukan pada replika tempatnya dieksekusi, operasi ini akan selalu melihat tampilan yang konsisten dari semua transaksi sebelumnya yang berhasil. Hal ini berarti bahwa operasi
get
(mencari entity yang diupdate berdasarkan kuncinya) dijamin melihat versi terbaru entity tersebut. - Kueri non-ancestor dapat menampilkan hasil yang sudah tidak berlaku karena mungkin dieksekusi di replika yang transaksi terbarunya belum diterapkan. Hal ini dapat terjadi meskipun operasi yang dilakukan dipastikan akan menerapkan transaksi yang belum diselesaikan karena kueri dapat dieksekusi dalam replika yang berbeda dari operasi sebelumnya.
- Waktu perubahan serentak dapat memengaruhi hasil kueri non-ancestor. Jika entity awalnya memenuhi kueri, tetapi kemudian diubah sehingga tidak lagi memenuhi kueri, entity tersebut mungkin masih disertakan dalam kumpulan hasil kueri jika perubahan belum diterapkan ke indeks dalam replika tempat kueri dieksekusi.
Statistik Datastore
Datastore menyimpan statistik tentang data yang disimpan untuk aplikasi, seperti berapa banyak entity yang ada dari jenis tertentu atau berapa banyak ruang yang digunakan oleh nilai properti dari jenis tertentu. Anda dapat melihat statistik ini di halaman Dasbor Datastore konsol Google Cloud. Anda juga dapat menggunakan Datastore API untuk mengakses nilai ini secara terprogram dari dalam aplikasi dengan membuat kueri untuk entity bernama khusus; lihat Statistik Datastore di Python 2 untuk mengetahui informasi selengkapnya.
Contoh Python 2 Datastore
Di Python API, model menjelaskan jenis entity, termasuk jenis dan konfigurasi untuk propertinya. Aplikasi menentukan model menggunakan class Python, dengan atribut class yang menjelaskan properti. Nama class menjadi nama jenis entity. Entity jenis tertentu diwakili oleh instance class model, dengan atribut instance yang mewakili nilai properti. Untuk membuat entity baru, Anda harus membuat objek dari class yang diinginkan, menetapkan atributnya, lalu menyimpannya (dengan memanggil metode seperti put()
):
import datetime
from google.appengine.ext import db
from google.appengine.api import users
class Employee(db.Model):
name = db.StringProperty(required=True)
role = db.StringProperty(required=True,
choices=set(["executive", "manager", "producer"]))
hire_date = db.DateProperty()
new_hire_training_completed = db.BooleanProperty(indexed=False)
email = db.StringProperty()
e = Employee(name="John",
role="manager",
email=users.get_current_user().email())
e.hire_date = datetime.datetime.now().date()
e.put()
Datastore API menyediakan dua antarmuka untuk kueri: antarmuka objek kueri dan bahasa kueri mirip SQL yang disebut GQL. Kueri menampilkan entity dalam bentuk instance class model:
training_registration_list = ["Alfred.Smith@example.com",
"jharrison@example.com",
"budnelson@example.com"]
employees_trained = db.GqlQuery("SELECT * FROM Employee WHERE email IN :1",
training_registration_list)
for e in employees_trained:
e.new_hire_training_completed = True
db.put(e)