Indeks Datastore

Catatan: Developer yang membangun aplikasi baru sangat dianjurkan untuk menggunakan Library Klien NDB, yang memiliki beberapa manfaat jika dibandingkan dengan library klien ini, seperti caching entity otomatis melalui Memcache API. Jika saat ini Anda menggunakan Library Klien DB versi lama, baca Panduan Migrasi DB ke NDB

App Engine telah menentukan 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 saat 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 kueri Datastore.

Definisi dan struktur indeks

Indeks ditentukan pada daftar properti dari jenis entity tertentu, dengan urutan yang sesuai (menaik atau menurun) untuk setiap properti. Untuk digunakan dengan kueri ancestor, indeks juga dapat menyertakan entity ancestor secara opsional.

Tabel indeks berisi kolom untuk setiap properti yang dinamai dalam definisi indeks. Setiap baris tabel mewakili entity di Datastore yang merupakan kemungkinan hasil untuk kueri berdasarkan indeks. Entity disertakan dalam indeks hanya jika memiliki nilai terindeks yang ditetapkan untuk setiap properti yang digunakan dalam indeks; jika definisi indeks mengacu pada properti di mana entity tidak memiliki value, entity tersebut tidak akan muncul dalam indeks, dan oleh karena itu tidak akan ditampilkan sebagai hasil untuk kueri apa pun berdasarkan indeks tersebut.

Catatan: Datastore membedakan antara entity yang tidak memiliki properti dan entity yang memiliki properti dengan nilai null (None). Jika Anda secara eksplisit menetapkan nilai null ke properti entity, entity tersebut dapat disertakan dalam hasil kueri yang merujuk ke properti tersebut.

Catatan: Indeks yang terdiri dari beberapa properti mewajibkan agar masing-masing properti tidak ditetapkan ke tidak terindeks.

Baris tabel indeks diurutkan terlebih dahulu berdasarkan ancestor, lalu berdasarkan nilai properti, dalam urutan yang ditentukan dalam definisi indeks. Indeks sempurna untuk kueri, yang memungkinkan kueri dijalankan dengan paling efisien, ditentukan pada properti berikut, secara berurutan:

  1. Properti yang digunakan dalam filter kesetaraan
  2. Properti yang digunakan dalam filter ketidaksetaraan (maksimal satu)
  3. Properti yang digunakan dalam tata urutan

Hal ini memastikan bahwa semua hasil untuk setiap kemungkinan eksekusi kueri akan muncul dalam baris tabel yang berurutan. Datastore menjalankan kueri menggunakan indeks sempurna dengan langkah-langkah berikut:

  1. Mengidentifikasi indeks yang sesuai dengan jenis, properti filter, operator filter, dan tata urutan pada kueri.
  2. Memindai dari awal indeks hingga entity pertama yang memenuhi semua kondisi filter kueri.
  3. Terus memindai indeks, menampilkan setiap entity secara bergantian, sampai
    • menemukan entity yang tidak memenuhi kondisi filter, atau
    • mencapai akhir indeks, atau
    • telah mengumpulkan jumlah hasil maksimum yang diminta oleh kueri.

Misalnya, pertimbangkan kueri berikut (dinyatakan dalam GQL):

SELECT * FROM Person WHERE last_name = "Smith"
                       AND height < 72
                  ORDER BY height DESC

Indeks sempurna untuk kueri ini adalah tabel kunci untuk entity jenis Person, dengan kolom untuk nilai properti last_name dan height. Indeks diurutkan terlebih dahulu dalam urutan menaik berdasarkan last_name, lalu dalam urutan menurun berdasarkan height.

Untuk membuat indeks ini, konfigurasikan indeks Anda seperti ini:

indexes:
- kind: Person
  properties:
  - name: last_name
    direction: asc
  - name: height
    direction: desc

Dua kueri dengan bentuk yang sama tetapi dengan nilai filter yang berbeda menggunakan indeks yang sama. Misalnya, kueri berikut menggunakan indeks yang sama dengan indeks di atas:

SELECT * FROM Person WHERE last_name = "Jones"
                       AND height < 63
                     ORDER BY height DESC

Dua kueri berikut juga menggunakan indeks yang sama, meskipun bentuknya berbeda:

SELECT * FROM Person WHERE last_name = "Friedkin"
                       AND first_name = "Damian"
                     ORDER BY height ASC

dan

SELECT * FROM Person WHERE last_name = "Blair"
                  ORDER BY first_name, height ASC

Konfigurasi indeks

Secara default, Datastore secara otomatis menetapkan indeks terlebih dahulu untuk setiap properti dari setiap jenis entity. Indeks yang telah ditetapkan ini cukup untuk menjalankan banyak kueri sederhana, seperti kueri khusus kesetaraan dan kueri ketidaksetaraan sederhana. Untuk semua kueri lainnya, aplikasi harus menentukan indeks yang diperlukan dalam file konfigurasi indeks bernama index.yaml. Jika aplikasi mencoba menjalankan kueri yang tidak dapat dijalankan dengan indeks yang tersedia (baik yang telah ditetapkan maupun yang ditentukan dalam file konfigurasi indeks), kueri akan gagal dengan pengecualian NeedIndexError.

Datastore membangun indeks otomatis untuk kueri dalam bentuk berikut:

  • Kueri Kindless hanya menggunakan ancestor dan filter kunci
  • Kueri yang hanya menggunakan filter ancestor dan kesetaraan
  • Kueri yang hanya menggunakan filter ketidaksetaraan (yang terbatas untuk satu properti)
  • Kueri yang hanya menggunakan filter ancestor, filter kesetaraan pada properti, dan filter ketidaksetaraan pada kunci
  • Kueri tanpa filter dan hanya satu tata urutan di satu properti, baik menaik ataupun menurun

Bentuk kueri lain mengharuskan indeksnya ditentukan dalam file konfigurasi indeks, termasuk:

  • Kueri dengan filter ancestor dan ketidaksetaraan
  • Kueri dengan satu atau beberapa filter ketidaksetaraan di sebuah properti dan satu atau beberapa filter kesetaraan di properti lainnya
  • Kueri dengan tata urutan pada kunci dalam urutan menurun
  • Kueri dengan beberapa tata urutan

Indeks dan properti

Berikut adalah beberapa pertimbangan khusus yang perlu diingat tentang indeks dan kaitannya dengan properti entity di Datastore:

Properti dengan jenis nilai campuran

Jika dua entity memiliki properti dengan nama yang sama tetapi jenis nilai yang berbeda, indeks properti akan terlebih dahulu mengurutkan entity berdasarkan jenis nilai, lalu berdasarkan pengurutan sekunder yang sesuai dengan setiap jenis. Misalnya, jika dua entity masing-masing memiliki properti bernama age, satu dengan nilai bilangan bulat dan satu dengan nilai string, entity dengan nilai bilangan bulat selalu mendahului entity dengan nilai string ketika diurutkan berdasarkan properti age, terlepas dari nilai properti itu sendiri.

Hal ini perlu diperhatikan terutama dalam kasus bilangan bulat dan angka floating point, yang diperlakukan sebagai jenis terpisah oleh Datastore. Karena semua bilangan bulat diurutkan sebelum semua float, properti dengan nilai bilangan bulat 38 diurutkan sebelum properti dengan nilai floating point 37.5.

Properti yang tidak terindeks

Jika mengetahui bahwa Anda tidak perlu memfilter atau mengurutkan properti tertentu, Anda dapat memberi tahu Datastore agar tidak mempertahankan entri indeks untuk properti tersebut dengan mendeklarasikan properti tidak terindeks. Tindakan ini dapat menurunkan biaya menjalankan aplikasi Anda dengan mengurangi jumlah penulisan yang harus dilakukan oleh Datastore. Entity dengan properti yang tidak terindeks berperilaku seolah-olah properti tersebut tidak ditetapkan: kueri dengan filter atau tata urutan di properti yang tidak terindeks tidak akan pernah cocok dengan entity tersebut.

Catatan: Jika properti muncul dalam indeks yang terdiri dari beberapa properti, maka menyetelnya ke tidak terindeks akan mencegahnya agar tidak terindeks dalam indeks yang dikombinasikan.

