【问题标题】:How to reduce read operations on Firebase Cloud Firestore?如何减少 Firebase Cloud Firestore 上的读取操作?
【发布时间】:2019-10-22 22:57:48
【问题描述】:

作为一名充满激情的高中教师,我将 Firebase Cloud Firestore 和 Flutter 用于我的只读应用程序。我的数据是大约 2000 个 image.png 文件,其中包含一些标签作为类别和子类别。类别、子类别和图像之间有一个顺序。我使用 Cloud Firestore 的原因是,我可以随时更改(= 添加、删除、更新)我的类别子类别和图像,并且我的用户将能够使用我的最新内容进行更新。

到目前为止一切正常,我的应用程序在离线时功能齐全,我可以与我的内容同步,但如果设备在线,即使 db 中没有任何变化,每次都已读取 Firestore 中的所有文档。这让我觉得我犯了一个巨大的错误。

这是打开应用程序时发生的代码:

return FutureBuilder<QuerySnapshot>(
      future: _getCategories(),
      builder: (context, catSnapshot) {

            if((!catSnapshot.hasData) && !catSnapshot.hasError) return customProgressIndicator("Categories Synchronizing");

        if(_categories.isNotEmpty) _categories.clear();
        _categories = catSnapshot.data.documents.map<Category>((document) => Category.fromJson(document.data)).toList();

        return FutureBuilder(
          future: _getSubCategories(),
          builder: (context, subSnapshot) {
            if(!subSnapshot.hasData && !subSnapshot.hasError) return customProgressIndicator("Subcategories Synchronizing");

            if(_subcategories.isNotEmpty) _subcategories.clear();
            for(int i=0; i<_categories.length; i++) {
              String catName = _categories[i].name;
              List<Subcategory> subcategoriesI = subSnapshot.data.documents.where((document) => document.data["catName"]==catName).map<Subcategory>((document) => Subcategory.fromJson(document.data)).toList();
              _subcategories[catName] = subcategoriesI;
            }

            return RefreshIndicator(
              onRefresh: _onRefresh,
              child: _categories.length==0 ? whenNoData() : ListView.builder(
                itemBuilder: (context, i) => _createCategoryItem(i),
                itemCount: _categories.length,
                padding: EdgeInsets.all(8.0),
              ),         );       },      );    },   )

这是我的主页:

class _CategoryListPageState  extends State<CategoryListPage> {
  List<Category> _categories = [];
  Map<String, List<Subcategory>> _subcategories = {};

 Future<QuerySnapshot> _getCategories() async{

    var conn=await UserHasConnection();
    print("connection: " + conn.toString());
    Future<QuerySnapshot> snapshots;

      if (widget.isUserPro) {
        snapshots = Firestore.instance.collection("categories").where("isPro", isEqualTo: true).orderBy("showOrder").getDocuments();
        snapshots.timeout(Duration(seconds: globalTimeOut), onTimeout: () {print("Timeout Category");});
        return snapshots;
      }   else {
        snapshots = Firestore.instance.collection("categories").orderBy("showOrder").getDocuments();
        snapshots.timeout(Duration(seconds: globalTimeOut), onTimeout: () {print("Timeout Category");});
        return snapshots;
      }
  }

