Indeks penelusuran

Halaman ini menjelaskan cara menambahkan dan menggunakan indeks penelusuran. Penelusuran teks lengkap dijalankan terhadap entri dalam indeks penelusuran.

Cara menggunakan indeks penelusuran

Anda dapat membuat indeks penelusuran di kolom mana pun yang ingin Anda sediakan untuk penelusuran teks lengkap. Untuk membuat indeks penelusuran, gunakan pernyataan DDL CREATE SEARCH INDEX. Untuk memperbarui indeks, gunakan pernyataan DDL ALTER SEARCH INDEX. Spanner otomatis membangun dan mengelola indeks penelusuran, termasuk menambahkan dan memperbarui data dalam indeks penelusuran segera setelah data tersebut berubah dalam database.

Partisi indeks penelusuran

Indeks penelusuran dapat dipartisi atau tidak dipartisi, bergantung pada jenis kueri yang ingin Anda percepat.

  • Contoh saat indeks berpartisi adalah pilihan terbaik adalah saat aplikasi mengkueri kotak surat email. Setiap kueri dibatasi untuk kotak surat tertentu.

  • Contoh saat kueri yang tidak dipartisi adalah pilihan terbaik adalah saat ada kueri di semua kategori produk dalam katalog produk.

Kasus penggunaan indeks penelusuran

Selain penelusuran teks lengkap, indeks penelusuran Spanner mendukung hal berikut:

  • Penelusuran JSON, yang merupakan cara efisien untuk mengindeks dan mengkueri dokumen JSON dan JSONB.
  • Penelusuran substring, yang merupakan jenis kueri yang mencari string yang lebih pendek (substring) dalam teks yang lebih besar.
  • Menggabungkan kondisi pada subset data yang diindeks, termasuk pencocokan persis dan numerik, ke dalam satu pemindaian indeks.

Untuk mengetahui informasi selengkapnya tentang kasus penggunaan, lihat Penelusuran versus indeks sekunder.

Contoh indeks penelusuran

Untuk menunjukkan kemampuan indeks penelusuran, misalkan ada tabel yang menyimpan informasi tentang album musik:

GoogleSQL

CREATE TABLE Albums (
  AlbumId STRING(MAX) NOT NULL,
  AlbumTitle STRING(MAX)
) PRIMARY KEY(AlbumId);

PostgreSQL

CREATE TABLE albums (
  albumid character varying NOT NULL,
  albumtitle character varying,
PRIMARY KEY(albumid));

Spanner memiliki beberapa fungsi tokenisasi yang membuat token. Untuk mengubah tabel sebelumnya agar pengguna dapat menjalankan penelusuran teks lengkap untuk menemukan judul album, gunakan fungsi TOKENIZE_FULLTEXT untuk membuat token dari judul album. Kemudian, buat kolom yang menggunakan jenis data TOKENLIST untuk menyimpan output tokenisasi dari TOKENIZE_FULLTEXT. Untuk contoh ini, kita akan membuat kolom AlbumTitle_Tokens.

GoogleSQL

ALTER TABLE Albums
  ADD COLUMN AlbumTitle_Tokens TOKENLIST
  AS (TOKENIZE_FULLTEXT(AlbumTitle)) HIDDEN;

PostgreSQL

ALTER TABLE albums
  ADD COLUMN albumtitle_tokens spanner.tokenlist
    GENERATED ALWAYS AS (spanner.tokenize_fulltext(albumtitle)) VIRTUAL HIDDEN;

Contoh berikut menggunakan DDL CREATE SEARCH INDEX untuk membuat indeks penelusuran (AlbumsIndex) pada token AlbumTitle (AlbumTitle_Tokens):

GoogleSQL

CREATE SEARCH INDEX AlbumsIndex
  ON Albums(AlbumTitle_Tokens);

PostgreSQL

Contoh ini menggunakan CREATE SEARCH INDEX.

CREATE SEARCH INDEX albumsindex ON albums(albumtitle_tokens);

Setelah menambahkan indeks penelusuran, gunakan kueri SQL untuk menemukan album yang cocok dengan kriteria penelusuran. Contoh:

GoogleSQL

