Contadores sin puntos calientes

25 minIntermedio

Cuando un contador recibe muchas escrituras, repartirlo en varios documentos y su índice de fechas en cubos evita que toda la carga caiga en un rango.

TeselaDB guarda los documentos y las entradas de índice ordenados por su clave y los reparte en rangos. Un contador que todas las peticiones incrementan es un solo documento, en un solo rango y con un solo líder. Un índice sobre una fecha de creación manda todas sus entradas nuevas al final de su intervalo.

Lo que vas a construir

Un contador de visitas repartido en ocho documentos, que se lee con una agregación, y un índice sobre la fecha de los eventos con sus entradas repartidas en cubos hash.

Requisitos

  • Docker y mongosh, para arrancar TeselaDB en tu máquina y mirar los datos.

Prepara el entorno

Shell
docker run -d --name teseladb -p 27017:27017 viuzi/teseladb:latest \
  start --dbpath /data --insecure --insecure-allow-remote --bind_ip 0.0.0.0
export TESELADB_URI="mongodb://127.0.0.1:27017/shop"
mongosh "$TESELADB_URI" --quiet --eval 'db.runCommand({ ping: 1 }).ok'
mongosh "$TESELADB_URI"

El contenedor arranca un nodo de TeselaDB sin TLS ni usuarios, que basta para seguir la guía en tu máquina. Cuando mongosh responde 1, el nodo ya acepta conexiones. TESELADB_URI apunta a ese nodo y a la base shop; en un clúster de producción la cadena lleva TLS y usuario, como cuenta Conectar tu aplicación. Con un solo nodo los rangos se dividen igual que en un clúster. Con varios, además, se reparten. La última línea abre mongosh sobre esa base, y en esa misma consola escribes todos los pasos de la guía, desde el contador repartido hasta la agregación que suma sus ocho documentos.

1. Reparte el contador

Cada incremento elige al azar uno de los ocho documentos de la página. Así las escrituras van a claves distintas y no chocan en una sola:

JavaScript
const SHARDS = 8;
for (let i = 0; i < 100; i++) {
  const shard = Math.floor(Math.random() * SHARDS);
  db.pageviews.updateOne({ _id: { page: "home", shard } }, { $inc: { count: 1 } }, { upsert: true });
}

2. Reparte el índice sobre la fecha

Con teselaBuckets, las entradas del índice se reparten en 16 cubos hash. El planificador junta los cubos al leer, así que las consultas por fecha no cambian:

JavaScript
db.events.createIndex({ createdAt: 1 }, { teselaBuckets: 16 });

Probarlo

El total de la página es la suma de sus ocho documentos. Una agregación que filtra la página y suma el campo count de cada uno de ellos devuelve los 100 incrementos que escribió el bucle del paso 1:

JavaScript
db.pageviews.aggregate([
  { $match: { "_id.page": "home" } },
  { $group: { _id: "$_id.page", views: { $sum: "$count" } } },
]);
[ { _id: 'home', views: 100 } ]

Con carga real, teseladb admin ranges --collection shop.pageviews muestra los documentos del contador en varios rangos cuando la colección crece y se divide.

Limpiar

JavaScript
db.pageviews.drop();
db.events.drop();

Al final borra también el contenedor con la última orden: el nodo guarda los datos dentro, sin volumen, así que con él desaparece todo lo que escribiste durante la guía y el puerto 27017 queda libre:

Shell
docker rm -f teseladb

Siguientes pasos