• Property Graph ของ Postgres 19 คือความสามารถ SQL/PGQ ที่ประกาศตารางเดิมให้เป็นจุดยอดและเส้นเชื่อม แล้วค้นหารูปแบบความสัมพันธ์แบบคงที่ด้วย MATCH โดยไม่ต้องคัดลอกข้อมูลหรือสร้างเอนจินประมวลผลกราฟแยกต่างหาก
  • รูปแบบกราฟจะถูก คอมไพล์เป็น relational join จึงยังใช้ optimizer, index และสถิติเดิมได้ทั้งหมด ทำให้การวิเคราะห์และปรับแต่ง traversal ที่ช้าทำได้แบบเดียวกับ join ปกติ
  • ตารางมิติสามารถแมปเป็นจุดยอดได้ตามธรรมชาติ และตาราง join ล้วน ๆ ก็แมปเป็นเส้นเชื่อมได้เช่นกัน แต่ fact table ที่มี foreign key หลายตัวและมีคุณสมบัติของตัวเอง เหมาะจะโมเดลเป็นจุดยอดศูนย์กลางที่แทนอีเวนต์มากกว่า
  • ตารางเดียวกันสามารถประกาศเป็นทั้งจุดยอดและหลายเส้นเชื่อมพร้อมกันได้ด้วย alias คนละตัว จึง ไม่จำเป็นต้องสร้างหรือเก็บตารางเส้นเชื่อมแยก โดยใช้ foreign key เดิมของ results ได้เลย
  • เนื่องจาก Postgres 19 ยังไม่รองรับเส้นทางความยาวแปรผัน จึงไม่เหมาะกับ shortest path, การเข้าถึงภายใน N-hop หรือ PageRank แต่เหมาะกับการค้นหาแบบความยาวคงที่ที่รู้โครงสร้างความสัมพันธ์ล่วงหน้า

วิธีอ่าน relational schema ให้เป็นกราฟ

  • ใน relational schema แบบ normalized ตารางมิติอย่าง drivers, constructors, circuits จะเก็บเอนทิตี ส่วน fact table จะบันทึกอีเวนต์อย่างผลการแข่งขันหรือรอบคัดเลือก
  • แต่ละแถวคือ จุดยอด ที่เป็นไปได้ และ foreign key คือ เส้นเชื่อม ที่เป็นไปได้ซึ่งชี้ไปยังแถวอื่น
    • ER diagram เทียบได้กับกราฟในระดับสคีมา
    • แถวจริงและความสัมพันธ์ผ่าน foreign key เทียบได้กับกราฟในระดับอินสแตนซ์
  • คำขออย่าง “หา constructor ที่สอดคล้องกับแต่ละ result” ก็คือการ traversal กราฟที่ใน relational SQL เขียนเป็น results JOIN constructors ON ...
  • SQL/PGQ ไม่ได้เพิ่มกราฟชุดใหม่เข้าไป แต่ช่วยให้ เขียนคำถามในรูปแบบกราฟ แทนการไล่เขียน join ตรง ๆ
  • แนวทาง RelBench จะดึงตารางออกมาเป็น pandas แล้วประกอบเป็นกราฟ PyTorch ในหน่วยความจำ แต่ property graph ของ Postgres ใช้ความสัมพันธ์ของตารางเดิมในฐานข้อมูลโดยตรง
  • ตัวอย่างของ PyG ก็ส่วนใหญ่ใช้ไฟล์แบบแบนหรือกราฟในหน่วยความจำ
  • โค้ดทดลองกับชุดข้อมูล Formula 1 ดูได้ที่ GitHub repository