SELECT AlbumId
FROM Albums
WHERE SEARCH(AlbumTitle_Tokens, "fifth symphony")

PostgreSQL

SELECT albumid
FROM albums
WHERE spanner.search(albumtitle_tokens, 'fifth symphony')

Membuat dan mengkueri indeks penelusuran untuk skema bernama

Anda dapat membuat indeks penelusuran pada skema bernama, seperti yang ditunjukkan dalam contoh berikut.

GoogleSQL

CREATE SCHEMA AlbumSchema;
CREATE TABLE AlbumSchema.Albums (
  AlbumId STRING(MAX) NOT NULL,
  AlbumTitle STRING(MAX),
  AlbumTitle_Tokens TOKENLIST AS (TOKENIZE_FULLTEXT(AlbumTitle)) STORED HIDDEN,
) PRIMARY KEY(AlbumId);

CREATE SEARCH INDEX AlbumSchema.AlbumsIndex ON AlbumSchema.Albums(AlbumTitle_Tokens);

PostgreSQL

Di PostgreSQL, Anda tidak dapat menggunakan nama yang sepenuhnya memenuhi syarat untuk membuat indeks pada skema bernama.

CREATE SCHEMA AlbumSchema;
CREATE TABLE AlbumSchema.albums (
  albumid character varying NOT NULL,
  albumtitle character varying,
  albumtitle_tokens spanner.tokenlist GENERATED ALWAYS AS (spanner.tokenize_fulltext(albumtitle)) STORED HIDDEN,
  PRIMARY KEY(albumid)
);
CREATE SEARCH INDEX albumsindex ON AlbumSchema.albums(albumtitle_tokens);

Contoh berikut menunjukkan cara membuat kueri indeks penelusuran pada skema bernama:

GoogleSQL

SELECT AlbumId
FROM AlbumSchema.Albums
WHERE SEARCH(AlbumTitle_Tokens, "fifth symphony")

Contoh berikut menggunakan petunjuk kueri untuk memberi tahu pengoptimal kueri agar menggunakan indeks bernama AlbumsIndex saat menjalankan kueri.

SELECT AlbumId
FROM AlbumSchema.Albums@{FORCE_INDEX="AlbumSchema.AlbumsIndex"}
WHERE SEARCH(AlbumTitle_Tokens, "fifth symphony")

PostgreSQL

Di PostgreSQL, Anda tidak dapat menggunakan nama yang sepenuhnya memenuhi syarat untuk membuat indeks pada skema bernama.

SELECT albumid
FROM AlbumSchema.albums
WHERE spanner.search(albumtitle_tokens, 'fifth symphony')

Contoh berikut menggunakan petunjuk kueri untuk memberi tahu pengoptimal kueri agar menggunakan indeks bernama albumsindex saat menjalankan kueri.

SELECT albumid
FROM AlbumSchema.albums /*@ FORCE_INDEX = albumsindex */
WHERE spanner.search(albumtitle_tokens, 'fifth symphony')

Konsistensi data

Saat indeks dibuat, Spanner menggunakan proses otomatis untuk mengisi ulang data guna memastikan konsistensi. Saat penulisan di-commit, indeks akan diperbarui dalam transaksi yang sama. Spanner otomatis melakukan pemeriksaan konsistensi data.

Definisi skema indeks penelusuran

Indeks penelusuran ditentukan pada satu atau beberapa kolom TOKENLIST dalam tabel. Indeks penelusuran memiliki komponen berikut:

  • Tabel dasar: tabel Spanner yang perlu diindeks.
  • Kolom TOKENLIST: kumpulan kolom yang menentukan token yang perlu diindeks. Urutan kolom ini tidak penting.

Misalnya, dalam pernyataan berikut, tabel dasarnya adalah Albums. Kolom TOKENLIST dibuat di AlbumTitle (AlbumTitle_Tokens) dan Rating (Rating_Tokens).

GoogleSQL

