skip to Main Content

I am creating a Store Ranking Algorithm, in which I have to fetch products from the user list and compare them with stores. Earlier my solution was taking n+1 query problem. So to optimize that I am trying to download as much data as possible at the mobile app. This is not a good approach but this is what I see as a solution.

I have 100K records in the products collection. 90 Records in store so far (will increase).

The Product has straight forward schema with name, image, measure, category, branch, storeRef etc. fields.

And user_product_list has quantity and product_ref fields.

Here is my full code.

Main evil of the loading time is storeProductsSnapshot. I am just trying to download data of store for offline algorithm.

  Future<void> rankStoresByPriceTotal2({double radius = 6}) async {
    
    final listId = await prefs.then((prefs) => prefs.getUserListId());

    try {
      final List<Stores> nearbyStores = await getNearbyStores(
        radius: radius,
        firestore: fireStore,
        userLocation: GeoPoint(20.68016662, -103.3822084),
      );

      // Creating StoreRef List for better batched read products
      final List<DocumentReference> storeRefs = nearbyStores
          .map((store) =>
          fireStore.collection(StoreConstant.storeCollection).doc(store.id))
          .toList();

      // Fetch all store products that meet the conditions
      // Main Evil who takes too much time.
      final QuerySnapshot storeProductsSnapshot = await fireStore
          .collection('products')
          .where('is_exist', isEqualTo: true)
          .where('storeRef', whereIn: storeRefs)
          .get();

      // Create a list of Product objects
      final storeProducts = storeProductsSnapshot.docs.map((productSnapshot) {
        final ref = productSnapshot.reference;

        return Product(
          productId: productSnapshot['product_id'].toString(),
          productRef: ref.id.toString(),
          isExist: productSnapshot['is_exist'] as bool,
          name: productSnapshot['name'].toString(),
          pImage: productSnapshot['pImage'].toString(),
          storeRef: productSnapshot['storeRef'].path.toString(),
          price: productSnapshot['price'].toString(),
        );
      }).toList();


      // Fetch the user's product list from the user_product_list subcollection
      QuerySnapshot productListSnapshot = await fireStore
          .collection('mylist')
          .doc(listId)
          .collection('user_product_list')
          .get();

      // Build a list of product references to fetch in a batched read
      final List<DocumentReference> productRefs = productListSnapshot.docs
          .map((productSnapshot) =>
      productSnapshot['product_ref'] as DocumentReference)
          .toList();

      // Fetch the products in a batched read
      final List<Product> userProducts = await Future.wait(
        productRefs.map(
              (ref) async {
            final productSnapshot = await ref.get();

            return Product(
              productId: productSnapshot['product_id'].toString(),
              productRef: ref.id.toString(),
              isExist: productSnapshot['is_exist'] as bool,
              name: productSnapshot['name'].toString(),
              pImage: productSnapshot['pImage'].toString(),
              storeRef: productSnapshot['storeRef'].path.toString(),
              price: productSnapshot['price'].toString(),
            );
          },
        ).toList(),
      );

      // Some Complex Algorithm to Find Best Stores using user products.
      final Map<DocumentReference, double> storeTotal = {};

      for (var storeRef in storeRefs) {
        for (var i = 0; i < productListSnapshot.size; i++) {
          final productSnapshot = productListSnapshot.docs[i];
          final product = userProducts[i];
          final price = double.parse(product.minPrice);
          final quantity = productSnapshot['quantity'];
          final productId = product.productId;


          if (product.isExist && product.storeRef == storeRef.path) {
            storeTotal[storeRef] =
                (storeTotal[storeRef] ?? 0) + (quantity * price);

            continue;
          }

          // Some product matching formula to find similar product
          final Product? productInStore = _getSimilarProductFromList(
            storeRef: storeRef.path,
            product: product,
            storeProducts: storeProducts,
          );
          if (productInStore == null) {

            // Some light operations

          } else if (productInStore.productId == productId) {
            /// Matching product price
            final price = double.parse(productInStore.minPrice);

            storeTotal[storeRef] =
                (storeTotal[storeRef] ?? 0) + (quantity * price);

            // Some light operations
            
          } else {
            /// similar product price
            final price = double.parse(productInStore.minPrice);

            storeTotal[storeRef] =
                (storeTotal[storeRef] ?? 0) + (quantity * price);

            // Some light operations
            
          }
        }
      }

      // Sort the found stores with the products.  
      
    } catch (e) {
      print('Error ranking stores: $e');

      
    }
  }

2

Answers


  1. Chosen as BEST ANSWER

    In addition to Alex answer, It is not good practice to download all data in mobile device due because user might have limited mobile data. So it's better to write such heavy functions on cloud. So we can use cloud functions.


  2. If you only need to download the data only once, you’ll lose the real-time future that Firestore provides. Besides that, even if you read the data once, 100k records can be considered an enormous number of documents that can be read in one go. I didn’t try, but I don’t think you could even read all those documents at once, because most likely they will not fit into memory. If you need all those 100k records, then I recommend you load them progressively in smaller chunks. You can load them somehow using the pagination algorithm. Please also note, that it will take some time to download all those documents.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search