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

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

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

วิธีวางโครงสร้างข้อมูล: เป็นแผนผัง JSON

ข้อมูลฐานข้อมูลเรียลไทม์ของ Firebase ทั้งหมดจะจัดเก็บเป็นออบเจ็กต์ JSON อาจคิดว่าฐานข้อมูลเป็นแผนผัง JSON ที่โฮสต์บนระบบคลาวด์ก็ได้ แตกต่างจากฐานข้อมูล SQL ตรงที่ไม่มีตารางหรือระเบียน เมื่อเพิ่มข้อมูลลงในทรี JSON ข้อมูลนั้นจะกลายเป็นโหนดในโครงสร้าง JSON ที่มีอยู่ด้วยคีย์ที่เชื่อมโยง คุณจะระบุคีย์ของคุณเอง เช่น รหัสผู้ใช้ หรือชื่อความหมาย หรือระบุคีย์เองโดยใช้เมธอด push() ก็ได้

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

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

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

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

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

หลีกเลี่ยงการซ้อนข้อมูล

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

สำหรับตัวอย่างสาเหตุที่ข้อมูลแบบซ้อนไม่ดี ให้ลองพิจารณาโครงสร้างแบบซ้อนกันต่อไปนี้

{
  // 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 ป้องกันไม่ให้เข้าถึงระเบียนบางรายการ

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

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