CREATE TABLE Albums (
  AlbumId STRING(MAX) NOT NULL,
  SingerId INT64 NOT NULL,
  ReleaseTimestamp INT64 NOT NULL,
  AlbumTitle STRING(MAX),
  Rating FLOAT64,
  AlbumTitle_Tokens TOKENLIST AS (TOKENIZE_FULLTEXT(AlbumTitle)) HIDDEN,
  Rating_Tokens TOKENLIST AS (TOKENIZE_NUMBER(Rating)) HIDDEN
) PRIMARY KEY(AlbumId);

PostgreSQL

CREATE TABLE albums (
  albumid character varying NOT NULL,
  singerid bigint NOT NULL,
  releasetimestamp bigint NOT NULL,
  albumtitle character varying,
  rating double precision,
  albumtitle_tokens spanner.tokenlist GENERATED ALWAYS AS (spanner.tokenize_fulltext(albumtitle)) VIRTUAL HIDDEN,
  rating_tokens spanner.tokenlist GENERATED ALWAYS AS (spanner.tokenize_fulltext(rating)) VIRTUAL HIDDEN,
PRIMARY KEY(AlbumId));

Gunakan pernyataan CREATE SEARCH INDEX berikut untuk membuat indeks penelusuran menggunakan token untuk AlbumTitle dan Rating:

GoogleSQL

CREATE SEARCH INDEX AlbumsIndex
ON Albums(AlbumTitle_Tokens, Rating_Tokens)
PARTITION BY SingerId
ORDER BY ReleaseTimestamp DESC

PostgreSQL

CREATE SEARCH INDEX albumsindex
ON albums(albumtitle_tokens, rating_tokens)
PARTITION BY singerid
ORDER BY releasetimestamp DESC

Indeks penelusuran memiliki opsi berikut:

  • Partisi: grup kolom opsional yang membagi indeks penelusuran. Membuat kueri indeks berpartisi sering kali jauh lebih efisien daripada membuat kueri indeks yang tidak berpartisi. Untuk mengetahui informasi selengkapnya, lihat Mempartisi indeks penelusuran.
  • Kolom tata urutan: kolom INT64 opsional yang menetapkan urutan pengambilan dari indeks penelusuran. Untuk mengetahui informasi selengkapnya, lihat Tata urutan indeks penelusuran.
  • Interleaving: seperti indeks sekunder, Anda dapat melakukan interleaving pada indeks penelusuran. Indeks penelusuran yang disisipkan menggunakan lebih sedikit resource untuk menulis dan bergabung dengan tabel dasar. Untuk mengetahui informasi selengkapnya, lihat Indeks penelusuran yang disisipkan.
  • Klausa opsi: daftar key-value pair yang menggantikan setelan default indeks penelusuran.

Tata letak internal indeks penelusuran

Elemen penting dari representasi internal indeks penelusuran adalah docid, yang berfungsi sebagai representasi kunci utama yang efisien penyimpanan dari tabel dasar yang dapat memiliki panjang arbitrer. Hal ini juga yang membuat urutan untuk tata letak data internal sesuai dengan kolom ORDER BY yang disediakan pengguna dari pernyataan CREATE SEARCH INDEX. ID ini direpresentasikan sebagai satu atau dua bilangan bulat 64-bit.

Indeks penelusuran diimplementasikan secara internal sebagai pemetaan dua tingkat:

  1. Token ke docid
  2. Docid ke kunci utama tabel dasar

Skema ini menghasilkan penghematan penyimpanan yang signifikan karena Spanner tidak perlu menyimpan kunci utama tabel dasar lengkap untuk setiap pasangan <token, document>.

Ada dua jenis indeks fisik yang menerapkan dua tingkat pemetaan:

  1. Indeks sekunder yang memetakan kunci partisi dan docid ke kunci utama tabel dasar. Pada contoh di bagian sebelumnya, tindakan ini memetakan {SingerId, ReleaseTimestamp, uid} ke {AlbumId}. Indeks sekunder juga menyimpan semua kolom yang ditentukan dalam klausa STORING dari CREATE SEARCH INDEX.
  2. Indeks token yang memetakan token ke docid, mirip dengan indeks terbalik dalam literatur pengambilan informasi. Spanner mengelola indeks token terpisah untuk setiap TOKENLIST indeks penelusuran. Secara logis, indeks token mengelola daftar docid untuk setiap token dalam setiap partisi (dikenal dalam pengambilan informasi sebagai daftar postingan). Daftar diurutkan berdasarkan token untuk pengambilan cepat, dan dalam daftar, docid digunakan untuk pengurutan. Indeks token individual adalah detail implementasi yang tidak ditampilkan melalui Spanner API.

