Praktik terbaik untuk mendesain skema Grafik Spanner

Dokumen ini menjelaskan cara membuat kueri yang efisien menggunakan praktik terbaik untuk mendesain skema Spanner Graph. Desain skema dapat bersifat iteratif, jadi sebaiknya identifikasi pola kueri penting terlebih dahulu untuk memandu desain skema Anda.

Untuk informasi umum tentang praktik terbaik desain skema Spanner, lihat Praktik terbaik desain skema.

Mengoptimalkan traversal edge

Pencarian tepi adalah proses menavigasi grafik dengan mengikuti tepinya, dimulai dari node tertentu dan bergerak di sepanjang tepi yang terhubung untuk mencapai node lain. Penjelajahan tepi adalah operasi dasar di Spanner Graph, sehingga meningkatkan efisiensi penjelajahan tepi adalah kunci untuk performa aplikasi Anda.

Anda dapat melintasi tepi dalam dua arah: melintasi dari node sumber ke node tujuan disebut traversal tepi maju, sedangkan melintasi dari node tujuan ke node sumber disebut traversal tepi balik.

Mengoptimalkan traversal tepi maju menggunakan interleaving

Untuk meningkatkan performa traversal tepi maju, gabungkan tabel input tepi ke dalam tabel input node sumber untuk menempatkan tepi bersama node sumber. Interleaving adalah teknik pengoptimalan penyimpanan di Spanner yang secara fisik menempatkan baris tabel turunan dengan baris induk yang sesuai di penyimpanan. Untuk mengetahui informasi selengkapnya tentang interleaving, lihat Ringkasan skema.

Contoh berikut menunjukkan praktik terbaik ini:

CREATE TABLE Person (
  id               INT64 NOT NULL,
  name             STRING(MAX),
) PRIMARY KEY (id);

CREATE TABLE PersonOwnAccount (
  id               INT64 NOT NULL,
  account_id       INT64 NOT NULL,
  create_time      TIMESTAMP,
) PRIMARY KEY (id, account_id),
  INTERLEAVE IN PARENT Person ON DELETE CASCADE;

Mengoptimalkan traversal tepi terbalik menggunakan kunci asing

Untuk menjelajahi tepi terbalik secara efisien, buat batasan kunci asing antara tepi dan node tujuan. Kunci asing ini secara otomatis membuat indeks sekunder di tepi yang diberi kunci oleh kunci node tujuan. Indeks sekunder secara otomatis digunakan selama eksekusi kueri.

Contoh berikut menunjukkan praktik terbaik ini:

CREATE TABLE Person (
  id               INT64 NOT NULL,
  name             STRING(MAX),
) PRIMARY KEY (id);

CREATE TABLE Account (
  id               INT64 NOT NULL,
  create_time      TIMESTAMP,
) PRIMARY KEY (id);

CREATE TABLE PersonOwnAccount (
  id               INT64 NOT NULL,
  account_id       INT64 NOT NULL,
  create_time      TIMESTAMP,
  CONSTRAINT FK_Account FOREIGN KEY (account_id) REFERENCES Account (id),
) PRIMARY KEY (id, account_id),
  INTERLEAVE IN PARENT Person ON DELETE CASCADE;

Mengoptimalkan traversal tepi terbalik menggunakan indeks sekunder

Jika tidak ingin membuat kunci asing di edge, misalnya, karena integritas data yang ketat yang diterapkannya, Anda dapat langsung membuat indeks sekunder di tabel input edge, seperti yang ditunjukkan dalam contoh berikut:

CREATE TABLE PersonOwnAccount (
  id               INT64 NOT NULL,
  account_id       INT64 NOT NULL,
  create_time      TIMESTAMP,
) PRIMARY KEY (id, account_id),
  INTERLEAVE IN PARENT Person ON DELETE CASCADE;

CREATE INDEX Reverse_PersonOwnAccount
ON PersonOwnAccount (account_id);

Tidak mengizinkan tepi yang menggantung

Tepi menggantung adalah tepi yang menghubungkan kurang dari dua node. Tepi yang tidak terikat dapat terjadi saat node dihapus tanpa menghapus tepi terkaitnya, atau saat tepi dibuat tanpa menautkannya dengan benar ke node-nya.

Tidak mengizinkan edge yang menggantung memberikan manfaat berikut:

  • Menerapkan integritas struktur grafik.
  • Meningkatkan performa kueri dengan menghindari pekerjaan tambahan untuk memfilter tepi tempat endpoint tidak ada.

