จัดโครงสร้างฐานข้อมูลของคุณ

คู่มือนี้ครอบคลุมแนวคิดหลักบางประการในสถาปัตยกรรมข้อมูลและแนวทางปฏิบัติแนะนำในการจัดโครงสร้างข้อมูล JSON ใน Firebase Realtime Database

การสร้างฐานข้อมูลที่โครงสร้างถูกต้องต้องอาศัยการวางแผนล่วงหน้าอย่างมาก ที่สำคัญที่สุด คุณต้องวางแผนวิธีบันทึกและเรียกข้อมูลในภายหลังเพื่อให้กระบวนการนี้ง่ายที่สุด

โครงสร้างข้อมูล: เป็นต้นไม้ JSON

ระบบจะจัดเก็บข้อมูล Firebase Realtime Database ทั้งหมดเป็นออบเจ็กต์ JSON คุณอาจนึกถึงฐานข้อมูลเป็นต้นไม้ JSON ที่โฮสต์ในระบบคลาวด์ ไม่มีตารางหรือระเบียนเหมือนกับฐานข้อมูล SQL เมื่อคุณเพิ่มข้อมูลลงในโครงสร้าง JSON ข้อมูลนั้นจะกลายเป็นโหนดในโครงสร้าง JSON ที่มีอยู่ซึ่งมีคีย์ที่เชื่อมโยง คุณสามารถระบุคีย์ของคุณเอง เช่น รหัสผู้ใช้หรือชื่อเชิงความหมาย หรือระบบจะระบุคีย์ให้คุณโดยใช้คำขอ POST

หากคุณสร้างคีย์ของคุณเอง คีย์จะต้องเข้ารหัส UTF-8 โดยต้องมีขนาดไม่เกิน 768 ไบต์ และต้องไม่มีอักขระควบคุม ., $, #, [, ], / หรือ ASCII ตั้งแต่ 0-31 หรือ 127 คุณไม่สามารถใช้อักขระควบคุม ASCII ในค่าได้เช่นกัน

ตัวอย่างเช่น ลองพิจารณาแอปพลิเคชันแชทที่อนุญาตให้ผู้ใช้จัดเก็บโปรไฟล์พื้นฐานและรายชื่อติดต่อ โปรไฟล์ผู้ใช้ทั่วไปจะอยู่ที่เส้นทาง เช่น /users/$uid ผู้ใช้ alovelace อาจมีรายการฐานข้อมูลที่มีลักษณะดังนี้

{
  "users": {
    "alovelace": {
      "name": "Ada Lovelace",
      "contacts": { "ghopper": true },
    },
    "ghopper": { ... },
    "eclarke": { ... }
  }
}

แม้ว่าฐานข้อมูลจะใช้โครงสร้าง JSON แต่ข้อมูลที่จัดเก็บในฐานข้อมูลอาจแสดงเป็นประเภทเนทีฟบางประเภทที่สอดคล้องกับประเภท JSON ที่มีอยู่เพื่อช่วยให้คุณเขียนโค้ดที่ดูแลรักษาได้มากขึ้น

แนวทางปฏิบัติแนะนำสำหรับโครงสร้างข้อมูล

หลีกเลี่ยงการฝังข้อมูล

เนื่องจาก Firebase Realtime Database อนุญาตให้ฝังข้อมูลได้ลึกสุด 32 ชั้น คุณจึงอาจคิดว่านี่ควรเป็นโครงสร้างเริ่มต้น อย่างไรก็ตาม เมื่อดึงข้อมูลในตำแหน่งในฐานข้อมูล คุณจะดึงข้อมูลโหนดย่อยทั้งหมดของโหนดนั้นด้วย นอกจากนี้ เมื่อคุณให้สิทธิ์เข้าถึงระดับอ่านหรือเขียนแก่ผู้ใช้ที่โหนดในฐานข้อมูล เท่ากับว่าคุณให้สิทธิ์เข้าถึงข้อมูลทั้งหมดภายใต้โหนดนั้นด้วย ดังนั้น ในทางปฏิบัติคุณควรทำให้โครงสร้างข้อมูลคงที่ที่สุดเท่าที่จะทำได้

ตัวอย่างที่แสดงให้เห็นว่าทำไม Structured Data ที่ฝังอยู่จึงไม่ดีคือโครงสร้างที่ฝังอยู่หลายชั้นต่อไปนี้

{
  // This is a poorly nested data architecture, because iterating the children
  // of the "chats" node to get a list of conversation titles requires
  // potentially downloading hundreds of megabytes of messages
  "chats": {
    "one": {
      "title": "Historical Tech Pioneers",
      "messages": {
        "m1": { "sender": "ghopper", "message": "Relay malfunction found. Cause: moth." },
        "m2": { ... },
        // a very long list of messages
      }
    },
    "two": { ... }
  }
}

การออกแบบที่ซ้อนกันนี้ทําให้การทำซ้ำข้อมูลเกิดปัญหา ตัวอย่างเช่น การแสดงชื่อของการสนทนาผ่านแชทจำเป็นต้องดาวน์โหลดโครงสร้าง chats ทั้งหมดซึ่งรวมถึงสมาชิกและข้อความทั้งหมดไปยังไคลเอ็นต์

รวมโครงสร้างข้อมูล

หากข้อมูลแยกออกเป็นเส้นทางแยกต่างหากแทน ซึ่งเรียกว่า "การแปลงข้อมูลให้เป็นรูปแบบปกติ" ระบบจะดาวน์โหลดข้อมูลได้อย่างมีประสิทธิภาพในการเรียกใช้แยกต่างหากตามต้องการ ลองดูโครงสร้างแบบแบนนี้

