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 bisa iteratif, jadi kami sarankan Anda terlebih dahulu mengidentifikasi pola kueri penting untuk memandu desain skema.

Untuk informasi umum tentang desain skema Spanner yang paling baik lihat Praktik terbaik desain skema.

Mengoptimalkan edge traversal

Edge traversal adalah proses navigasi melalui grafik dengan mengikuti tepi, mulai pada {i>node<i} tertentu dan bergerak di sepanjang tepi yang terhubung untuk mencapai node lainnya. Edge traversal adalah operasi mendasar di Spanner Graph, jadi meningkatkan efisiensi edge traversal adalah kunci performa aplikasi Anda.

Anda bisa melintasi tepi dalam dua arah: melintas dari {i>node<i} sumber ke node tujuan disebut forward edge traversal, saat melintas dari node tujuan ke node sumber disebut reverse edge traversal.

Mengoptimalkan forward edge traversal menggunakan interleaving

Untuk meningkatkan performa forward edge traversal, masukkan tabel input tepi ke depan ke dalam tabel input node sumber untuk menempatkan tepi-tepinya dengan node sumber. Interleaving adalah teknik pengoptimalan penyimpanan di Spanner yang secara fisik menempatkan baris tabel turunan dengan baris induknya yang sesuai di Storage. Untuk informasi selengkapnya tentang interleaving, lihat Ringkasan skema.

Contoh berikut menunjukkan praktik terbaik tersebut:

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 reverse edge traversal menggunakan kunci asing

Untuk melintasi tepi balik secara efisien, buat batasan kunci asing antara {i>edge<i} dan {i>node <i}tujuan. {i>Foreign key<i} ini secara otomatis membuat indeks sekunder di bagian tepi yang dikunci oleh kunci {i>node<i} tujuan. Indeks sekundernya adalah digunakan secara otomatis selama eksekusi kueri.

Contoh berikut menunjukkan praktik terbaik tersebut:

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 edge terbalik menggunakan indeks sekunder

Jika Anda tidak ingin membuat {i>foreign key<i} di tepi, misalnya, karena integritas data ketat yang diberlakukannya, Anda dapat langsung membuat indeks sekunder di tabel input edge, seperti yang ditunjukkan pada 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);

Larang tepi yang menjuntai

Tepi menjuntai adalah tepi yang menghubungkan kurang dari dua node. Menggantung tepi dapat terjadi ketika sebuah {i>node<i} dihapus tanpa menghilangkan tepian yang terkait, atau ketika tepian dibuat tanpa menghubungkannya dengan benar ke {i>node-<i}nya.

Melarang tepi yang tidak rapi akan memberikan manfaat berikut:

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

Melarang tepi yang menjuntai menggunakan batasan referensial

Untuk melarang tepi yang menjuntai, tentukan batasan pada keduanya endpoint:

  • Masukkan tabel input edge ke tabel input node sumber. Ini memastikan bahwa {i>node<i} sumber dari sebuah edge selalu ada.
  • Buat batasan {i>foreign key<i} di tepian untuk memastikan bahwa node tujuan dari tepi selalu ada.

Contoh berikut menggunakan interleaving dan {i>foreign key<i} untuk memberlakukan integritas referensial:

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;

Gunakan ON DELETE CASCADE untuk otomatis menghapus tepi saat menghapus node

Saat Anda menggunakan interleaving atau {i>foreign key<i} untuk melarang tepi yang menjuntai, gunakan Klausa ON DELETE untuk mengontrol perilaku saat Anda ingin menghapus node dengan tepi yang masih melekat. Untuk informasi selengkapnya, lihat menghapus jenjang 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 simpul dengan tepi akan gagal.
  • ON DELETE CASCADE: Menghapus node akan otomatis menghapus tepi yang terkait dalam transaksi yang sama.

Menghapus jenjang untuk tepi yang menghubungkan berbagai jenis {i>node<i}

  • Hapus edge saat node sumber dihapus. Misalnya,INTERLEAVE IN PARENT Person ON DELETE CASCADE akan menghapus semua permintaan keluar PersonOwnAccount tepi dari node Person yang sedang dihapus. Untuk selengkapnya informasi, lihat Membuat tabel sisipan.

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