Misalnya, suatu entity memiliki properti a dan b dan Anda ingin membuat indeks yang dapat memenuhi kueri seperti WHERE a ="bike" and b="red". Selain itu, misalnya Anda tidak mempermasalahkan kueri WHERE a="bike" dan WHERE b="red". Jika Anda menetapkan a ke tidak terindeks serta membuat indeks untuk a dan b, Datastore tidak akan membuat entri indeks untuk indeks a dan b, sehingga kueri WHERE a="bike" and b="red" tidak akan berfungsi. Agar Datastore dapat membuat entri untuk indeks a dan b, a dan b harus diindeks.

Anda mendeklarasikan properti tidak terindeks dengan menetapkan indexed=False di konstruktor properti:

class Person(db.Model):
  name = db.StringProperty()
  age = db.IntegerProperty(indexed=False)

Nantinya, Anda dapat mengubah properti agar kembali diindeks dengan memanggil konstruktor lagi menggunakan indexed=True:

class Person(db.Model):
  name = db.StringProperty()
  age = db.IntegerProperty(indexed=True)

Namun, perhatikan bahwa mengubah properti dari tidak terindeks menjadi diindeks tidak memengaruhi entity yang sudah ada yang mungkin telah dibuat sebelum perubahan tersebut. Pemfilteran kueri pada properti tidak akan menampilkan entity yang sudah ada tersebut, karena entity tersebut tidak ditulis ke indeks kueri saat dibuat. Agar entity dapat diakses oleh kueri mendatang, Anda harus menulis ulang entity tersebut ke Datastore agar dapat dimasukkan dalam indeks yang sesuai. Artinya, Anda harus melakukan hal berikut untuk setiap entity yang sudah ada:

  1. Ambil (dapatkan) entity dari Datastore.
  2. Menulis (put) entity kembali ke Datastore.

Demikian pula, mengubah properti dari diindeks menjadi tidak terindeks hanya memengaruhi entity yang kemudian ditulis ke Datastore. Entri indeks untuk setiap entity yang ada dengan properti tersebut akan tetap ada sampai entity tersebut diperbarui atau dihapus. Untuk menghindari hasil yang tidak diinginkan, Anda harus menghapus permanen kode dari semua kueri yang memfilter atau mengurutkan berdasarkan properti (yang kini tidak terindeks).

Batas indeks

Datastore menetapkan batas jumlah dan ukuran keseluruhan entri indeks yang dapat dikaitkan dengan satu entity. Batas ini cukup besar, dan sebagian besar aplikasi tidak terpengaruh. Namun, ada situasi di mana Anda mungkin mencapai batas tersebut.

Sesuai dengan yang sudah dijelaskan di atas, Datastore membuat entri dalam indeks yang telah ditentukan untuk setiap properti dari setiap entity, kecuali string teks panjang (Text) dan string byte panjang (Blob), serta yang telah Anda deklarasikan secara eksplisit sebagai tidak terindeks. Properti juga dapat disertakan dalam indeks kustom tambahan yang dideklarasikan di file konfigurasi index.yaml Anda. Asalkan suatu entity tidak memiliki properti daftar, entity akan memiliki maksimal satu entri di setiap indeks kustom tersebut (untuk indeks non-ancestor) atau satu entri untuk setiap ancestor entity (untuk indeks ancestor). Setiap entri indeks ini harus diperbarui| setiap kali nilai properti berubah.

Untuk properti yang memiliki nilai tunggal untuk setiap entity, setiap kemungkinan nilai hanya perlu disimpan sekali per entity dalam indeks properti yang telah ditentukan. Meski demikian, entity dengan banyak properti bernilai tunggal dapat melebihi batas entri indeks atau batas ukuran. Demikian pula, entity yang dapat memiliki beberapa nilai untuk properti yang sama memerlukan entri indeks terpisah untuk setiap nilai; sekali lagi, jika jumlah kemungkinan nilainya besar, entity tersebut dapat melebihi batas entri.