{
  // Chats contains only meta info about each conversation
  // stored under the chats's unique ID
  "chats": {
    "one": {
      "title": "Historical Tech Pioneers",
      "lastMessage": "ghopper: Relay malfunction found. Cause: moth.",
      "timestamp": 1459361875666
    },
    "two": { ... },
    "three": { ... }
  },

  // Conversation members are easily accessible
  // and stored by chat conversation ID
  "members": {
    // we'll talk about indices like this below
    "one": {
      "ghopper": true,
      "alovelace": true,
      "eclarke": true
    },
    "two": { ... },
    "three": { ... }
  },

  // Messages are separate from data we may want to iterate quickly
  // but still easily paginated and queried, and organized by chat
  // conversation ID
  "messages": {
    "one": {
      "m1": {
        "name": "eclarke",
        "message": "The relay seems to be malfunctioning.",
        "timestamp": 1459361875337
      },
      "m2": { ... },
      "m3": { ... }
    },
    "two": { ... },
    "three": { ... }
  }
}

ตอนนี้คุณจะทำซ้ำผ่านรายการห้องแชทได้ด้วยการดาวน์โหลดเพียง 2-3 ไบต์ต่อการสนทนา แล้วดึงข้อมูลเมตาสำหรับข้อมูลหรือการแสดงห้องใน UI ได้อย่างรวดเร็ว คุณจะดึงข้อมูลและแสดงข้อความแยกกันได้เมื่อเข้ามาใหม่ ทำให้ UI ตอบสนองอย่างรวดเร็วอยู่เสมอ

สร้างข้อมูลที่ปรับขนาดได้

เมื่อสร้างแอป การดาวน์โหลดรายการบางส่วนมักจะดีกว่า ซึ่งมักเกิดขึ้นหากรายการมีเรคคอร์ดหลายพันรายการ เมื่อความสัมพันธ์นี้เป็นแบบคงที่และเป็นแบบทิศทางเดียว คุณก็เพียงแค่ซ้อนออบเจ็กต์ย่อยไว้ใต้ออบเจ็กต์หลัก

บางครั้งความสัมพันธ์นี้เป็นแบบไดนามิกมากขึ้น หรือคุณอาจจำเป็นต้องทำให้ข้อมูลนี้ผิดมาตรฐาน ในหลายกรณี คุณสามารถทำให้ข้อมูลไม่เป็นรูปแบบมาตรฐานได้โดยใช้การค้นหาเพื่อดึงข้อมูลชุดย่อย ตามที่อธิบายไว้ในดึงข้อมูล

แต่วิธีนี้อาจยังไม่เพียงพอ ตัวอย่างเช่น ความสัมพันธ์แบบ 2 ทางระหว่างผู้ใช้กับกลุ่ม ผู้ใช้จะอยู่ในกลุ่มได้ และกลุ่มจะประกอบไปด้วยรายชื่อผู้ใช้ เมื่อต้องเลือกว่าผู้ใช้อยู่ในกลุ่มใด การทำงานจะซับซ้อน

สิ่งที่ต้องใช้คือวิธีการที่ยอดเยี่ยมในการแสดงกลุ่มที่ผู้ใช้เป็นสมาชิก และดึงข้อมูลสำหรับกลุ่มเหล่านั้นเท่านั้น ดัชนีของกลุ่มช่วยได้มากในส่วนนี้

// An index to track Ada's memberships
{
  "users": {
    "alovelace": {
      "name": "Ada Lovelace",
      // Index Ada's groups in her profile
      "groups": {
         // the value here doesn't matter, just that the key exists
         "techpioneers": true,
         "womentechmakers": true
      }
    },
    ...
  },
  "groups": {
    "techpioneers": {
      "name": "Historical Tech Pioneers",
      "members": {
        "alovelace": true,
        "ghopper": true,
        "eclarke": true
      }
    },
    ...
  }
}

คุณอาจสังเกตเห็นว่าการดำเนินการนี้ทำซ้ำข้อมูลบางส่วนด้วยการจัดเก็บความสัมพันธ์ทั้งภายใต้ระเบียนของ Ada และภายใต้กลุ่ม ตอนนี้ alovelace จะได้รับการจัดทำดัชนีภายใต้กลุ่ม และ techpioneers จะแสดงในโปรไฟล์ของ Ada ดังนั้น หากต้องการลบ Ada จากกลุ่ม จะต้องอัปเดตใน 2 แห่ง

นี่เป็นข้อมูลซ้ำที่จำเป็นสำหรับความสัมพันธ์แบบ 2 ทาง ซึ่งช่วยให้คุณดึงข้อมูลการเป็นสมาชิกของ Ada ได้อย่างรวดเร็วและมีประสิทธิภาพ แม้รายชื่อผู้ใช้หรือกลุ่มจะมีจำนวนหลายล้านรายการหรือเมื่อRealtime Databaseกฎด้านความปลอดภัยprevent access to some of the records

วิธีนี้เป็นการสลับข้อมูลโดยแสดงรายการรหัสเป็นคีย์และตั้งค่าเป็น "จริง" ทำให้การตรวจสอบคีย์ง่ายเหมือนการอ่าน /users/$uid/groups/$group_id และตรวจสอบว่าเป็น null หรือไม่ ดัชนีทำงานเร็วกว่า และมีประสิทธิภาพกว่าการค้นหาหรือการสแกนข้อมูล

ขั้นตอนถัดไป