Tidak mengizinkan tepi yang menggantung menggunakan batasan referensi

Untuk melarang tepi yang menggantung, tentukan batasan pada kedua endpoint:

  • Gabungkan tabel input tepi ke dalam tabel input node sumber. Pendekatan ini memastikan bahwa node sumber tepi selalu ada.
  • Buat batasan kunci asing pada tepi untuk memastikan bahwa node tujuan tepi selalu ada.

Contoh berikut menggunakan interleaving dan kunci asing untuk menerapkan integritas referensi:

CREATE TABLE PersonOwnAccount (
  id               INT64 NOT NULL,
  account_id       INT64 NOT NULL,
  create_time      TIMESTAMP,
  CONSTRAINT FK_Account FOREIGN KEY (account_id) REFERENCES Account (id) ON DELETE CASCADE,
) PRIMARY KEY (id, account_id),
  INTERLEAVE IN PARENT Person ON DELETE CASCADE;

Menggunakan ON DELETE CASCADE untuk menghapus tepi secara otomatis saat menghapus node

Saat Anda menggunakan interleaving atau kunci asing untuk melarang edge yang menggantung, gunakan klausa ON DELETE untuk mengontrol perilaku saat Anda ingin menghapus node dengan edge yang masih terpasang. Untuk informasi selengkapnya, lihat menghapus cascading untuk tabel sisipan dan menghapus cascading dengan kunci asing.

Anda dapat menggunakan ON DELETE dengan cara berikut:

  • ON DELETE NO ACTION (atau menghapus klausa ON DELETE): Menghapus node dengan tepi akan gagal.
  • ON DELETE CASCADE: Menghapus node akan otomatis menghapus sisi terkait dalam transaksi yang sama.

Menghapus kaskade untuk tepi yang menghubungkan berbagai jenis node

  • Menghapus tepi saat node sumber dihapus. Misalnya,INTERLEAVE IN PARENT Person ON DELETE CASCADE menghapus semua tepi PersonOwnAccount keluar dari node Person yang dihapus. Untuk informasi selengkapnya, lihat Membuat tabel yang diselang-seling.

  • Menghapus tepi saat node tujuan dihapus. Misalnya, CONSTRAINT FK_Account FOREIGN KEY(account_id) REFERENCES Account(id) ON DELETE CASCADE menghapus semua tepi PersonOwnAccount masuk ke node Account yang dihapus. Untuk informasi selengkapnya, lihat Kunci asing.

Menghapus cascade untuk tepi yang menghubungkan jenis node yang sama

Jika node sumber dan tujuan tepi memiliki jenis yang sama dan tepi diselingi ke dalam node sumber, Anda dapat menentukan ON DELETE CASCADE hanya untuk node sumber atau node tujuan (tetapi tidak keduanya).

Untuk menghapus edge yang menggantung secara otomatis dalam kedua kasus tersebut, buat kunci asing pada referensi node sumber edge, bukan menyisipkan tabel input edge ke dalam tabel input node sumber.

Sebaiknya gunakan interleaving untuk mengoptimalkan traversal tepi maju. Pastikan untuk memverifikasi dampaknya pada beban kerja Anda sebelum melanjutkan. Lihat contoh berikut, yang menggunakan AccountTransferAccount sebagai tabel input edge:

--Define two Foreign Keys, each on one end Node of Transfer Edge, both with ON DELETE CASCADE action:
CREATE TABLE AccountTransferAccount (
  id               INT64 NOT NULL,
  to_id            INT64 NOT NULL,
  amount           FLOAT64,
  create_time      TIMESTAMP NOT NULL,
  order_number     STRING(MAX),
  CONSTRAINT FK_FromAccount FOREIGN KEY (id) REFERENCES Account (id) ON DELETE CASCADE,
  CONSTRAINT FK_ToAccount FOREIGN KEY (to_id) REFERENCES Account (id) ON DELETE CASCADE,
) PRIMARY KEY (id, to_id);

Memfilter menurut properti node atau edge dengan indeks sekunder

Indeks sekunder sangat penting untuk pemrosesan kueri yang efisien. Fungsi ini mendukung pencarian cepat node dan edge berdasarkan nilai properti tertentu, tanpa harus menelusuri seluruh struktur grafik. Hal ini penting saat Anda menggunakan grafik besar, karena menjelajahi semua node dan tepi dapat sangat tidak efisien.