Spanner mendukung opsi berikut untuk docid.

Indeks penelusuran Docid Perilaku
Klausa ORDER BY dihilangkan untuk indeks penelusuran {uid} Spanner menambahkan nilai unik tersembunyi (UID) untuk mengidentifikasi setiap baris.
ORDER BY column {column, uid} Spanner menambahkan kolom UID sebagai penentu antara baris dengan nilai column yang sama dalam partisi.

Catatan penggunaan:

  • Kolom UID internal tidak ditampilkan melalui Spanner API.
  • Dalam indeks yang tidak ditambahi UID, transaksi yang menambahkan baris dengan (partisi,tata urutan) yang sudah ada akan gagal.

Misalnya, perhatikan data berikut:

AlbumId SingerId ReleaseTimestamp SongTitle
a1 1 997 Hari-hari yang indah
a2 1 743 Mata indah

Dengan asumsi kolom pra-pengurutan dalam urutan menaik, konten indeks token yang dipartisi oleh SingerId mempartisi konten indeks token dengan cara berikut:

SingerId _token ReleaseTimestamp uid
1 indah 743 uid1
1 indah 997 uid2
1 hari 743 uid1
1 mata 997 uid2

Sharding indeks penelusuran

Saat memisahkan tabel, Spanner mendistribusikan data indeks penelusuran sehingga semua token dalam baris tabel dasar tertentu berada dalam pemisahan yang sama. Dengan kata lain, indeks penelusuran di-sharding berdasarkan dokumen. Strategi sharding ini memiliki implikasi performa yang signifikan:

  1. Jumlah server yang berkomunikasi dengan setiap transaksi tetap konstan, terlepas dari jumlah token atau jumlah kolom TOKENLIST yang diindeks.
  2. Kueri penelusuran yang melibatkan beberapa ekspresi bersyarat dijalankan secara independen pada setiap pemisahan, sehingga menghindari overhead performa yang terkait dengan gabungan terdistribusi.

Indeks penelusuran memiliki dua mode distribusi:

  • Sharding seragam (default). Dalam sharding seragam, data yang diindeks untuk setiap baris tabel dasar ditetapkan secara acak ke pemisahan indeks partisi.
  • Sharding tata urutan. Dalam sharding tata urutan, data untuk setiap baris tabel dasar ditetapkan ke pemisahan indeks partisi berdasarkan kolom ORDER BY (yaitu, kolom pra-pengurutan). Misalnya, dalam kasus tata urutan menurun, semua baris dengan nilai tata urutan terbesar muncul di pemisahan indeks pertama dari sebuah partisi, dan grup nilai tata urutan terbesar berikutnya di pemisahan berikutnya.

Mode sharding ini memiliki konsekuensi antara risiko hotspot dan biaya kueri:

  • Indeks penelusuran yang di-sharding secara seragam direkomendasikan jika pola baca atau tulis ke indeks penelusuran dapat menyebabkan hotspot. Sharding seragam mengurangi hotspot dengan mendistribusikan beban baca dan tulis secara merata di seluruh pemisahan, tetapi hal ini dapat meningkatkan penggunaan resource selama eksekusi kueri sebagai konsekuensinya. Dalam indeks penelusuran yang di-sharding secara seragam, kueri harus membaca semua pemisahan dalam partisi, karena data didistribusikan secara acak. Saat mengakses indeks yang di-sharding secara seragam, Spanner membaca semua pemisahan secara paralel untuk mengurangi latensi kueri secara keseluruhan.
  • Indeks penelusuran yang di-sharding berdasarkan tata urutan lebih disarankan jika pola baca atau tulis tidak mungkin menyebabkan hotspot. Pendekatan ini dapat mengurangi biaya kueri yang ORDER BY-nya cocok dengan ORDER BY indeks, dan menentukan LIMIT yang relatif rendah. Saat menjalankan kueri tersebut, Spanner membaca mulai dari pemisahan pertama partisi secara bertahap, dan kueri dapat diselesaikan tanpa membaca semua pemisahan jika LIMIT dapat terpenuhi lebih awal.
  • Mode sharding indeks penelusuran dikonfigurasi menggunakan klausa OPTIONS.