องค์ประกอบของ property graph

  • CREATE PROPERTY GRAPH คือ อ็อบเจ็กต์ประกาศแบบมีชื่อ ที่สร้างขึ้นบนตารางเดิม
    • VERTEX TABLES ระบุตารางที่จะอ่านแต่ละแถวเป็นจุดยอด
    • EDGE TABLES ระบุตารางที่จะอ่านแต่ละแถวเป็นความสัมพันธ์เชื่อมต่อ
  • สำหรับแต่ละตารางจุดยอด จะกำหนดองค์ประกอบต่อไปนี้
    • KEY: ตัวระบุของจุดยอด ซึ่งส่วนใหญ่มักใช้ primary key
    • LABEL: ชื่อประเภทจุดยอดที่จะใช้ใน MATCH
    • PROPERTIES: รายการคอลัมน์ที่เข้าถึงได้จากการ query แบบกราฟ
  • สำหรับแต่ละตารางเส้นเชื่อม ต้องระบุ SOURCE และ DESTINATION โดยทั้งสองค่านี้อ้างอิงคีย์ของจุดยอด
  • CREATE PROPERTY GRAPH ไม่ได้ย้ายหรือคัดลอกข้อมูล
    • แถวยังคงอยู่ในตารางเดิม
    • ตัวประกาศเพียงแค่นิยามวิธีอ่านโครงสร้าง foreign key เดิมให้เป็นกราฟ
  • องค์ประกอบเชิงโครงสร้างของ property graph มีเพียง จุดยอดและเส้นเชื่อม สองชนิดเท่านั้น
    • label และ property เป็นคุณลักษณะที่สังกัดจุดยอดหรือเส้นเชื่อม
    • ใน psql ค่า Element Kind ของแต่ละองค์ประกอบจะเป็น vertex หรือ edge เท่านั้น

query รูปแบบคงที่ด้วย MATCH

  • การ query กราฟจะเขียนรูปแบบ MATCH ภายใน GRAPH_TABLE(...)
  • แพตเทิร์นต่อไปนี้แสดงความสัมพันธ์จาก driver ผ่าน result ไปยัง race
MATCH (d IS driver)<-[IS of_driver]-(res IS result)-[IS in_race]->(ra IS race)  
  • clause COLUMNS ใช้ระบุคอลัมน์ที่ผลลัพธ์ของการ query กราฟจะส่งออกมา
  • ใน SELECT ชั้นนอก สามารถ query ผลลัพธ์จาก GRAPH_TABLE(...) ได้เหมือนตารางทั่วไป
  • เพราะแพตเทิร์นมีทั้งทิศทางและ label จึงอ่านความสัมพันธ์ได้คล้ายประโยค ทำให้เข้าใจโครงสร้างได้ง่ายกว่า multi-join แบบเดียวกัน

โครงสร้างที่ถูกแปลงเป็น relational join ตอนรันจริง

  • MATCH ไม่ใช่เอนจินประมวลผลกราฟแยกต่างหากที่นำมาต่อเพิ่มใน Postgres แต่เป็น syntax ที่คอมไพล์เป็น relational join
  • เมื่อลอง EXPLAIN กับแพตเทิร์น driver–result–race จะเห็น hash join 4 ตัวที่ทำงานบนตารางต้นทาง
  • เช่นเดียวกับ join ที่เขียนเอง มันยังใช้สิ่งต่อไปนี้
    • optimizer เดิมของ Postgres
    • index เดิม
    • สถิติเดิม
  • หาก traversal ของกราฟช้า สาเหตุและแนวทางปรับแต่งก็เหมือนกับกรณีที่ join ปกติช้า
  • ใน psql ก็สามารถสำรวจกราฟได้ด้วยคำสั่งลักษณะคล้ายการดูตาราง
    • \dG: แสดงรายการ property graph
    • \d f1: แสดงแต่ละจุดยอดและเส้นเชื่อม ตารางต้นทาง ชนิดขององค์ประกอบ และจุดยอดต้นทาง/ปลายทางของเส้นเชื่อม
    • \d+ f1: สร้างคำสั่ง CREATE PROPERTY GRAPH แบบเต็มขึ้นมาใหม่ รวมถึงคีย์ของเส้นเชื่อมที่อนุมานจาก primary key ของตารางเส้นเชื่อม