Mempercepat pemfilteran node berdasarkan properti

Untuk mempercepat pemfilteran berdasarkan properti node, buat indeks sekunder pada properti. Misalnya, kueri berikut menemukan akun untuk nama panggilan tertentu. Tanpa indeks sekunder, semua node Account akan dipindai agar cocok dengan kriteria pemfilteran.

GRAPH FinGraph
MATCH (acct:Account)
WHERE acct.nick_name = "abcd"
RETURN acct.id;

Untuk mempercepat kueri, buat indeks sekunder pada properti yang difilter, seperti yang ditunjukkan dalam contoh berikut:

CREATE TABLE Account (
  id               INT64 NOT NULL,
  create_time      TIMESTAMP,
  is_blocked       BOOL,
  nick_name        STRING(MAX),
) PRIMARY KEY (id);

CREATE INDEX AccountByNickName
ON Account (nick_name);

Tips: Gunakan indeks yang difilter dengan NULL untuk properti yang jarang. Untuk mengetahui informasi selengkapnya, lihat Menonaktifkan pengindeksan nilai NULL.

Mempercepat traversal tepi maju dengan pemfilteran pada properti tepi

Saat menjelajahi tepi sambil memfilter propertinya, Anda dapat mempercepat kueri dengan membuat indeks sekunder pada properti tepi dan menyisipkan indeks ke dalam node sumber.

Misalnya, kueri berikut menemukan akun yang dimiliki oleh orang tertentu setelah waktu tertentu:

GRAPH FinGraph
MATCH (person:Person)-[owns:Owns]->(acct:Account)
WHERE person.id = 1
  AND owns.create_time >= PARSE_TIMESTAMP("%c", "Thu Dec 25 07:30:00 2008")
RETURN acct.id;

Secara default, kueri ini membaca semua edge orang yang ditentukan, lalu memfilter edge yang memenuhi kondisi di create_time.

Contoh berikut menunjukkan cara meningkatkan efisiensi kueri dengan membuat indeks sekunder pada referensi node sumber tepi (id) dan properti tepi (create_time). Gabungkan indeks di bawah tabel input node sumber untuk meletakkan indeks bersama node sumber.

CREATE TABLE PersonOwnAccount (
  id               INT64 NOT NULL,
  account_id       INT64 NOT NULL,
  create_time      TIMESTAMP,
) PRIMARY KEY (id, account_id),
  INTERLEAVE IN PARENT Person ON DELETE CASCADE;

CREATE INDEX PersonOwnAccountByCreateTime
ON PersonOwnAccount (id, create_time)
INTERLEAVE IN Person;

Dengan pendekatan ini, kueri dapat menemukan semua tepi yang memenuhi kondisi di create_time secara efisien.

Mempercepat traversal tepi terbalik dengan pemfilteran pada properti tepi

Saat menjelajahi tepi terbalik sambil memfilter propertinya, Anda dapat mempercepat kueri dengan membuat indeks sekunder menggunakan node tujuan dan properti tepi untuk pemfilteran.

Contoh kueri berikut melakukan traversal tepi terbalik dengan pemfilteran pada properti tepi:

GRAPH FinGraph
MATCH (acct:Account)<-[owns:Owns]-(person:Person)
WHERE acct.id = 1
  AND owns.create_time >= PARSE_TIMESTAMP("%c", "Thu Dec 25 07:30:00 2008")
RETURN person.id;