GoogleSQL

CREATE SEARCH INDEX AlbumsIndex
ON Albums(AlbumTitle_Tokens, Rating_Tokens)
PARTITION BY SingerId
ORDER BY ReleaseTimestamp DESC
OPTIONS (sort_order_sharding = true);

PostgreSQL

Mode sharding indeks penelusuran dikonfigurasi menggunakan klausa WITH.

CREATE SEARCH INDEX albumsindex
ON albums(albumtitle_tokens, rating_tokens)
PARTITION BY singerid
ORDER BY releasetimestamp DESC
WITH (sort_order_sharding = true);

Jika sort_order_sharding=false ditetapkan atau tidak ditentukan, indeks penelusuran akan dibuat menggunakan sharding seragam.

Indeks penelusuran yang disisipkan

Seperti indeks sekunder, Anda dapat meng-interleave indeks penelusuran dalam tabel induk dari tabel dasar. Alasan utama menggunakan indeks penelusuran yang disisipkan adalah untuk menempatkan data tabel dasar dengan data indeks untuk partisi kecil. Kolokasi oportunistik ini memiliki keuntungan berikut:

  • Operasi tulis tidak perlu melakukan commit dua fase.
  • Back-join indeks penelusuran dengan tabel dasar tidak didistribusikan.

Indeks penelusuran yang disisipkan memiliki batasan berikut:

  1. Hanya indeks yang di-sharding berdasarkan tata urutan yang dapat disisipkan.
  2. Indeks penelusuran hanya dapat disisipkan dalam tabel tingkat atas (bukan dalam tabel turunan).
  3. Seperti tabel yang disisipkan dan indeks sekunder, jadikan kunci tabel induk sebagai awalan kolom PARTITION BY dalam indeks penelusuran yang disisipkan.

Menentukan indeks penelusuran yang disisipkan

Contoh berikut menunjukkan cara menentukan indeks penelusuran yang disisipkan:

GoogleSQL

CREATE TABLE Singers (
  SingerId INT64 NOT NULL
) PRIMARY KEY(SingerId);

CREATE TABLE Albums (
  SingerId INT64 NOT NULL,
  AlbumId STRING(MAX) NOT NULL,
  AlbumTitle STRING(MAX),
  AlbumTitle_Tokens TOKENLIST AS (TOKENIZE_FULLTEXT(AlbumTitle)) HIDDEN
) PRIMARY KEY(SingerId, AlbumId),
INTERLEAVE IN PARENT Singers ON DELETE CASCADE;

CREATE SEARCH INDEX AlbumsIndex
ON Albums(AlbumTitle_Tokens)
PARTITION BY SingerId,
INTERLEAVE IN Singers
OPTIONS (sort_order_sharding = true);

PostgreSQL

CREATE TABLE singers(
  singerid bigint NOT NULL
PRIMARY KEY(singerid));

CREATE TABLE albums(
  singerid bigint NOT NULL,
  albumid character varying NOT NULL,
  albumtitle character varying,
  albumtitle_tokens spanner.tokenlist
  GENERATED ALWAYS
AS (
  spanner.tokenize_fulltext(albumtitle)
) VIRTUAL HIDDEN,
  PRIMARY KEY(singerid, albumid)),
INTERLEAVE IN PARENT singers ON DELETE CASCADE;

CREATE
  SEARCH INDEX albumsindex
ON
  albums(albumtitle_tokens)
  PARTITION BY singerid INTERLEAVE IN singers WITH(sort_order_sharding = true);

Tata urutan indeks penelusuran

Persyaratan untuk definisi tata urutan indeks penelusuran berbeda dengan indeks sekunder.

Misalnya, perhatikan tabel berikut:

GoogleSQL

