แสดงเนื้อหา Firestore แบบรวมจาก CDN

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

หากเนื้อหานี้มาจาก Cloud Firestore ผู้ใช้แต่ละคนจะออก ค้นหาผลลัพธ์เดียวกันเมื่อโหลดแอปพลิเคชัน เนื่องจาก จะไม่มีการแคชผลลัพธ์ระหว่างผู้ใช้ แอปพลิเคชันจะทำงานช้าลง แพงกว่าที่จำเป็น

วิธีแก้ไข: การรวมกลุ่ม

แพ็กเกจ Cloud Firestore ช่วยให้คุณรวบรวมแพ็กเกจข้อมูลจากการค้นหาทั่วไปได้ ผลลัพธ์ในแบ็กเอนด์โดยใช้ Firebase Admin SDK และแสดงผลลัพธ์เหล่านี้ Blob ที่คำนวณไว้ล่วงหน้าที่แคชไว้ใน CDN สิ่งนี้ช่วยให้ผู้ใช้ การโหลดครั้งแรกที่รวดเร็วขึ้นและลดต้นทุนการค้นหา Cloud Firestore

ในคู่มือนี้ เราจะใช้ Cloud Functions ในการสร้างแพ็กเกจและ โฮสติ้งของ Firebase เพื่อแคชแบบไดนามิกและแสดงเนื้อหาแพ็กเกจ เพิ่มเติม ข้อมูลเกี่ยวกับแพ็กเกจจะอยู่ในคู่มือ

ก่อนอื่น ให้สร้างฟังก์ชัน HTTP สาธารณะแบบง่ายๆ เพื่อค้นหา "เรื่องราว" ล่าสุด 50 รายการ และ แสดงผลเป็นกลุ่ม

Node.js
exports.createBundle = functions.https.onRequest(async (request, response) => {
  // Query the 50 latest stories
  const latestStories = await db.collection('stories')
    .orderBy('timestamp', 'desc')
    .limit(50)
    .get();

  // Build the bundle from the query results
  const bundleBuffer = db.bundle('latest-stories')
    .add('latest-stories-query', latestStories)
    .build();

  // Cache the response for up to 5 minutes;
  // see https://firebase.google.com/docs/hosting/manage-cache
  response.set('Cache-Control', 'public, max-age=300, s-maxage=600');

  response.end(bundleBuffer);
});
      
Java

package com.example;

import com.google.auth.oauth2.GoogleCredentials;
import com.google.cloud.firestore.Firestore;
import com.google.cloud.firestore.FirestoreBundle;
import com.google.cloud.firestore.Query.Direction;
import com.google.cloud.firestore.QuerySnapshot;
import com.google.cloud.functions.HttpFunction;
import com.google.cloud.functions.HttpRequest;
import com.google.cloud.functions.HttpResponse;
import com.google.firebase.FirebaseApp;
import com.google.firebase.FirebaseOptions;
import com.google.firebase.cloud.FirestoreClient;
import java.io.BufferedWriter;
import java.io.IOException;

public class ExampleFunction implements HttpFunction {

  public static FirebaseApp initializeFirebase() throws IOException {
    if (FirebaseApp.getApps().isEmpty()) {
      FirebaseOptions options = FirebaseOptions.builder()
          .setCredentials(GoogleCredentials.getApplicationDefault())
          .setProjectId("YOUR-PROJECT-ID")
          .build();

      FirebaseApp.initializeApp(options);
    }

    return FirebaseApp.getInstance();
  }

  @Override
  public void service(HttpRequest request, HttpResponse response) throws Exception {
    // Get a Firestore instance
    FirebaseApp app = initializeFirebase();
    Firestore db = FirestoreClient.getFirestore(app);

    // Query the 50 latest stories
    QuerySnapshot latestStories = db.collection("stories")
        .orderBy("timestamp", Direction.DESCENDING)
        .limit(50)
        .get()
        .get();

    // Build the bundle from the query results
    FirestoreBundle bundle = db.bundleBuilder("latest-stores")
        .add("latest-stories-query", latestStories)
        .build();

    // Cache the response for up to 5 minutes
    // see https://firebase.google.com/docs/hosting/manage-cache
    response.appendHeader("Cache-Control", "public, max-age=300, s-maxage=600");

    // Write the bundle to the HTTP response
    BufferedWriter writer = response.getWriter();
    writer.write(new String(bundle.toByteBuffer().array()));
  }
}
      