ความต่างระหว่าง key และ property

  • คอลัมน์ที่กำหนดเป็น KEY ของจุดยอด จะไม่กลายเป็น property โดยอัตโนมัติ
  • ถ้ากำหนด driver_id เป็นเพียงคีย์ของจุดยอด แล้วกรองด้วย d.driver_id = 1 จะล้มเหลว เพราะคอลัมน์นั้นไม่ได้ถูกเปิดให้ query เป็น property
  • key ใช้ระบุจุดยอด แต่ไม่ได้เปิดเผยต่อ query
  • หากต้องการกรองหรือคืนค่าคอลัมน์ ID ต้องเพิ่มลงในรายการ PROPERTIES อย่างชัดเจน

ข้อจำกัดเรื่องเส้นทางความยาวแปรผันใน Postgres 19

  • Postgres 19 ยังไม่รองรับ element pattern quantifier แบบตามเส้นเชื่อม 1~3 ครั้ง
ERROR: element pattern quantifier is not supported  
  • ถ้าต้องการเดินสองฮอป ต้องระบุแพตเทิร์นเส้นเชื่อมสองชุดใน MATCH แบบชัดเจน
  • ไม่สามารถแสดงเส้นทางความยาวตามอำเภอใจด้วย syntax ของ property graph ได้
  • การเดินกราฟที่เปิดความลึกไว้ต้องใช้ recursive CTE บนตารางต้นทาง ซึ่งจะออกนอก syntax ของ property graph

การแมปตารางเดิมให้เป็นจุดยอดและเส้นเชื่อม

  • ตารางมิติเป็นจุดยอด

    • ตารางเอนทิตีอย่าง drivers, constructors, circuits ที่มี primary key คงที่และมีแอตทริบิวต์ของตัวเอง สามารถแมปเป็นจุดยอดได้ตรงไปตรงมา
    • ใช้ primary key เป็นคีย์ของจุดยอด และเปิดเผยคอลัมน์ที่ต้องการเป็น property ก็เพียงพอ
  • ตาราง join ล้วน ๆ เป็นเส้นเชื่อม

    • ตาราง bridge แบบ many-to-many อย่าง student_courses(student_id, course_id) มีหน้าที่เดิมคือเชื่อมสองเอนทิตี จึง แมปเป็นเส้นเชื่อมได้อย่างเป็นธรรมชาติ
    • ประกาศ foreign key ฝั่งหนึ่งเป็นจุดยอดต้นทาง และอีกฝั่งเป็นจุดยอดปลายทาง
    • เพราะแต่ละแถวของตาราง join คือความสัมพันธ์หนึ่งรายการอยู่แล้ว จึงไม่ต้องแปลงข้อมูลแยกสำหรับกราฟ
    • ในโครงสร้างนี้ students และ courses จะเป็นจุดยอด ส่วน student_courses จะเป็นเส้นเชื่อม enrolled_in
  • fact table เป็นจุดยอดของอีเวนต์

    • แถวใน results ชี้ไปยังสามเอนทิตีคือ driver, race และ constructor พร้อมทั้งมีข้อมูลของตัวเองอย่าง grid, position, points, status
    • เนื่องจากเส้นเชื่อมใน SQL/PGQ เป็น ความสัมพันธ์แบบสองทางเลือก ที่มีต้นทางหนึ่งและปลายทางหนึ่ง จึงไม่สามารถนำทั้งแถวที่มี foreign key สามตัวมาเป็นเส้นเชื่อมเดี่ยวได้
    • ถ้า fact row เองเป็นสิ่งที่ต้องการวิเคราะห์ การประกาศตารางนั้นเป็นจุดยอดจะเหมาะกว่า
    • จุดยอด results จะเก็บอีเวนต์และคุณสมบัติของมัน
    • เส้นเชื่อมขนาดแคบอย่าง results_driver, results_race, results_constructor จะใช้เชื่อมไปยังเอนทิตีภายนอก
    • โมเดลนี้สร้าง โครงสร้างแบบ hub เช่น driver <- result -> race และสามารถหยุดที่จุดยอด result เพื่อกรองหรืออ่าน property ได้
    • หากสิ่งที่สนใจคือความสัมพันธ์เอง ให้โมเดลเป็นเส้นเชื่อม แต่ถ้าสิ่งที่สนใจคือแถวเฉพาะหรืออีเวนต์ที่มีคุณสมบัติเฉพาะ ให้โมเดลเป็นจุดยอด
    • ตาราง join แทนความสัมพันธ์ ส่วน fact table แทนอีเวนต์

