Contar documentos en Firestore: La solución obvia no siempre es la mejor

Contar documentos en Cloud Firestore parece sencillo, pero la solución más directa a menudo es una trampa. Descubre por qué un simple conteo puede salirte caro y cómo hacerlo bien.

Cuando estás empezando con Cloud Firestore y la necesidad de saber cuántos documentos tienes en una colección aparece, tu primer impulso, y de hecho la solución más simple que uno suele ver en tutoriales básicos, es hacer un get() de toda la colección y luego revisar la propiedad .size del querySnapshot. Te lo digo al tiro: esa es una receta para el desastre si tu colección crece.

En mi experiencia, esta aproximación es como usar un martillo para clavar un tornillo: funciona a medias para cosas chicas, pero te va a dejar la pura escoba en proyectos más grandes. Y lo peor, te va a costar plata, mucha plata.

El problema de la solución "fácil"

La razón por la que el .size directo es problemático es simple: para obtener el tamaño de la colección de esa forma, Firestore tiene que leer cada documento de la colección. En Firestore, cada lectura de documento tiene un costo asociado. Si tienes 100 documentos, son 100 lecturas. Si tienes 1000, son 1000. Y si llegas a tener 100.000 o un millón, bueno, ahí el costo y el rendimiento se van al carajo.

Además del costo, piensa en el rendimiento. ¿Quieres que tu aplicación frontend descargue cientos de miles de documentos solo para mostrarlos en un contador? No, nadie quiere eso. La interfaz de usuario se va a sentir lenta, la experiencia del usuario se va a ir a pique y el consumo de datos de tu usuario se va a disparar. No es una buena idea.

Hace unos años, en un proyecto para un cliente, me tocó ver un PR que hacía exactamente esto. La colección en ese momento tenía unos 500 documentos, entonces en desarrollo funcionaba impecable. Pero mi alarma se disparó: ¿qué pasa cuando esto tenga 10.000? ¿O 100.000? De hecho, rechacé el PR y explicamos la necesidad de una solución más escalable. Esas son las discusiones de arquitectura que valen la pena.

Las alternativas antes del gran cambio (y por qué aún importan)

Antes de 2023, cuando las cosas no eran tan "directas", la comunidad de Firebase había ideado varias estrategias para manejar esto, dependiendo del tamaño de la colección:

1. Colecciones pequeñas (menos de 100 documentos)

Aquí, la solución "fácil" del querySnapshot.size es tolerable. Ojo que digo tolerable, no óptima. Si sabes con certeza que tu colección nunca va a crecer mucho, o si el conteo es algo que solo se hace una vez y no está en el camino crítico del usuario, puedes usarla. Pero siempre con un ojo puesto en el futuro.


import { collection, getDocs } from "firebase/firestore";

const querySnapshot = await getDocs(collection(db, "cities"));
console.log("Número de ciudades: ", querySnapshot.size);

2. Colecciones medianas a grandes (más de 100, idealmente para cualquier colección que crezca)

Para esto, la solución era y sigue siendo, en muchos casos, un contador distribuido. La idea es simple: mantienes un documento separado (o un campo en un documento existente) que almacena el conteo total de tu colección. Cuando agregas un documento, incrementas el contador. Cuando eliminas uno, lo decrementas. Firestore tiene una función atómica llamada FieldValue.increment() que hace esto de forma segura, evitando condiciones de carrera.

Esto generalmente se implementa con Cloud Functions: cada vez que se crea o elimina un documento en la colección principal, se dispara una función que actualiza el contador. Así, el conteo es una sola lectura de un documento pequeño, no de toda la colección.

Yo he usado esta técnica muchas veces y, de hecho, la sigo implementando en proyectos donde necesito conteos en tiempo real o en escenarios offline, que es una limitación de la nueva función de conteo.

El nuevo "as bajo la manga": Agregaciones con count() (2023)

Desde 2023, Firestore introdujo las consultas de agregación, incluyendo la función count(). Esto es un cambio importante y, en muchos escenarios, la forma preferida de contar documentos. Ahora puedes hacer algo como esto:


import { collection, getCountFromServer } from "firebase/firestore";

const coll = collection(db, "cities");
const snapshot = await getCountFromServer(coll);
console.log('Total de ciudades: ', snapshot.data().count);

Este método es fantástico porque te permite obtener el conteo directamente desde Firestore sin leer todos los documentos. El precio se basa en el número de entradas de índice coincidentes, lo que generalmente es mucho más barato que leer documentos individuales, especialmente en colecciones grandes.

Limitaciones importantes de count()

  • Sin oyentes en tiempo real: No puedes usar count() con listeners en tiempo real. Si necesitas que el contador se actualice instantáneamente a medida que los documentos se agregan o eliminan, aún necesitarás un contador distribuido o alguna otra lógica.
  • Sin consultas offline: Tampoco funciona para consultas offline. Esto significa que si tu aplicación necesita mostrar el conteo cuando el usuario no tiene conexión, count() no te va a servir.

Para mí, estas limitaciones no son menores. Si bien el count() es una herramienta potente y que me alegra que exista, no anula la necesidad de entender y a veces usar los contadores distribuidos.

Entonces, ¿cuándo usar qué?

  • Para un conteo puntual y no crítico donde no necesitas reactividad y la colección es grande: usa getCountFromServer(). Es eficiente en costo y rendimiento.
  • Cuando necesitas un conteo en tiempo real, offline o con baja latencia para mostrar en la UI, especialmente si la colección es dinámica y grande: Implementa un contador distribuido con Cloud Functions y FieldValue.increment(). Esto te dará la flexibilidad que necesitas.
  • Si estás seguro que tu colección es minúscula (menos de 100 documentos) y no va a crecer: el querySnapshot.size puede ser un atajo, pero úsalo con mucho cuidado. En mi caso, yo casi siempre prefiero evitarlo por si acaso.

Lo que aprendí tarde y ojalá hubiera sabido antes

Si hay algo que aprendí un poco tarde en el mundo de los servicios cloud, es que la ignorancia sobre el modelo de precios cuesta caro. Muy caro. Al principio, uno se enfoca solo en que el código funcione, pero no siempre se piensa en el "cómo" o en el "cuánto" cuesta esa operación a escala. Muchas veces, la solución más "fácil" de escribir o la primera que encuentras en un foro, termina siendo la más difícil de mantener o la más costosa a largo plazo. Planificar la escalabilidad desde el principio, incluso en cosas tan aparentemente "básicas" como contar documentos, te ahorra muchos dolores de cabeza, refactors a las 2 AM y billeteras vacías. No hay solución mágica, hay que entender los tradeoffs.

Jorge RequenaDeveloper full-stack · Chile