简介
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?