Untuk mempercepat kueri ini menggunakan indeks sekunder, gunakan salah satu opsi berikut:

  • Buat indeks sekunder pada referensi node tujuan tepi (account_id) dan properti tepi (create_time), seperti yang ditunjukkan dalam contoh berikut:

    CREATE TABLE PersonOwnAccount (
      id               INT64 NOT NULL,
      account_id       INT64 NOT NULL,
      create_time      TIMESTAMP,
    ) PRIMARY KEY (id, account_id),
      INTERLEAVE IN PARENT Person ON DELETE CASCADE;
    
    CREATE INDEX PersonOwnAccountByCreateTime
    ON PersonOwnAccount (account_id, create_time);
    

    Pendekatan ini memberikan performa yang lebih baik karena tepi balik diurutkan menurut account_id dan create_time, yang memungkinkan mesin kueri menemukan tepi untuk account_id secara efisien yang memenuhi kondisi pada create_time. Namun, jika pola kueri yang berbeda memfilter properti yang berbeda, setiap properti mungkin memerlukan indeks terpisah, yang dapat menambahkan overhead.

  • Buat indeks sekunder pada referensi node tujuan edge (account_id) dan simpan properti edge (create_time) di kolom penyimpanan, seperti yang ditunjukkan dalam contoh berikut:

    CREATE TABLE PersonOwnAccount (
      id               INT64 NOT NULL,
      account_id       INT64 NOT NULL,
      create_time      TIMESTAMP,
    ) PRIMARY KEY (id, account_id),
      INTERLEAVE IN PARENT Person ON DELETE CASCADE;
    
    CREATE INDEX PersonOwnAccountByCreateTime
    ON PersonOwnAccount (account_id) STORING (create_time);
    

    Pendekatan ini dapat menyimpan beberapa properti; namun, kueri harus membaca semua tepi node tujuan, lalu memfilter properti tepi.

Anda dapat menggabungkan pendekatan ini dengan mengikuti panduan berikut:

  • Gunakan properti edge di kolom indeks jika properti tersebut digunakan dalam kueri yang penting untuk performa.
  • Untuk properti yang digunakan dalam kueri yang kurang sensitif terhadap performa, tambahkan properti tersebut di kolom penyimpanan.

Jenis node dan edge model dengan label dan properti

Jenis node dan edge biasanya dimodelkan dengan label. Namun, Anda juga dapat menggunakan properti untuk membuat model jenis. Pertimbangkan contoh yang memiliki banyak jenis akun yang berbeda, seperti BankAccount, InvestmentAccount, dan RetirementAccount. Anda dapat menyimpan akun dalam tabel input terpisah dan membuat modelnya sebagai label terpisah, atau Anda dapat menyimpan akun dalam satu tabel input dan menggunakan properti untuk membedakan antarjenis.

Mulai proses pemodelan dengan membuat model jenis dengan label. Pertimbangkan penggunaan properti dalam skenario berikut.

Meningkatkan pengelolaan skema

Jika grafik Anda memiliki banyak jenis node dan edge yang berbeda, mengelola tabel input terpisah untuk setiap jenis dapat menjadi sulit. Untuk mempermudah pengelolaan skema, buat model jenis sebagai properti.

Jenis model di properti untuk mengelola jenis yang sering berubah

Saat Anda membuat model jenis sebagai label, penambahan atau penghapusan jenis memerlukan perubahan pada skema. Jika Anda melakukan terlalu banyak pembaruan skema dalam jangka waktu yang singkat, Spanner mungkin memperlambat pemrosesan pembaruan skema yang diantrekan. Untuk mengetahui informasi selengkapnya, lihat Membatasi frekuensi pembaruan skema.

Jika Anda perlu sering mengubah skema, sebaiknya buat model jenis di properti untuk mengatasi batasan frekuensi update skema.

Mempercepat kueri

Membuat model jenis dengan properti dapat mempercepat kueri saat pola node atau tepi merujuk ke beberapa label. Contoh kueri berikut menemukan semua instance SavingsAccount dan InvestmentAccount yang dimiliki oleh Person, dengan asumsi jenis akun dimodelkan dengan label:

GRAPH FinGraph
MATCH (:Person {id: 1})-[:Owns]->(acct:SavingsAccount|InvestmentAccount)
RETURN acct.id;

Pola node acct mereferensikan dua label. Jika ini adalah kueri yang penting untuk performa, pertimbangkan untuk membuat model Account menggunakan properti. Pendekatan ini mungkin memberikan performa kueri yang lebih baik, seperti yang ditunjukkan dalam contoh kueri berikut. Sebaiknya Anda menjalankan benchmark pada kedua kueri tersebut.

GRAPH FinGraph
MATCH (:Person {id: 1})-[:Owns]->(acct:Account)
WHERE acct.type IN ("Savings", "Investment")
RETURN acct.id;

Menyimpan jenis di kunci elemen node untuk mempercepat kueri

Untuk mempercepat kueri dengan pemfilteran pada jenis node saat jenis node dimodelkan dengan properti dan jenisnya tidak berubah selama masa aktif node, ikuti langkah-langkah berikut:

  1. Sertakan properti sebagai bagian dari kunci elemen node.
  2. Tambahkan jenis node di tabel input tepi.
  3. Sertakan jenis node dalam kunci referensi tepi.

