Indeks Datastore

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 datastore-indexes.xml, yang dihasilkan dalam direktori /war/WEB-INF/appengine-generated aplikasi Anda saat ini 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.

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 (null). 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:

Query q1 =
    new Query("Person")
        .setFilter(
            CompositeFilterOperator.and(
                new FilterPredicate("lastName", FilterOperator.EQUAL, "Smith"),
                new FilterPredicate("height", FilterOperator.EQUAL, 72)))
        .addSort("height", Query.SortDirection.DESCENDING);

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

Untuk membuat indeks ini, konfigurasikan indeks Anda seperti ini:

<?xml version="1.0" encoding="utf-8"?>
<datastore-indexes autoGenerate="false">
  <datastore-index kind="Person" ancestor="false" source="manual">
    <property name="lastName" direction="asc"/>
    <property name="height" direction="desc"/>
  </datastore-index>
</datastore-indexes>

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:

Query q2 =
    new Query("Person")
        .setFilter(
            CompositeFilterOperator.and(
                new FilterPredicate("lastName", FilterOperator.EQUAL, "Jones"),
                new FilterPredicate("height", FilterOperator.EQUAL, 63)))
        .addSort("height", Query.SortDirection.DESCENDING);

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

Query q3 =
    new Query("Person")
        .setFilter(
            CompositeFilterOperator.and(
                new FilterPredicate("lastName", FilterOperator.EQUAL, "Friedkin"),
                new FilterPredicate("firstName", FilterOperator.EQUAL, "Damian")))
        .addSort("height", Query.SortDirection.ASCENDING);

dan

Query q4 =
    new Query("Person")
        .setFilter(new FilterPredicate("lastName", FilterOperator.EQUAL, "Blair"))
        .addSort("firstName", Query.SortDirection.ASCENDING)
        .addSort("height", Query.SortDirection.ASCENDING);

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 datastore-indexes.xml. 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 DatastoreNeedIndexException.

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.

Pada Java Datastore API tingkat rendah, properti ditetapkan sebagai terindeks atau tidak diindeks berdasarkan tiap entity, tergantung pada metode yang Anda gunakan untuk menetapkannya (setProperty() atau setUnindexedProperty()):

DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();

Key acmeKey = KeyFactory.createKey("Company", "Acme");

Entity tom = new Entity("Person", "Tom", acmeKey);
tom.setProperty("name", "Tom");
tom.setProperty("age", 32);
datastore.put(tom);

Entity lucy = new Entity("Person", "Lucy", acmeKey);
lucy.setProperty("name", "Lucy");
lucy.setUnindexedProperty("age", 29);
datastore.put(lucy);

Filter ageFilter = new FilterPredicate("age", FilterOperator.GREATER_THAN, 25);

Query q = new Query("Person").setAncestor(acmeKey).setFilter(ageFilter);

// Returns tom but not lucy, because her age is unindexed
List<Entity> results = datastore.prepare(q).asList(FetchOptions.Builder.withDefaults());

Anda dapat mengubah properti terindeks menjadi tidak diindeks dengan mereset nilainya dengan setUnindexedProperty(), atau dari tidak terindeks menjadi terindeks dengan meresetnya dengan setProperty().

Namun, perhatikan bahwa mengubah properti dari tidak diindeks 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.

Seperti yang dijelaskan di atas, Datastore membuat entri dalam indeks yang telah ditentukan untuk setiap properti dari setiap entity kecuali string teks panjang (Text), string byte panjang (Blob), dan entity tersemat (EmbeddedEntity) dan entity yang secara eksplisit Anda nyatakan sebagai tidak terindeks. Properti juga dapat disertakan dalam indeks kustom tambahan yang dideklarasikan di file konfigurasi datastore-indexes.xml 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

Query q =
    new Query("Widget")
        .setFilter(
            CompositeFilterOperator.and(
                new FilterPredicate("x", FilterOperator.EQUAL, 1),
                new FilterPredicate("y", FilterOperator.EQUAL, 2)))
        .addSort("date", Query.SortDirection.ASCENDING);

yang menyebabkan SDK menyarankan indeks berikut:

<?xml version="1.0" encoding="utf-8"?>
<datastore-indexes autoGenerate="false">
  <datastore-index kind="Widget" ancestor="false" source="manual">
    <property name="x" direction="asc"/>
    <property name="y" direction="asc"/>
    <property name="date" direction="asc"/>
  </datastore-index>
</datastore-indexes>
Indeks ini akan memerlukan total entri |x| * |y| * |date| untuk setiap entity (di mana |x| mengindikasikan jumlah nilai yang terkait dengan entitas untuk properti x). Misalnya, kode berikut
Entity widget = new Entity("Widget");
widget.setProperty("x", Arrays.asList(1, 2, 3, 4));
widget.setProperty("y", Arrays.asList("red", "green", "blue"));
widget.setProperty("date", new Date());
datastore.put(widget);

membuat entity dengan empat nilai untuk properti x, tiga nilai untuk properti y, dan date yang disetel 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:

<?xml version="1.0" encoding="utf-8"?>
<datastore-indexes autoGenerate="false">
  <datastore-index kind="Widget">
    <property name="x" direction="asc" />
    <property name="date" direction="asc" />
  </datastore-index>
  <datastore-index kind="Widget">
    <property name="y" direction="asc" />
    <property name="date" direction="asc" />
  </datastore-index>
</datastore-indexes>
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 akan menyebabkan indeks melebihi entri indeks atau batas ukuran akan gagal dengan IllegalArgumentException. 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 datastore-indexes.xml Anda.

  2. Jalankan perintah berikut dari direktori tempat datastore-indexes.xml Anda berada untuk menghapus indeks tersebut dari Datastore:

    gcloud datastore indexes cleanup datastore-indexes.xml
    
  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 datastore-indexes.xml Anda.

  5. Jalankan perintah berikut dari direktori tempat datastore-indexes.xml Anda berada untuk membuat indeks di Datastore:

    gcloud datastore indexes create datastore-indexes.xml
    

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.