Situasi ini menjadi lebih buruk jika entity memiliki beberapa properti, yang masing-masing dapat memiliki banyak nilai. Untuk mengakomodasi entity semacam itu, indeks harus menyertakan entri untuk setiap kemungkinan kombinasi nilai properti. Indeks kustom yang merujuk ke beberapa properti, yang masing-masing dengan beberapa nilai, kombinasinya dapat "meledak", sehingga memerlukan banyak entri untuk entity yang hanya memiliki sedikit kemungkinan nilai properti. Indeks yang meledak tersebut dapat secara drastis meningkatkan biaya penulisan entity ke Datastore, karena banyaknya jumlah entri indeks yang harus diperbarui, dan juga dapat dengan mudah menyebabkan entity melebihi batas entri indeks atau batas ukuran.

Mempertimbangkan kueri

SELECT * FROM Widget WHERE x=1 AND y=2 ORDER BY date

yang menyebabkan SDK menyarankan indeks berikut:

indexes:
- kind: Widget
  properties:
  - name: x
  - name: y
  - name: date
Indeks ini akan memerlukan total |x| * |y| * |date| entri untuk setiap entity (dengan |x| mengacu pada jumlah nilai yang terkait dengan entity untuk properti x). Misalnya, kode berikut
class Widget(db.Expando):
  pass

e2 = Widget()
e2.x = [1, 2, 3, 4]
e2.y = ['red', 'green', 'blue']
e2.date = datetime.datetime.now()
e2.put()

membuat entity dengan empat nilai untuk properti x, tiga nilai untuk properti y, dan date yang ditetapkan ke tanggal saat ini. Ini akan memerlukan 12 entri indeks, satu untuk setiap kemungkinan kombinasi nilai properti:

(1, "red", <now>) (1, "green", <now>) (1, "blue", <now>)

(2, "red", <now>) (2, "green", <now>) (2, "blue", <now>)

(3, "red", <now>) (3, "green", <now>) (3, "blue", <now>)

(4, "red", <now>) (4, "green", <now>) (4, "blue", <now>)

Jika properti yang sama diulang beberapa kali, Datastore dapat mendeteksi exploding index dan menyarankan indeks alternatif. Namun, dalam semua kasus lainnya (seperti kueri yang ditentukan dalam contoh ini), Datastore akan menghasilkan exploding index. Dalam kasus ini, Anda dapat menghindari exploding index dengan mengonfigurasi indeks secara manual dalam file konfigurasi indeks:

indexes:
- kind: Widget
  properties:
  - name: x
  - name: date
- kind: Widget
  properties:
  - name: y
  - name: date
Tindakan ini mengurangi jumlah entri yang diperlukan menjadi hanya (|x| * |date| + |y| * |date|), atau 7 entri, bukan 12:

(1, <now>) (2, <now>) (3, <now>) (4, <now>)

("red", <now>) ("green", <now>) ("blue", <now>)

Setiap operasi put yang dapat menyebabkan indeks melebihi entri indeks atau batas ukuran akan gagal dengan pengecualian BadRequestError. Teks pengecualian menjelaskan batas mana yang terlampaui ("Too many indexed properties" atau "Index entries too large"), serta indeks kustom mana yang menyebabkannya. Jika Anda membuat indeks baru yang akan melebihi batas entity apa pun saat dibangun, kueri terhadap indeks tersebut akan gagal dan indeks akan muncul dalam status Error di Konsol Google Cloud. Untuk menyelesaikan indeks dalam status Error:

  1. Hapus indeks di status Error dari file index.yaml Anda.

  2. Jalankan perintah berikut dari direktori tempat index.yaml Anda berada untuk menghapus indeks tersebut dari Datastore:

    gcloud datastore indexes cleanup index.yaml
    
  3. Selesaikan penyebab error. Contoh:

    • Merumuskan kembali definisi indeks dan kueri yang sesuai.
    • Menghapus entity yang menyebabkan exploding index.
  4. Tambahkan kembali indeks ke file index.yaml Anda.

  5. Jalankan perintah berikut dari direktori tempat index.yaml Anda berada untuk membuat indeks di Datastore:

    gcloud datastore indexes create index.yaml
    

Anda dapat menghindari exploding index dengan menghindari kueri yang memerlukan indeks kustom menggunakan properti daftar. Seperti yang dijelaskan di atas, ini mencakup kueri dengan beberapa tata urutan atau kueri dengan campuran filter kesetaraan dan ketidaksetaraan.