简介

Cloud FireStore是Firebase供给的一种保管于云端的NoSQL数据库方案。

数据结构为Collection(调集)和Document(文档),文档中存储键值对,整个数据库是由小型文档组成的大型调集。

  • 支撑子调集、复杂分层
  • 支撑单索引、复合索引
  • 支撑复合查询
  • 支撑业务读写
  • 支撑水平扩展、主动扩容

Bundle

Bundle是以查询条件为维度,将一系列查询成果缓存在一起的数据体。比如对“overlay_categories”的全表查询,对“overlay_materials”的分页查询。

优势:

  • 生成查询快照,防止屡次查询Firestore形成的费用耗费。
  • 结合CDN,在网络数据查询时直接回来查询快照,加速响应速度。
  • 能够作为客户端本地的初始化数据,后续使用API查询时会增量更新数据缓存。
  • Bundle在更新缓存时也会告诉addSnapshotListener的监听器,可与线上数据更新逻辑兼并处理。
  • Bundle数据生成的缓存与API更新的缓存数据属于同一种缓存,统一管理。

下风:

  • 数据会以近似JSON的格局明文存储,不行存储敏感信息。
  • Bundle数据包的导入需求经历:数据读取,解析,入库的过程,比较耗时。
  • 性能随调集量级上升而下降,不引荐长期离线使用。
  • 缓存没有索引,缓存的查询不如API查询快。

服务端

SDK生成Bundle数据包

fromgoogle.cloudimportfirestore
fromgoogle.cloud.firestore_bundleimportFirestoreBundle
​
db=firestore.Client()
bundle=FirestoreBundle("latest-stories")
​
doc_snapshot=db.collection("stories").document("news-item").get()
query=db.collection("stories")._query()
​
# Build the bundle
# Note how `query` is named "latest-stories-query"
bundle_buffer:str=bundle.add_document(doc_snapshot).add_named_query(
 "latest-stories-query",query,
).build()
​

Cloud Function生成Bundle数据包

index.js

运行时 : Node.js 12 入口点 : createBundle

需求添加运行时环境变量 GCLOUD_PROJECT = “ProjectID” BUCKET_NAME = “xxxx”

constfunctions=require('firebase-functions');
constadmin=require('firebase-admin');
admin.initializeApp();
​
constbundleNamePrefix=process.env.BUNDLE_NAME_PREFIX
constbucketName=process.env.BUCKET_NAME
​
constcollectionNames=[
"doubleexposure_categories",
"materials"
]
​
exports.createBundle=functions.https.onRequest(async(request,response)=>{
​
// Query the 50 latest stories
// Build the bundle from the query results
conststore=admin.firestore();
constbucket=admin.storage().bucket(`gs://${bucketName}`);
​
for(idxincollectionNames) {
 constname=collectionNames[idx]
 constbundleName=`${bundleNamePrefix}_${name}_${Date.now()}`;
 constbundle=store.bundle(bundleName);
 constqueryData=awaitstore.collection(name).get();
​
 // functions.logger.log("queryData name:", name);
 // functions.logger.log("queryData:", queryData);
 bundle.add(name,queryData)
 constbundleBuffer=bundle.build();
 bucket.file(`test-firestore-bundle/${bundleName}`).save(bundleBuffer);
}
// 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("OK");
});

package.json

{
"name":"createBundle",
"description":"Uppercaser Firebase Functions Quickstart sample for Firestore",
"dependencies": {
 "firebase-admin":"^10.2.0",
 "firebase-functions":"^3.21.0"
},
"engines": {
 "node":"16"
}
}

客户端

离线缓存配置

funcsetupFirestore(){
   letdb=Firestore.firestore()
   letsettings=db.settings
   /**
    对于 Android 和 Apple 平台,离线耐久化默许处于启用状态。如需停用耐久化,请将 PersistenceEnabled 选项设置为 false。
    */
   settings.isPersistenceEnabled=true
   /**
    启用耐久化后,Cloud Firestore 会缓存从后端接纳的每个文档以便离线访问。
    Cloud Firestore 会为缓存大小设置默许阈值。
    超出默许值后,Cloud Firestore 会定时测验清理较旧的未使用文档。您能够配置不同的缓存大小阈值,也能够彻底停用清理功能
    */
   settings.cacheSizeBytes=FirestoreCacheSizeUnlimited
   /**
    假如您在设备离线时获取了某个文档,Cloud Firestore 会从缓存中回来数据。
    查询调集时,假如没有缓存的文档,系统会回来空成果。提取特定文档时,系统会回来过错。
    */
   db.settings=settings
}

加载Bundle数据

// Loads a remote bundle from the provided url.
funcfetchRemoteBundle(forfirestore:Firestore,fromurl:URL,completion:@escaping((Result<LoadBundleTaskProgress,Error>)->Void)){
   guardletinputStream=InputStream(url:url)else{
     leterror=self.buildError("Unable to create stream from the given url: (url)")
     completion(.failure(error))
     return
   }
​
   // The return value of this function is ignored, but can be used for more granular bundle load observation.
   let_=firestore.loadBundle(inputStream){(progress,error)in
     switch(progress,error){
       case(.some(letvalue),.none):
         ifvalue.state==.success{
           completion(.success(value))
         }else{
           letconcreteError=self.buildError("Expected bundle load to be completed, but got (value.state) instead")
           completion(.failure(concreteError))
         }
       case(.none,.some(letconcreteError)):
         completion(.failure(concreteError))
       case(.none,.none):
         letconcreteError=self.buildError("Operation failed, but returned no error.")
         completion(.failure(concreteError))
       case(.some(letvalue),.some(letconcreteError)):
         letconcreteError=self.buildError("Operation returned error (concreteError) with nonnull progress: (value)")
         completion(.failure(concreteError))
     }
   }
}

使用提示

  • Bundle加载一次后就会兼并入Firestore缓存,不需求重复导入。
  • Bundle在创建时,会以Query为维度添加Name标签,能够经过QueryName查询导入的Bundle数据调集。
-(void)getQueryNamed:(NSString*)namecompletion:(void(^)(FIRQuery*_Nullablequery))completion
  • Bundle由异步加载完成,在加载完成之后才干查到数据,能够用监听器来查询。
-(id<FIRListenerRegistration>)addSnapshotListener:(FIRQuerySnapshotBlock)listener
 NS_SWIFT_NAME(addSnapshotListener(_:))

性能测验

小数据量样本

数据大小 文档数量
22 KB 33
次数 iPhone 6s 加载耗时 iPhone XR 加载耗时
第一次 1066 ms 627 ms
第2次 715 ms 417 ms
第三次 663 ms 375 ms
第四次 635 ms 418 ms
第五次 683 ms 578 ms

大数据量样本

数据大小 文档数量
2.7 M 359
次数 iPhone 6s 加载耗时 iPhone XR 加载耗时
第一次 4421 ms 2002 ms
第2次 698 ms 417 ms
第三次 663 ms 375 ms
第四次 635 ms 418 ms
第五次 683 ms 578 ms

参阅链接

Firestore Data Bundles-A new implementation for cached Firestore documents

Load Data Faster and Lower Your Costs with Firestore Data Bundles!

Why is my Cloud Firestore query slow?