Hapus jenjang untuk tepi yang menghubungkan jenis simpul yang sama

Ketika {i>node<i} sumber dan tujuan dari suatu edge memiliki jenis yang sama dan edge disisipi ke node sumber, Anda dapat menentukan ON DELETE CASCADE hanya untuk node sumber atau node tujuan (tetapi tidak untuk kedua node tersebut).

Untuk secara otomatis menghapus tepian yang menjuntai pada kedua kasus tersebut, buat {i>foreign key <i}di referensi node sumber edge alih-alih menyisipkan tabel input edge tabel input simpul sumber.

Kami merekomendasikan {i>interleaving <i}untuk mengoptimalkan traversal edge maju. Pastikan untuk memverifikasi dampak beban kerja Anda sebelum melanjutkan. Lihat contoh berikut, yang menggunakan AccountTransferAccount sebagai input edge tabel:

--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. Mendukung pencarian cepat terhadap simpul dan tepi berdasarkan nilai properti tertentu, tanpa harus menjelajahi seluruh struktur grafik. Hal ini penting ketika Anda bekerja dengan grafik besar, karena melintasi semua {i>node<i} dan tepi bisa sangat tidak efisien.

Mempercepat pemfilteran node berdasarkan properti

Untuk mempercepat pemfilteran berdasarkan properti {i>node<i}, buat indeks sekunder di properti baru. Misalnya, kueri berikut menemukan akun untuk nama panggilan. Tanpa indeks sekunder, semua node Account akan dipindai agar sesuai 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, sebagai 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 AccountByEmail
ON Account (nick_name);

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

Mempercepat traversal tepi maju dengan pemfilteran pada properti edge

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

Misalnya, kueri berikut menemukan akun milik 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 tepian orang yang ditentukan, lalu memfilter tepian yang memenuhi kondisi pada create_time.

Contoh berikut menunjukkan cara meningkatkan efisiensi kueri dengan membuat indeks sekunder pada referensi node sumber edge (id) dan properti edge (create_time). Sisipkan indeks di bawah tabel input node sumber untuk menempatkan indeks dengan 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 menggunakan pendekatan ini, kueri bisa secara efisien menemukan semua tepi yang memenuhi kondisi pada create_time.

Mempercepat traversal tepi terbalik dengan pemfilteran pada properti edge

Ketika Anda melintasi tepi balik saat memfilter propertinya, Anda bisa mempercepat kueri dengan membuat indeks sekunder menggunakan simpul tujuan dan properti tepi untuk pemfilteran.

Kueri contoh berikut melakukan {i>reverse edge traversal<i} dengan pemfilteran pada properti edge:

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:

  • Membuat indeks sekunder pada referensi node tujuan edge (account_id) dan properti edge (create_time), seperti yang ditunjukkan di 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 kinerja yang lebih baik karena tepi baliknya diurutkan menurut account_id dan create_time, yang memungkinkan mesin kueri menemukan tepi untuk account_id yang memenuhi kondisi dengan efisien create_time. Namun, jika pola kueri yang berbeda memfilter maka setiap properti mungkin memerlukan indeks terpisah, yang dapat menambahkan {i>overhead<i}.

  • Membuat indeks sekunder pada referensi node tujuan edge (account_id) dan simpan properti edge (create_time) di elemen 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 tersebut harus membaca semua tepian {i>node<i} tujuan dan kemudian memfilter properti edge.

Anda dapat menggabungkan pendekatan ini dengan mengikuti panduan berikut:

  • Gunakan properti tepi dalam kolom indeks jika digunakan dalam kueri yang penting bagi performa.
  • Untuk properti yang digunakan dalam kueri yang kurang sensitif terhadap performa, tambahkan properti tersebut dalam kolom yang disimpan.

Node model dan jenis edge dengan label dan properti

