Praktik terbaik untuk mendesain skema Grafik Spanner

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

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

Mengoptimalkan penelusuran tepi

Penelusuran tepi adalah proses menavigasi grafik dengan mengikuti tepinya, dimulai dari node tertentu dan bergerak di sepanjang tepi yang terhubung untuk mencapai node lain. Arah tepi ditentukan oleh skema. Penelusuran tepi adalah operasi mendasar dalam Spanner Graph, sehingga peningkatan efisiensi penelusuran tepi sangat penting untuk performa aplikasi Anda.

Anda dapat melintasi tepi dalam dua arah:

  • forward edge traversal: mengikuti tepi keluar dari node sumber.

  • traversal tepi terbalik: mengikuti tepi masuk dari node tujuan.

Mengingat seseorang, contoh kueri berikut melakukan traversal tepi maju dari tepi Owns:

GRAPH FinGraph
MATCH (person:Person {id: 1})-[owns:Owns]->(accnt:Account)
RETURN accnt.id;

Dengan akun tertentu, kueri contoh berikut melakukan penelusuran tepi terbalik dari tepi Owns:

GRAPH FinGraph
MATCH (accnt:Account {id: 1})<-[owns:Owns]-(person:Person)
RETURN person.name;

Mengoptimalkan traversal edge penerusan menggunakan penyisipan

Untuk meningkatkan performa traversal tepi penerusan, selang-selingi tabel input tepi ke dalam tabel input node sumber untuk mengelokasikan tepi dengan node sumber. Penyisipan adalah teknik pengoptimalan penyimpanan di Spanner yang secara fisik menempatkan baris tabel turunan bersama baris induk yang sesuai di penyimpanan. Untuk mengetahui informasi selengkapnya tentang penyisipan, 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 penelusuran tepi terbalik menggunakan kunci asing

Untuk melintasi tepi terbalik secara efisien, buat batasan kunci asing yang diterapkan antara tepi dan node tujuan. Kunci asing yang diterapkan ini membuat indeks sekunder di tepi yang dikunci oleh kunci node tujuan. Indeks sekunder 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 yang diterapkan pada edge, misalnya, karena integritas data yang ketat yang diterapkan, Anda dapat langsung membuat indeks sekunder pada 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 AccountOwnedByPerson
ON PersonOwnAccount (account_id), INTERLEAVE IN Account;

INTERLEAVE IN mendeklarasikan hubungan lokalitas data antara indeks sekunder dan tabel yang disisipkan (Account, dalam contoh). Dengan penyisipan, baris indeks sekunder AccountOwnedByPerson ditempatkan bersama dengan baris yang sesuai dari tabel Account. Untuk mengetahui informasi selengkapnya tentang interleaving, lihat Hubungan tabel induk-turunan. Untuk mengetahui informasi selengkapnya tentang indeks penyisipan, lihat Indeks dan penyisipan.

Mengoptimalkan penelusuran tepi menggunakan kunci asing informasional

Jika skenario Anda memiliki hambatan performa penulisan yang disebabkan oleh kunci asing yang diterapkan, seperti saat Anda sering memperbarui node hub yang memiliki banyak edge yang terhubung, pertimbangkan untuk menggunakan kunci asing informasional. Menggunakan kunci asing informasional pada kolom yang mereferensikan tabel edge akan membantu pengoptimal kueri menghilangkan pemindaian tabel node yang berlebihan. Namun, karena kunci asing informatif tidak memerlukan indeks sekunder pada tabel edge, kunci asing tersebut tidak meningkatkan kecepatan pencarian saat kueri mencoba menemukan edge menggunakan node akhir. Untuk mengetahui informasi selengkapnya, lihat Perbandingan jenis kunci asing.

Penting untuk dipahami bahwa jika aplikasi Anda tidak dapat menjamin integritas referensial, penggunaan kunci asing informasional untuk pengoptimalan kueri dapat menyebabkan hasil kueri yang salah.

Contoh berikut membuat tabel dengan kunci asing informasional pada kolom account_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) NOT ENFORCED
) PRIMARY KEY (id, account_id),
  INTERLEAVE IN PARENT Person ON DELETE CASCADE;

Jika penyisipan bukan opsi, Anda dapat menandai kedua referensi tepi dengan kunci asing informasi, seperti dalam contoh berikut:

CREATE TABLE PersonOwnAccount (
  id               INT64 NOT NULL,
  account_id       INT64 NOT NULL,
  create_time      TIMESTAMP,
  CONSTRAINT FK_Person FOREIGN KEY (id)
    REFERENCES Person (id) NOT ENFORCED,
  CONSTRAINT FK_Account FOREIGN KEY (account_id)
    REFERENCES Account (id) NOT ENFORCED
) PRIMARY KEY (id, account_id);

Jangan biarkan tepi menggantung

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

Tidak mengizinkan ujung yang tidak terhubung memberikan manfaat berikut:

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

Melarang ujung yang menggantung menggunakan batasan referensial

Untuk melarang ujung yang tidak terhubung, tentukan batasan pada kedua endpoint:

  • Selang-selingi tabel input tepi ke dalam tabel input node sumber. Pendekatan ini memastikan bahwa node sumber dari tepi selalu ada.
  • Buat batasan kunci asing yang diterapkan pada edge untuk memastikan bahwa node tujuan edge selalu ada.

