Cachea Firestore 🔥 y baja los costes 💸 disparando el rendimiento ✅

Publicado el 23.04.2023 a las 23:47

Cachea Firestore 🔥 y baja los costes 💸 disparando el rendimiento ✅

  1. ¿Qué es Firestore?

  2. ¿Cuánto cuesta Firestore?

  3. EnableIndexedDbPersistence, la función que te encantará 😉

    • ¿Qué es lo que hace enableIndexedDbPersistence?

    • ¿Cómo implemento enableIndexedDbPersistence en una aplicación Angular?

Cachea Firestore 🔥 y baja los costes 💸 disparando el rendimiento ✅

Esta semana, leyendo la documentación de Firestore me encontré con una función que cachea la base de datos de Firestore en el dispositivo del cliente, ahorrando una cantidad enorme de lecturas de documentos de la base de datos 🔥


El descubrimiento me ha parecido increíble, sobre todo por lo bien que funciona introduciendo muy poco código nuevo.

¿Qué es Firestore?

Firestore es un servicio de base de datos NoSQL en la nube desarrollado por Google basado en documentos.


Cuando comienzo una APP comienzo usando Firestore como base de datos porque me da la libertad de ir cambiando la estructura de los datos, no como en las bases de datos SQL que editar tablas cuando ya tienes datos no es tan fácil, es posible pero no fácil.


Es un servicio totalmente administrado, lo que significa que Google se encarga de la infraestructura, la escalabilidad y la seguridad de la base de datos.


Que Google se encargue de la seguridad de la base de datos no significa que tú no tengas que programar unas reglas para asegurarte de quién puede leer o escribir datos en la misma.


Firestore utiliza un modelo de datos de documentos y colecciones, lo que significa que los datos se organizan en colecciones de documentos JSON.

¿Cuánto cuesta Firestore?

Puedes encontrar los precios de Firestore en documentación oficial, no obstante, te lo resumiré a continuación.


Firestore cobra por lectura de documentos, es decir, si tienes una APP de pedidos, cada vez que un usuario consulte un pedido será una lectura, y si guardas cada posición del pedido como una colecciones de documentos dentro del pedido entonces se te contabilizará también cada posición como lectura de un documentos.

También podrías almacenar las posiciones como un array dentro del pedido, evitando así que se te cargue una lectura por cada posición, pero eso dependerá de la estructura de tu APP, puede que no sea recomendable para tu caso particular, habría que analizarlo.


Actualmente, Firestore te permite 50.000 lecturas de documentos diarias gratuitas, y a partir de ahí, para Europa se te facturará 0,06 centavos de dólar por cada 100.000 lecturas de documentos.


Es posible que pienses que ni de lejos llegarás a superar el tier gratuito, pero a nada que no programes bien la gestión de la lectura de documentos o tengas un tráfico superior a 10 usuarios lo superarás con creces pudiéndote encontrar con sorpresas desagradables en algún caso.


Te digo por experiencia, que te pude doler mucho la cabeza como no gestiones bien las lecturas en Firestore.

En una ocasión estuve a punto de tener que pagar una factura enorme de Firestore por los pelos 😱


Conté lo que me ocurrió y como prevenirlo en un artículo que te dejo a continuación:

Imagen del artículo

Controla los gastos de tu proyecto de Firebase 🔥

Evita sorpresas desagradables por abusos en tu proyecto de Firebase 😨

enableIndexedDbPersistence, la función que cachea por tí 😉

Hasta que conocí la función enableIndexedDbPersistence, hacía un cacheo de los documentos leídos manualmente.


Yo, particularmente, programaba un servicio en Angular que se encargaba de suscribirse a una colección de documentos y actualizar mi caché cada vez que había un cambio.

Desde cada componente de mi aplicación consumió los documentos del servicio, no hacía la consulta directamente en cada componente.


Puede parece simple y fácil, pero no era tan fácil de mantener ni funcionaba también como la función enableIndexedDbPersistence que el SDK de Firebase te facilita.

¿Qué es lo que hace enableIndexedDbPersistence?

Como te he dicho, la función enableIndexedDbPersistence cachea la base de datos de Firestore en el dispositivo cliente, de forma que si lees un documento y este no sufre modificaciones, cada vez que lo vuelvas a consultar lo hará desde la caché, evitando así que se te contabilice para la facturación.


Una maravilla 🎁

¿Cómo implemento enableIndexedDbPersistence en una aplicación Angular?

Te dejo aquí el enlace a la documentación oficial.


Para manipular los servicios de Firebase con mis aplicaciones de Angular uso la librería oficial

Para implementar enableIndexedDbPersistence en mis aplicaciones de Angular lo que hice fue:

  1. Crear un servicio en el que inyectaba AngularFirestore
  2. En el constructor del servicio activaba el caché con enableIndexedDbPersistence
  3. Exporta el objeto AngularFirestore con el caché activado
  4. En cada servicio donde antes consumía AngularFirestore lo sustituía por el nuevo objeto con el caché activado
  5. A funcionar 🚀

Tan simple y sencillo.


El código del servicio es:

import { Injectable } from '@angular/core';
import { AngularFirestore } from '@angular/fire/compat/firestore';
import { enableIndexedDbPersistence } from 'firebase/firestore';

@Injectable({
  providedIn: 'root'
})
export class DatabaseService {
    
  constructor(private afs: AngularFirestore) { 
    enableIndexedDbPersistence(this.afs.firestore)
      .then(() => {
        console.log('✅ Persistencia habilitada en el índice de la base de datos');
      })
      .catch((error) => {
        console.error('❌ Error al habilitar la persistencia en el índice de la base de datos: ', error);
      });
  }

  public getDatabase(){
    return this.afs
  }
}
        

En los servicios que antes inyectabas AngularFirestore, inyecta ahora el nuevo servicio, en el ejemplo anterior el servicio se llama DatabaseService

import { Router } from '@angular/router';
import { map } from 'rxjs/operators';
import { Injectable, NgZone } from '@angular/core';
import { Observable, of, Subscription } from 'rxjs';
import { UsuarioInterface, RolesInterface } from 'src/app/models/UsuarioInterface';

👉 import { DatabaseService } from './database.service'; //Nuevo servicio

@Injectable({
  providedIn: 'root',
})
export class AuthService {  
  private userFirebase:firebase.User;

  constructor(    
    👉 private afs: DatabaseService,
    private router: Router, private ngZone: NgZone
  ) {}

  public getUserById(id: string): Observable <UsuarioInterface> {
    return this.afs.getDatabase() 👈
      .doc<IUsuario>('/usuarios/' + id)
      .snapshotChanges()
      .pipe(
        map((action) => {
          if (action.payload.exists == false) {
            return null;
          } else {
            const data = action.payload.data() as UsuarioInterface;
            return data;
          }
        })
      );
  }
}
        

Hasta luego 🖖