Jenis node dan edge biasanya dimodelkan dengan label. Namun, Anda juga dapat menggunakan properti ke jenis model. Bayangkan sebuah contoh di mana ada banyak jenis akun, seperti BankAccount, InvestmentAccount, dan RetirementAccount. Anda dapat menyimpan akun dalam tabel input dan membuat modelnya sebagai label terpisah, atau menyimpan akun dengan satu input tabel dan menggunakan properti untuk membedakan jenis-jenis.

Mulai proses pemodelan dengan memodelkan jenis dengan label. Pertimbangkan menggunakan properti dalam skenario berikut.

Meningkatkan pengelolaan skema

Jika grafik Anda memiliki berbagai jenis node dan tepi, mengelola input terpisah untuk setiap tabel bisa menjadi sulit. Untuk mempermudah pengelolaan skema, buat model jenis sebagai properti.

Jenis model di properti untuk mengelola jenis yang sering berubah

Saat Anda memodelkan jenis sebagai label, penambahan atau penghapusan jenis memerlukan perubahan pada skemanya. Jika Anda melakukan terlalu banyak pembaruan skema dalam waktu singkat, Spanner mungkin throttle pemrosesan pembaruan skema dalam antrean. Untuk informasi selengkapnya, lihat Batasi frekuensi pembaruan skema.

Jika Anda sering mengubah skema, sebaiknya buat model yang ketik properti untuk mengatasi keterbatasan frekuensi skema pembaruan.

Mempercepat kueri

Jenis pemodelan dengan properti dapat mempercepat kueri saat pola node atau tepi merujuk pada beberapa label. Contoh kueri berikut menemukan semua {i>instance <i} SavingsAccount dan InvestmentAccount dimiliki oleh Person, dengan asumsi akun type ini dimodelkan dengan label:

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

Pola node acct merujuk pada dua label. Jika ini adalah kueri yang penting bagi performa, pertimbangkan untuk membuat model Account menggunakan properti. Ini mungkin memberikan performa kueri yang lebih baik, seperti yang ditampilkan dalam kueri contoh. Sebaiknya Anda menjalankan tolok ukur untuk kedua kueri tersebut.

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

Jenis penyimpanan 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 sepanjang masa aktif node, ikuti langkah-langkah berikut:

  1. Sertakan properti sebagai bagian dari kunci elemen node.
  2. Tambahkan jenis node di tabel input edge.
  3. Sertakan jenis node di kunci yang merujuk pada edge.

Contoh berikut menerapkan pengoptimalan ini ke node Account dan tepi 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

Spanner Time to live (TTL) adalah mekanisme yang mendukung akhir masa berlaku dan penghapusan data setelah periode tertentu. Ini sering digunakan untuk data yang memiliki masa aktif atau relevansi, seperti informasi sesi, cache sementara, atau peristiwa log. Dalam kasus ini, TTL membantu menjaga ukuran dan performa database.

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

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 {i>node<i} memiliki TTL dan tabel tepi yang disisipkan di dalamnya, interleaf harus didefinisikan dengan ON DELETE CASCADE Demikian pula, jika tabel {i>node<i} memiliki TTL dan dirujuk oleh tabel {i>edge<i} melalui {i>foreign key<i}, {i>foreign key<i} harus didefinisikan dengan ON DELETE CASCADE.

Dalam contoh berikut, AccountTransferAccount disimpan hingga sepuluh tahun saat akun tetap aktif. Setelah akun dihapus, transfer juga 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 tepi

Anda bisa menggunakan tabel input yang sama untuk mendefinisikan lebih dari satu {i>node<i} dan {i>edge<i} di skema.

Pada tabel contoh berikut, node Account memiliki kunci gabungan (owner_id, account_id). Ada definisi edge implisit, node Person dengan kunci (id) memiliki node Account dengan kunci gabungan (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 Account node dan tepi PersonOwnAccount, seperti yang ditunjukkan dalam contoh skema berikut. Untuk memastikan bahwa semua nama tabel elemen unik, contoh memberikan nilai tepi tabel yang mendefinisikan alias Owns.

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