Contoh berikut menggunakan penyisipan dan kunci asing yang diterapkan untuk menerapkan 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 menghapus tepi secara otomatis saat menghapus node

Saat Anda menggunakan penyisipan atau kunci asing yang diterapkan untuk melarang ujung yang menggantung, gunakan klausa ON DELETE untuk mengontrol perilaku saat Anda ingin menghapus node dengan ujung yang masih terlampir. Untuk mengetahui informasi selengkapnya, lihat Menghapus secara bertingkat untuk tabel yang disisipkan dan Tindakan 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 tepi terkait dalam transaksi yang sama.

Cascade penghapusan untuk tepi yang menghubungkan berbagai jenis node

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

  • Menghapus tepi saat node tujuan dihapus. Misalnya, CONSTRAINT FK_Account FOREIGN KEY(account_id) REFERENCES Account(id) ON DELETE CASCADE menghapus semua edge PersonOwnAccount masuk ke dalam node Account yang dihapus.

Cascade penghapusan untuk tepi yang menghubungkan jenis node yang sama

Jika node sumber dan tujuan dari suatu edge memiliki jenis yang sama dan edge disisipkan ke dalam node sumber, Anda dapat menentukan ON DELETE CASCADE hanya untuk node sumber atau node tujuan (tetapi tidak untuk kedua node).

Untuk menghapus tepi yang tidak terhubung dalam kedua kasus, buat kunci asing yang diterapkan pada referensi node sumber tepi, bukan menyisipkan tabel input tepi ke dalam tabel input node sumber.

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

--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. Indeks ini mendukung pencarian cepat node dan tepi berdasarkan nilai properti tertentu, tanpa harus menjelajahi seluruh struktur grafik. Hal ini penting saat Anda bekerja dengan grafik besar, karena melintasi semua node dan tepi dapat sangat tidak efisien.

Mempercepat pemfilteran node menurut properti

Untuk mempercepat pemfilteran menurut properti node, buat indeks sekunder pada properti. Misalnya, kueri berikut menemukan akun untuk nama panggilan tertentu. Tanpa indeks sekunder, semua node Account dipindai untuk mencocokkan 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 sparse. Untuk mengetahui informasi selengkapnya, lihat Menonaktifkan pengindeksan nilai NULL.

Mempercepat penelusuran edge penerusan dengan pemfilteran pada properti edge

Saat melintasi 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 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). Gabungkan indeks di bawah tabel input node sumber untuk menempatkan 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 menggunakan pendekatan ini, kueri dapat secara efisien menemukan semua tepi yang memenuhi kondisi pada create_time.

Mempercepat penelusuran edge terbalik dengan pemfilteran pada properti edge

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

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

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 edge (account_id) dan properti edge (create_time), 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 PersonOwnAccountByCreateTime
    ON PersonOwnAccount (account_id, create_time);
    

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

  • Buat indeks sekunder pada referensi node tujuan edge (account_id) dan simpan properti edge (create_time) dalam 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 pedoman berikut:

  • Gunakan properti edge di kolom indeks jika 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 tepi biasanya dimodelkan dengan label. Namun, Anda juga dapat menggunakan properti untuk memodelkan jenis. Pertimbangkan contoh yang memiliki banyak jenis akun yang berbeda, seperti BankAccount, InvestmentAccount, dan RetirementAccount. Anda dapat menyimpan akun dalam tabel input terpisah dan memodelkannya sebagai label terpisah, atau Anda dapat menyimpan akun dalam satu tabel input dan menggunakan properti untuk membedakan jenisnya.

Mulai proses pembuatan model dengan membuat model jenis dengan label. Pertimbangkan untuk menggunakan properti dalam skenario berikut.

Meningkatkan pengelolaan skema

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

Jenis model dalam properti untuk mengelola jenis yang sering berubah

Saat Anda memodelkan 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 membatasi pemrosesan pembaruan skema yang diantrekan. Untuk mengetahui informasi selengkapnya, lihat Membatasi frekuensi update skema.

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

Mempercepat kueri

Jenis pemodelan dengan properti dapat mempercepat kueri saat pola node atau tepi mereferensikan 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 penting untuk performa, pertimbangkan untuk memodelkan Account menggunakan properti. Pendekatan ini dapat memberikan performa kueri yang lebih baik, seperti yang ditunjukkan dalam contoh kueri berikut. Sebaiknya Anda melakukan tolok ukur kedua kueri.

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

Simpan jenis toko 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 edge.
  3. Sertakan jenis node dalam kunci perujukan edge.

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 tepi

Time to live (TTL) Spanner adalah mekanisme yang mendukung habis 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 edge yang disisipkan di dalamnya, penyisipan harus ditentukan dengan ON DELETE CASCADE. Demikian pula, jika tabel node memiliki TTL dan direferensikan oleh tabel edge melalui kunci asing, kunci asing harus ditentukan dengan ON DELETE CASCADE untuk mempertahankan integritas referensial, atau ditentukan sebagai kunci asing informasi untuk memungkinkan keberadaan edge yang tidak terhubung.

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 gabungan (owner_id, account_id). Ada definisi edge implisit, node Person dengan kunci (id) memiliki node Account dengan kunci gabungan (owner_id, account_id) jika 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 edge.

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 berikutnya