ใช้ตารางเดียวกันเป็นทั้งจุดยอดและเส้นเชื่อมพร้อมกัน

  • การแบ่งว่าเป็น “จุดยอดหรือเส้นเชื่อม” ใช้กับ การประกาศองค์ประกอบ แต่ละตัวในกราฟ ไม่ได้ผูกติดกับตารางต้นทาง
  • ตารางเดียวกันสามารถประกาศในทั้ง VERTEX TABLES และ EDGE TABLES ได้พร้อมกันด้วย alias ที่ต่างกัน
  • สามารถใช้ results เป็นจุดยอด result และนำตารางเดียวกันกลับมาใช้ซ้ำเป็น alias ของเส้นเชื่อมดังนี้
    • results AS res_driver: เชื่อมจาก result ไปยัง driver
    • results AS res_race: เชื่อมจาก result ไปยัง race
    • results AS res_constr: เชื่อมจาก result ไปยัง constructor
  • alias ของเส้นเชื่อมแต่ละตัวใช้คอลัมน์ primary key และ foreign key ที่มีอยู่แล้วใน results
  • ไม่จำเป็นต้องสร้างตาราง results_driver, results_race, results_constructor แยก และ alias ก็ไม่ได้เก็บข้อมูลใด ๆ
  • fact table ที่มี foreign key สามตัวต้องประกาศเป็น alias ของเส้นเชื่อมสามตัว โดยแต่ละตัวมีต้นทางและปลายทางอย่างละหนึ่ง ไม่ใช่เส้นเชื่อมเดียวที่มีหลายปลายทาง
  • วิธีนี้ใช้ alias เชิงประกาศสามตัวบนตารางต้นทางเดียว แทนการสร้างตารางจริงหรือวิวสามชุด

การชนกันของชื่อ property และชนิดข้อมูล

  • ถ้าละ PROPERTIES ไว้ SQL/PGQ จะ เปิดเผยทุกคอลัมน์ของตารางเป็น property
  • หากทั้ง results และ qualifying มีคอลัมน์ number เหมือนกัน แต่ชนิดข้อมูลเป็น double precision กับ bigint ตามลำดับ จะเกิดข้อผิดพลาดนี้
ERROR: property "number" data type mismatch: double precision vs. bigint  
  • เพราะภายในกราฟเดียวกัน property ที่มีชื่อเดียวกันต้องมีชนิดข้อมูลเดียวกัน คอลัมน์ชื่อซ้ำที่ชนิดต่างกันจึงชนกัน
  • การระบุเฉพาะคอลัมน์ที่ต้องการใน PROPERTIES จะช่วยตัดคอลัมน์ที่ชนกันออกจากกราฟได้
  • ทั้งปัญหาที่ key ไม่ได้กลายเป็น property อัตโนมัติ และปัญหาชนกันของชนิดข้อมูล แก้ได้ด้วย allowlist ของ property แบบ explicit