ถัดไป ให้กำหนดค่าโฮสติ้งของ Firebase เพื่อแสดงและแคช Cloud Function นี้โดย กำลังแก้ไข firebase.json ด้วยการกำหนดค่านี้ CDN โฮสติ้งของ Firebase จะแสดงเนื้อหาแพ็กเกจตามการตั้งค่าแคชที่กำหนดโดย Cloud Function เมื่อแคชหมดอายุ ระบบจะรีเฟรชเนื้อหาโดยทริกเกอร์ อีกครั้ง

firebase.json
{
  "hosting": {
    // ...
    "rewrites": [{
      "source": "/createBundle",
      "function": "createBundle"
    }]
  },
  // ...
}

สุดท้ายในเว็บแอปพลิเคชัน ให้ดึงเนื้อหาที่รวมอยู่ใน CDN แล้วโหลด ลงใน Firestore SDK

// If you are using module bundlers.
import firebase from "firebase/app";
import "firebase/firestore";
import "firebase/firestore/bundle" // This line enables bundle loading as a side effect.

async function fetchFromBundle() {
  // Fetch the bundle from Firebase Hosting, if the CDN cache is hit the 'X-Cache'
  // response header will be set to 'HIT'
  const resp = await fetch('/createBundle');

  // Load the bundle contents into the Firestore SDK
  await db.loadBundle(resp.body);

  // Query the results from the cache
  // Note: omitting "source: cache" will query the Firestore backend.
  
  const query = await db.namedQuery('latest-stories-query');
  const storiesSnap = await query.get({ source: 'cache' });

  // Use the results
  // ...
}

ค่าใช้จ่ายที่ประหยัดได้โดยประมาณ

ลองนึกถึงเว็บไซต์ข่าวที่มีผู้ใช้ 100,000 คนต่อวัน และผู้ใช้แต่ละรายโหลด เรื่องราวยอดนิยม 50 เรื่องแบบเดียวกันในการโหลดครั้งแรก หากไม่มีการแคชใดๆ จะทําให้ 50 x 100,000 = การอ่านเอกสาร 5,000,000 ครั้งต่อวันจาก Cloud Firestore

คราวนี้ให้สมมติว่าเว็บไซต์ใช้เทคนิคข้างต้นและแคชผลลัพธ์ 50 รายการดังกล่าวสำหรับ ไม่เกิน 5 นาที ดังนั้นแทนที่จะต้องโหลดผลการค้นหา สำหรับผู้ใช้ทุกคน จะมีการโหลดผลการค้นหา 12 ครั้งต่อชั่วโมง ไม่ว่ามีผู้ใช้เข้ามามากแค่ไหน ในเว็บไซต์ จำนวนการค้นหาใน Cloud Firestore จะเท่าเดิม แทนที่จะเป็น อ่านเอกสาร 5,000,000 ครั้ง หน้านี้จะใช้เอกสาร 12 x 24 x 50 = 14,400 การอ่านต่อวัน ค่าใช้จ่ายเพิ่มเติมเล็กน้อยสำหรับโฮสติ้งของ Firebase และ Cloud Functions สามารถชดเชยต้นทุนได้อย่างง่ายดายด้วยการประหยัดค่าใช้จ่ายของ Cloud Firestore

แม้ว่านักพัฒนาแอปจะได้ประโยชน์จากการประหยัดต้นทุน แต่ผู้ที่ได้ประโยชน์มากที่สุดก็คือ ผู้ใช้รายนั้น โหลดเอกสาร 50 รายการนี้จาก CDN โฮสติ้งของ Firebase แทนการโหลด จาก Cloud Firestore โดยตรงสามารถลดความเร็วตั้งแต่ 100-200 มิลลิวินาทีขึ้นไป เวลาในการโหลดเนื้อหาของหน้าเว็บ การศึกษาได้แสดงให้เห็นหลายครั้งว่า หน้าเว็บที่โหลดเร็ว ทำให้ผู้ใช้มีความสุขมากขึ้น