  _getSubCategories() async{
    Future<QuerySnapshot> snapshots;
      if(widget.isUserPro){
    snapshots=Firestore.instance.collection("subcategories").where("isPro", isEqualTo: true).orderBy("showOrder").getDocuments();
        snapshots.timeout(Duration(seconds: globalTimeOut), onTimeout: () {
          print("Timeout Subcategory");
        });
        return snapshots;
      }  else  {  snapshots=Firestore.instance.collection("subcategories").orderBy("showOrder").getDocuments();
        snapshots.timeout(Duration(seconds: globalTimeOut), onTimeout: () {
          print("Timeout Subcategory");
        });
        return snapshots;
      }
  }

这些是我的 Firestore 文档集合:

categories
  isPro: boolean (there aid free and paid-proUser's)
  name: string, name of the category
  scCount: int, number of subcategories it has
  showOrder: its order among other categories

subcategories
  catName: name of the category it belongs to
  fcCount: number of image.png it has
  imageBytes: subcategories have an image
  isPro: boolean, subcategories can also be free or paid
  name: subcategories have a name
  showOrder: each subcategory has an order among other subcategories in that category
  title: title of the subcat, similar to name

flashcards
  backBytes: back image base64encoded
  backStamp
  catName: category of this image belongs to
  frontBytes: front image base64encoded
  frontStamp
  isPro: cards are also free or paid (for search function below)
  name
  showOrder: order of this card in the subcategory
  subName: name of the subcategory that this card belongs to

*** 我有针对不同设备屏幕尺寸的单独的 flascards、flashcards2x、flashcards3x 集合

还有一个从 flashcardContents 集合 frontContent 属性中完成的搜索功能:

flashcardContents
  frontContent
  globalOrder
  isPro
  name
  showOrder
  updateTime
  updateType: (1-adden, 2-updated, 3-deleted)

最后,我的 FirebaseService 函数:

class FlashcardFirebaseService extends IServiceBase {
  Future<List<Flashcard>> QueryRatedFlashCards(List<Flashcard> _flashcards,
      List<String> flashcardNames, String ratioPostfix, bool isUserPro) async {
    var snapshot;
    flashcardNames.forEach((flashcardName) {
      print("flashcardName: " + flashcardName);

      if (isUserPro) {
        //For pro user
        snapshot = Firestore.instance
            .collection("flashcards${ratioPostfix}")
            .where("name", isEqualTo: flashcardName)
            .snapshots();
      } else {
        snapshot = Firestore.instance
            .collection("flashcards${ratioPostfix}")
            .where("name", isEqualTo: flashcardName)
            .where("isPro", isEqualTo: false)
            .snapshots();
      }

      snapshot.listen((onData) {
        _flashcards.addAll(onData.documents
            .map<Flashcard>((document) => Flashcard.fromJson(document.data))
            .toList());
      });
    });
    return _flashcards;
  }

  static Future<List<Category>> SyncCategories(bool isPro, double lastStamp){
    Firestore.instance
        .collection("categories")
        .getDocuments().then((data){
          return data;
    });
  }

  static Future<List<Category>> SyncSubcategories(bool isPro){
    Firestore.instance
        .collection("subcategories")
        .getDocuments().then((data){
      return data;
    });      }  }


class FlashcardSearchService extends IServiceBase {
  static Future SyncLocalSearchDb() async {
    Query baseQuery;
    AppConfig appConfig;
    appConfig = await AppConfigService.GetAppConfiguration().then((appConfig){
      if (appConfig != null) {
        var lastSyncDate;
        try {
          lastSyncDate = appConfig.fcLastSync;
        } catch (ex) {}

        print("lastSyncDate ---------------------------:"+ (lastSyncDate/1000).toString());

        baseQuery = Firestore.instance
            .collection("flashcardContents")
            .where("updateTime", isGreaterThan: lastSyncDate/1000);
      } else {
        appConfig = null;

        print("appConfig null");

        //Create all card contents
        baseQuery = Firestore.instance.collection("flashcardContents");
        appConfig = new AppConfig();
      }

      baseQuery.getDocuments().then((query) {
        query.documents.forEach((doc) => _syncFlashcardContent(doc));
        if(query.documents.length>0)
          {
            appConfig.fcLastSync = new DateTime.now().millisecondsSinceEpoch.toDouble();
            print("new lastSyncDate ---------------------------:"+ (appConfig.fcLastSync /1000).toString());
            print("SYNC!!! " + appConfig.fcLastSync.toString());
         AppConfigService.UpdateApplicationConfigurationSyncTime(appConfig);

          } else {
              print("CANT SYNC!!! " + appConfig.fcLastSync.toString());
            }
        return appConfig;
      }).timeout(Duration(seconds: globalTimeOut),onTimeout: (){
        print("TimeOutSync");
        return appConfig;
      });
    });

总而言之,我有一个抽认卡 image.png 集合供我的学生用于学习。我的内容每周都会在一定程度上发生变化,我希望我的用户能赶上我。问题是,如果设备在线,我的代码会不断读取所有文档,即使没有更改,即使我在几秒钟后关闭应用程序,我也会打开它。这对我来说是负担不起的。

抱歉,帖子太长了,我希望至少你喜欢代码,任何想法都很有价值和赞赏。

我发现我的应用有这么多读取计数的主要原因是:“如果侦听器断开连接超过 30 分钟(例如,如果用户离线),您将被收取读取费用,就好像您发出了全新的查询。” 所以新的问题是:是否有可能改变这个听众的行为?或者有没有办法绕过它?我觉得我的应用不太适合 Firestore,但到目前为止这似乎是唯一的问题!

总结一下:我需要一个可以工作的侦听器,例如,一天一次,因为我的收藏可能一天更改一次。或者我需要一种方法来绕过它。例如,当我的应用程序离线时,监听器不起作用。在不禁用 Firestore 离线持久性的情况下禁用我的应用程序网络连接的任何选项?谢谢

【问题讨论】:

  • 即使文档没有改变,您如何准确地知道正在阅读的文档?如果没有更改,则应使用本地缓存。
  • 我一直从 firebase 控制台检查,过去 36 小时我打开了我的应用程序一次,然后在主屏幕上关闭它并执行任何操作,现在我有大约 500 次读取,并且我是唯一的用户跨度>

标签: firebase flutter google-cloud-firestore instance snapshot


【解决方案1】:

我们在 Stack Overflow 上确实不可能诊断出您数据库上所有读取的来源,因为我们不了解您的应用程序的总体操作(我们没有您的所有源代码,我们也没有了解用户的行为)。但是,一个非常常见的意外读取来源来自 Firebase 控制台本身。当您使用控制台浏览数据库时,您需要读取数据才能填充控制台。如果您在一个正在积极更改的集合上保持控制台窗口打开,那么在控制台打开时,随着时间的推移,这将进一步累积读取。

【讨论】:

  • 谢谢,我不知道打开控制台可能会导致一些读取操作,我会记住这一点。但是,我仍然相信代码中会有一些不正确的东西。无论如何,感激不尽。
  • 再次阅读您的回答后,我意识到我的收藏从未改变。所以我认为我的代码有问题,因为现在我有大约 100 个文档和我的读取计数 858,没有任何改变,只是打开我的应用程序一次,没有写入,没有删除,没有下载,但是 858 读取,我不能了解
  • 嘿,Doug,我想我得换个问题了:“如果监听器断开连接超过 30 分钟(例如,如果用户下线),您将被收取阅读费用,就像你发出了一个全新的查询。”这是我的应用程序读取次数增加的主要原因,我的数据库不是每 30 分钟更改一次,而是一天更改一次,所以我需要一个侦听器每天只工作一次。有可能吗?
  • 如果您有新问题,可以单独发布吗?当问题随时间变化时,Stack Overflow 无法正常工作。
猜你喜欢
  • 2021-05-22
  • 2021-06-08
  • 2020-06-07
  • 2021-04-04
  • 2021-11-23
  • 2020-03-31
  • 2019-12-29
  • 2022-01-10
  • 1970-01-01
相关资源
最近更新 更多