query ที่เหมาะและไม่เหมาะ

  • รูปแบบความสัมพันธ์แบบคงที่

    • property graph เหมาะกับคำถามประเภท “หาสิ่งที่เชื่อมกับ X ด้วยแพตเทิร์นเฉพาะนี้”
    • ในกราฟ Formula 1 สามารถเขียน query อย่างเช่น
      1. หา driver คนหนึ่งว่าเคยแข่งให้ constructor ใดบ้าง
      2. หา rival ด้วยแพตเทิร์นจาก driver ผ่าน result และ race ไปยัง result อื่นและ driver อื่น
      3. กรองทั้งโครงสร้างและเงื่อนไขของ property เช่นผลการแข่งขันที่อันดับออกตัวเกิน 10 และ driver เป็นชาวอิตาลี
    • เมื่อรู้รูปร่างของความสัมพันธ์ล่วงหน้า และโครงสร้างนั้น คงที่และมีขอบเขตจำกัด syntax สำหรับค้นหา กรอง และสรุปผลจะอ่านง่ายขึ้น
    • แม้เป็น query ที่ต้อง self-join หลายครั้ง ก็สามารถเขียนให้อ่านง่ายด้วยแพตเทิร์น MATCH เดียว
  • ปัญหาที่ไม่รู้เส้นทางล่วงหน้า

    • ปัญหาต่อไปนี้ไม่เหมาะกับ property graph ของ Postgres 19
      • shortest path ระหว่าง driver สองคน
      • ทุกเป้าหมายที่เข้าถึงได้ภายใน N-hop
      • การสำรวจความเชื่อมต่อที่ไม่รู้ความลึกล่วงหน้า
    • query เหล่านี้ต้องใช้ traversal ความยาวแปรผัน จึงต้องใช้ recursive CTE บนตารางต้นทาง
    • อัลกอริทึมกราฟอย่าง PageRank, การตรวจหาคอมมูนิตี้, การคำนวณ centrality ก็เป็นคนละปัญหากับ pattern matching จึงไม่อยู่ในขอบเขตของฟีเจอร์นี้
    • property graph ช่วยหาสิ่งที่เชื่อมต่อกันเมื่อรู้โครงสร้างเส้นทางอยู่แล้ว แต่ไม่สามารถค้นพบเส้นทางเมื่อยังไม่รู้ว่า X กับ Y เชื่อมกันอย่างไร หรือคำนวณความสำคัญเชิงโครงสร้างของกราฟได้

สิ่งที่ควรพิจารณาก่อนนำไปใช้

  • SQL/PGQ คือ declarative overlay ที่วางทับบนโครงสร้าง foreign key เดิม และจะไม่เก็บข้อมูลเพิ่ม เว้นแต่จะเลือกทำแยกเอง
  • ประโยชน์หลักมีสองอย่าง
    • เขียน traversal แบบคงรูปให้อ่านง่ายกว่า relational join
    • มีอ็อบเจ็กต์แบบมีชื่อไว้ใช้บันทึกสคีมาในรูปกราฟ
  • ข้อจำกัดใหญ่ที่สุดคือไม่มีเส้นทางความยาวแปรผัน ทำให้การสำรวจที่ลึกหรือเปิดปลายต้องใช้ recursive SQL
  • ตัวตารางเองไม่ได้เป็นจุดยอดหรือเส้นเชื่อมโดยเนื้อแท้ แต่ในแต่ละกราฟสามารถตัดสินใจได้ว่าจะอ่านแถวเหล่านั้นเป็นจุดยอด เส้นเชื่อม หรือทั้งสองแบบ
  • หากต้องการ query รูปแบบกราฟแบบคงที่บนสคีมาเดิมใน Postgres 19 อาจลองพิจารณา CREATE PROPERTY GRAPH และ MATCH ก่อนย้ายข้อมูลไปฐานข้อมูลกราฟแยก
  • หากต้องสำรวจความลึกที่ไม่ทราบล่วงหน้า ก็ยังต้องใช้ recursive CTE แต่ไม่ว่าจะใช้วิธีใด ข้อมูลก็ไม่จำเป็นต้องออกจาก Postgres
  • ก่อนใช้งานจริงควรทดสอบด้วยตัวเองว่าสามารถให้ประสิทธิภาพตามต้องการได้หรือไม่

ยังไม่มีความคิดเห็น

ยังไม่มีความคิดเห็น