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

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

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

โซลูชัน: แพ็กเกจ

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

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

ก่อนอื่นให้สร้างฟังก์ชัน 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 Functions นี้โดยการแก้ไข firebase.json เมื่อใช้การกำหนดค่านี้ Firebase Hosting CDN จะแสดงเนื้อหาของแพ็กเกจตามการตั้งค่าแคชที่กำหนดโดย 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 Hosting และ Cloud Functions จะชดเชยด้วยการประหยัดค่าใช้จ่ายของ Cloud Firestore ได้อย่างง่ายดาย

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