Contoh berikut menerapkan pengoptimalan ini ke node Account dan edge AccountTransferAccount.

CREATE TABLE Account (
  type             STRING(MAX) NOT NULL,
  id               INT64 NOT NULL,
  create_time      TIMESTAMP,
) PRIMARY KEY (type, id);

CREATE TABLE AccountTransferAccount (
  type             STRING(MAX) NOT NULL,
  id               INT64 NOT NULL,
  to_type          STRING(MAX) NOT NULL,
  to_id            INT64 NOT NULL,
  amount           FLOAT64,
  create_time      TIMESTAMP NOT NULL,
  order_number     STRING(MAX),
) PRIMARY KEY (type, id, to_type, to_id),
  INTERLEAVE IN PARENT Account ON DELETE CASCADE;

CREATE PROPERTY GRAPH FinGraph
  NODE TABLES (
    Account
  )
  EDGE TABLES (
    AccountTransferAccount
      SOURCE KEY (type, id) REFERENCES Account
      DESTINATION KEY (to_type, to_id) REFERENCES Account
  );

Mengonfigurasi TTL pada node dan edge

Time to live (TTL) Spanner adalah mekanisme yang mendukung masa berlaku dan penghapusan data secara otomatis setelah jangka waktu tertentu. Hal ini sering digunakan untuk data yang memiliki masa aktif atau relevansi terbatas, seperti informasi sesi, cache sementara, atau log peristiwa. Dalam kasus ini, TTL membantu mempertahankan ukuran dan performa database.

Contoh berikut menggunakan TTL untuk menghapus akun 90 hari setelah penutupannya:

CREATE TABLE Account (
  id               INT64 NOT NULL,
  create_time      TIMESTAMP,
  close_time       TIMESTAMP,
) PRIMARY KEY (id),
  ROW DELETION POLICY (OLDER_THAN(close_time, INTERVAL 90 DAY));

Jika tabel node memiliki TTL dan tabel tepi yang diselingi di dalamnya, interleave harus ditentukan dengan ON DELETE CASCADE. Demikian pula, jika tabel node memiliki TTL dan direferensikan oleh tabel tepi melalui kunci asing, kunci asing harus ditentukan dengan ON DELETE CASCADE.

Dalam contoh berikut, AccountTransferAccount disimpan hingga sepuluh tahun selama akun tetap aktif. Jika akun dihapus, histori transfer juga akan dihapus.

CREATE TABLE AccountTransferAccount (
  id               INT64 NOT NULL,
  to_id            INT64 NOT NULL,
  amount           FLOAT64,
  create_time      TIMESTAMP NOT NULL,
  order_number     STRING(MAX),
) PRIMARY KEY (id, to_id),
  INTERLEAVE IN PARENT Account ON DELETE CASCADE,
  ROW DELETION POLICY (OLDER_THAN(create_time, INTERVAL 3650 DAY));

Menggabungkan tabel input node dan edge

Anda dapat menggunakan tabel input yang sama untuk menentukan lebih dari satu node dan tepi dalam skema Anda.

Dalam contoh tabel berikut, node Account memiliki kunci komposit (owner_id, account_id). Ada definisi tepi implisit, node Person dengan kunci (id) memiliki node Account dengan kunci komposit (owner_id, account_id) saat id sama dengan owner_id.

CREATE TABLE Person (
  id INT64 NOT NULL,
) PRIMARY KEY (id);

-- Assume each account has exactly one owner.
CREATE TABLE Account (
  owner_id INT64 NOT NULL,
  account_id INT64 NOT NULL,
) PRIMARY KEY (owner_id, account_id);

Dalam hal ini, Anda dapat menggunakan tabel input Account untuk menentukan node Account dan tepi PersonOwnAccount, seperti yang ditunjukkan dalam contoh skema berikut. Untuk memastikan bahwa semua nama tabel elemen unik, contoh ini memberikan alias Owns pada definisi tabel tepi.

CREATE PROPERTY GRAPH FinGraph
  NODE TABLES (
    Person,
    Account
  )
  EDGE TABLES (
    Account AS Owns
      SOURCE KEY (owner_id) REFERENCES Person
      DESTINATION KEY (owner_id, account_id) REFERENCES Account
  );

Langkah selanjutnya