CREATE TABLE Albums (
  AlbumId STRING(MAX) NOT NULL,
  ReleaseTimestamp INT64 NOT NULL,
  AlbumName STRING(MAX),
  AlbumName_Token TOKENLIST AS (TOKEN(AlbumName)) HIDDEN
) PRIMARY KEY(AlbumId);

PostgreSQL

CREATE TABLE albums (
  albumid character varying NOT NULL,
  releasetimestamp bigint NOT NULL,
  albumname character varying,
  albumname_token spanner.tokenlist
      GENERATED ALWAYS AS(spanner.token(albumname)) VIRTUAL HIDDEN,
PRIMARY KEY(albumid));

Aplikasi dapat menentukan indeks sekunder untuk mencari informasi menggunakan AlbumName yang diurutkan berdasarkan ReleaseTimestamp:

CREATE INDEX AlbumsSecondaryIndex ON Albums(AlbumName, ReleaseTimestamp DESC);

Indeks penelusuran yang setara terlihat seperti berikut (ini menggunakan tokenisasi pencocokan persis, karena indeks sekunder tidak mendukung penelusuran teks lengkap):

CREATE SEARCH INDEX AlbumsSearchIndex
ON Albums(AlbumName_Token)
ORDER BY ReleaseTimestamp DESC;

Tata urutan indeks penelusuran harus sesuai dengan persyaratan berikut:

  1. Hanya gunakan kolom INT64 untuk tata urutan indeks penelusuran. Kolom yang memiliki ukuran arbitrer menggunakan terlalu banyak resource dalam indeks penelusuran karena Spanner perlu menyimpan docid di samping setiap token. Secara khusus, kolom tata urutan tidak dapat menggunakan jenis TIMESTAMP karena TIMESTAMP menggunakan presisi nanodetik yang tidak sesuai dengan bilangan bulat 64-bit.
  2. Kolom tata urutan tidak boleh berupa NULL. Ada dua cara untuk memenuhi persyaratan ini:

    1. Deklarasikan kolom tata urutan sebagai NOT NULL.
    2. Konfigurasikan indeks untuk mengecualikan nilai NULL.

Stempel waktu sering digunakan untuk menentukan tata urutan. Praktik yang umum adalah menggunakan mikrodetik sejak epoch Unix untuk stempel waktu tersebut.

Aplikasi biasanya mengambil data terbaru terlebih dahulu menggunakan indeks penelusuran yang diurutkan dalam urutan menurun.

Indeks penelusuran yang difilter NULL

Indeks penelusuran dapat menggunakan sintaksis WHERE column_name IS NOT NULL untuk mengecualikan baris tabel dasar. Pemfilteran NULL dapat diterapkan ke kunci partisi, kolom tata urutan, dan kolom tersimpan. Pemfilteran NULL pada kolom array yang disimpan tidak diizinkan.

Contoh

GoogleSQL

CREATE SEARCH INDEX AlbumsIndex
ON Albums(AlbumTitle_Tokens)
STORING (Genre)
WHERE Genre IS NOT NULL

PostgreSQL

CREATE SEARCH INDEX albumsindex
ON albums(albumtitle_tokens)
INCLUDE (genre)
WHERE genre IS NOT NULL

Kueri harus menentukan kondisi pemfilteran NULL (Genre IS NOT NULL untuk contoh ini) dalam klausa WHERE. Jika tidak, Pengoptimal Kueri tidak akan dapat menggunakan indeks penelusuran. Untuk informasi selengkapnya, lihat Persyaratan kueri SQL.

Penggunaan filter WHERE <column> IS NOT NULL dalam definisi indeks memastikan bahwa hanya nilai non-null yang diindeks. Hal ini memungkinkan pengoptimal kueri menggunakan indeks secara efektif untuk kueri yang relevan, meskipun kondisi IS NOT NULL tidak secara eksplisit berada dalam klausa WHERE kueri.

Gunakan pemfilteran NULL pada kolom yang dihasilkan untuk mengecualikan baris berdasarkan kriteria arbitrer apa pun. Untuk mengetahui informasi selengkapnya, lihat Membuat indeks parsial menggunakan kolom yang dihasilkan.

Langkah berikutnya