Tema representativo de entrevista

Entrevista de diseño de sistemas: ¿Cómo diseñarías una tabla de clasificación de juegos en tiempo real?

Diseño de sistemasDifícil
Equipo editorial de Offer.ccPublicado Actualizado

Pregunta

Diseña una tabla de clasificación de juegos en tiempo real. Cada temporada cuenta con 50 millones de jugadores activos, con picos de 200,000 actualizaciones de puntaje y 1 millón de lecturas por segundo. El sistema debe mostrar el top 100 global, el rango exacto de un jugador y los jugadores cercanos. Las actualizaciones deben ser visibles en un plazo de 5 segundos y el p99 de lectura debe mantenerse por debajo de los 100 milisegundos. Los puntajes son enteros de 0 a 1,000,000, solo se conserva el mejor puntaje de la temporada de cada jugador y los jugadores empatados comparten el mismo rango. Explica las API, el modelo de datos, la validación de puntajes, el ordenamiento y particionamiento (sharding), la consistencia, el cierre de temporada, la recuperación, la capacidad y la verificación.

Enunciado y alcance

Diseña una tabla de clasificación estacional para un juego competitivo. Una temporada tiene 50 millones de jugadores activos, recibe hasta 200,000 actualizaciones de puntaje por segundo y atiende hasta 1 millón de lecturas por segundo. Los jugadores necesitan el top 100 global, su rango exacto y 10 jugadores a cada lado. Un resultado enviado debe aparecer en un lapso de 5 segundos y el p99 de lectura debe permanecer por debajo de los 100 milisegundos. Los puntajes son enteros en 0..1,000,000, y cada jugador conserva únicamente su mejor puntaje de la temporada. Los empates comparten rango, de modo que a los rangos 1, 2, 2 les sigue el 4. Los jugadores empatados tienen un orden de visualización estable por player_id, pero ese orden no altera su rango.

La escala, la latencia y el rango de puntajes son supuestos de entrevista. Un cliente no puede declarar un puntaje confiable. El servicio de partidas completa la validación de resultados y las comprobaciones antitrampas antes de producir un evento que la tabla de clasificación pueda consumir. El problema evalúa la indexación ordenada, la separación de lecturas y escrituras, el particionamiento de claves calientes (hot-key sharding), la consistencia, la capacidad de reconstrucción y el ciclo de vida de la temporada. No solicita un modelo antitrampas.

Una guía pública de entrevistas técnicas incluye "diseñar una tabla de clasificación para un juego" como un ejemplo de diseño de sistemas y señala que evalúa la lógica de clasificación, el rendimiento de lectura/escritura y las actualizaciones en tiempo real. Un tutorial de tablas de clasificación en Redis publicado en 2026 también demuestra la ruta básica de Sorted Set para actualizar puntajes, recuperar el Top-N y consultar el rango. La evidencia pública no establece una atribución confiable a una empresa, por lo que companyName permanece nulo.

Qué evalúa el entrevistador

La primera señal es si el candidato define qué es el "rango". Ordenar por mayor puntaje primero es sencillo; decidir si los empates comparten rango, favorecen a quien lo logró primero o desempatan por ID de jugador cambia directamente el modelo de datos. Sin este contrato, dos servicios pueden producir rangos diferentes para los mismos jugadores.

La segunda señal es reconocer que un Sorted Set resuelve una única colección ordenada. Redis documenta las actualizaciones con ZADD y las consultas de rango con ZREVRANK como O(log N), lo cual es útil para una tabla de clasificación moderada. Redis Cluster, sin embargo, asigna ranuras de hash (hash slots) por clave. Mantener la tabla global en una sola clave no divide sus 50 millones de miembros simplemente porque se agregaron más nodos al clúster. Una respuesta sólida mantiene la ruta simple al principio y solo agrega particionamiento a nivel de aplicación cuando una clave excede los presupuestos de memoria, escritura o recuperación.

La tercera señal es convertir "exacto dentro de 5 segundos" en una versión de lectura consistente. Mover a un jugador de un contenedor (bucket) de puntaje a otro crea una desaparición intermedia con eliminar-luego-agregar, o un duplicado con agregar-luego-eliminar. Leer los conteos de buckets de un instante y el orden de los buckets de otro también produce un rango erróneo. Un diseño verificable hace que una solicitud lea una única versión completamente publicada.

Por último, el diseño debe ser operable. Los eventos de puntaje necesitan idempotencia, las correcciones deben poder reducir un puntaje, el cierre de temporada debe congelar una instantánea consistente y una caché perdida debe ser reconstruible a partir de un libro mayor (ledger) confiable. Un diagrama que solo contenga "servicio de juegos → Redis" no aborda duplicados, puntos calientes, pérdida de datos, revocación de puntajes fraudulentos ni disputas de liquidación.

Preguntas para aclarar antes de responder

  • ¿El puntaje es acumulativo, el mejor o el más reciente? Este enunciado conserva el mejor puntaje estacional. Un puntaje acumulativo hace que la identidad del evento y los incrementos atómicos sean críticos, ya que de lo contrario los eventos duplicados se sumarían dos veces. Si los administradores pueden corregir puntajes, la API necesita valores absolutos versionados en lugar de únicamente ZINCRBY.
  • ¿Cómo se clasifican los empates? Este enunciado utiliza la clasificación de competición: solo cuentan los puntajes estrictamente mayores y los jugadores empatados comparten rango. La regla "el primero en lograrlo gana" requiere una marca de tiempo confiable en la clave de ordenamiento; el orden de empate lexicográfico predeterminado de un ZSET no proporciona esa regla automáticamente.
  • ¿Cuáles son los requisitos de exactitud y frescura? Las versiones publicadas deben ser exactas, con un retraso (staleness) de hasta 5 segundos. Una visibilidad inmediata linearizable pondría la coordinación entre particiones (shards) en la ruta de escritura. Un rango aproximado permitiría muestras o bocetos de cuantiles (quantile sketches) y un diseño mucho más simple.
  • ¿Qué tablas son requeridas? La ruta principal es la tabla global de la temporada. Un conjunto fijo y pequeño de regiones puede tener vistas materializadas separadas. Una tabla de amigos generalmente obtiene los puntajes de los amigos en lote y los ordena por solicitud en lugar de mantener una tabla por jugador.
  • ¿Qué lecturas se admiten? Top 100, rango personal y un vecindario de 20 jugadores. La paginación profunda no acotada crea escaneos costosos, por lo que las ventanas deben ser limitadas y usar un cursor versionado. Una versión expirada requiere reubicación.
  • ¿Quién confirma un puntaje? Solo escribe un servicio confiable de resultados de partidas. El envío de un cliente es una entrada de juego, no un puntaje que pueda insertarse directamente en el índice ordenado.
  • ¿Cuándo cierra la temporada? Usa la hora del evento en el servidor y un corte explícito. La política de producto debe definir si los resultados tardíos se rechazan, se revisan o se asignan a otro lugar; un trabajo en segundo plano no puede mutar silenciosamente una instantánea premiada.
  • ¿Qué es durable? Una vista de rango puede estar desactualizada brevemente o reconstruirse. El registro de puntajes confiable y la instantánea final de la temporada no pueden desaparecer ante una falla de caché.

Estructura de respuesta en 30 segundos

"Definiría el rango como 1 + número de jugadores con una puntuación estrictamente mayor, de modo que los jugadores empatados compartan rango. Un servicio de partidas confiable escribe un mejor puntaje absoluto con un event_id y una versión de jugador. La tabla de puntajes durable es la fuente de la verdad, y su flujo de cambios materializa la tabla de clasificación. A menor escala, un Redis Sorted Set soporta ZADD, Top-N y rango inverso. Una vez que la clave caliente global para 50 millones de jugadores exceda los presupuestos de un solo shard, la particionaría por rangos de puntaje fijos. El rango personal es el conteo en todos los buckets superiores, más el conteo de puntajes superiores en el bucket del jugador, más uno.

"Un movimiento entre buckets no puede exponerse a medio camino. Por lo tanto, los materializadores confirman una versión cada pocos segundos. Después de que todos los buckets, prefijos de conteo y el Top 100 estén listos, un coordinador intercambia atómicamente el manifiesto actual. Las respuestas incluyen leaderboard_version y as_of. Las escrituras son idempotentes por evento y versión, la caché es reconstruible desde el registro de puntajes y el cierre de temporada congela y reconcilia una versión completa antes de que se emitan las recompensas".

Análisis detallado paso a paso

Paso 1: Fijar las API y el contrato de ordenamiento antes de elegir el almacenamiento.

Separa la ruta de escritura interna de las lecturas públicas:

text
POST /internal/v1/seasons/{season_id}/scores:apply
GET  /v1/seasons/{season_id}/leaderboard/top?limit=100
GET  /v1/seasons/{season_id}/players/{player_id}/rank
GET  /v1/seasons/{season_id}/players/{player_id}/neighbors?radius=10&version=...

La escritura contiene event_id, match_id, player_id, best_score absoluto, score_version y una hora de finalización confiable. Solo la identidad de resultados de partidas está autorizada. Una lectura devuelve leaderboard_version, as_of, puntaje, rango y miembros. Una solicitud de vecindario debe retener la versión de la primera respuesta; de lo contrario, los cambios de rango al paginar pueden duplicar u omitir jugadores.

Cuatro invariantes rigen el diseño: cada (season_id, player_id) aparece una vez en una versión; el puntaje publicado del jugador coincide con el índice ordenado; el rango siempre es igual a 1 + count(score > my_score); y un manifiesto referencia únicamente versiones de shard que hayan finalizado en su totalidad.

Paso 2: Hacer de la tabla de puntajes confiable la fuente de la verdad.

El servicio de liquidación de partidas valida la autorización, el estado de la partida y los resultados antitrampas antes de escribir en un registro de resultados inmutable. En una única transacción de base de datos, el escritor de la tabla de clasificación deduplica event_id y actualiza condicionalmente la fila del jugador. Rechaza una versión anterior, devuelve el resultado previo ante un reintento idéntico y cambia el mejor puntaje normal solo cuando new_score > best_score. La revocación de un puntaje fraudulento escribe un score_version mayor y un valor absoluto corregido, por lo que una reducción también converge.

Tras el commit, un outbox o un flujo de cambios durable equivalente emite:

text
ScoreChanged {
  season_id, player_id, old_score, new_score,
  score_version, event_id, committed_at
}

Los eventos se particionan por jugador y un materializador aplica únicamente un cambio más reciente que la versión actual de ese jugador. La entrega duplicada no puede sumar puntos dos veces y un evento antiguo retrasado no puede sobrescribir un puntaje nuevo. La tabla de clasificación es una vista materializada desechable. El registro y la tabla de puntajes actuales son las fuentes de reconstrucción.

Paso 3: Presentar primero el diseño de un solo Sorted Set.

Cuando la membresía, los picos de escritura, la memoria y el tiempo de recuperación caben en un shard, un ZSET por temporada es el punto de partida adecuado:

text
key    = leaderboard:{season_id}
member = player_id
score  = best_score

ZADD establece un nuevo puntaje para un miembro existente y lo reposiciona. Un rango descendente devuelve el Top-N y ZREVRANK devuelve una posición. Redis documenta la actualización como O(log N), un rango de tamaño fijo como O(log N + M) y la búsqueda de rango como O(log N). El rango de enteros del enunciado 0..1,000,000 está muy por debajo del límite de 2^53 hasta el cual un double representa enteros con exactitud.

Los miembros con el mismo puntaje se ordenan por el orden lexicográfico binario de los valores de los miembros. Eso solo se ajusta a un contrato donde los empates comparten rango y el ID controla el orden de visualización. No empaquetes un puntaje arbitrario y una marca de tiempo en milisegundos en un solo valor de punto flotante asumiendo que el ordenamiento compuesto se mantendrá correcto. ZREVRANK + 1 tampoco es el rango comercial compartido porque los miembros empatados reciben posiciones diferentes. La fórmula comercial es 1 + count(score > my_score). Dentro de un ZSET, ZCOUNT key (my_score +inf calcula ese conteo; ( hace que el límite inferior sea exclusivo. Mantén la posición de visualización separada del rango comercial.

Paso 4: Demostrar por qué una escala mayor requiere particionamiento a nivel de aplicación.

Redis Cluster asigna claves a ranuras de hash y una ranura estable es atendida por un único primario. La clave leaderboard:{season} sigue siendo una sola clave, por lo que sus miembros, escrituras y carga de recuperación permanecen concentrados en el primario de una sola ranura. Particionar por hash(player_id) equilibra las escrituras, pero encarece el rango global porque cada solicitud debe combinar o contar a través de todos los shards de jugadores.

Este enunciado tiene un rango de puntaje finito, por lo que los buckets de rangos de puntaje fijos son útiles. Con un rango de 10,000 puntos por bucket, hay 101 buckets:

text
bucket_id = floor(score / 10,000)
rank(player) = 1
             + count(all buckets with a higher bucket_id)
             + count(score > player_score inside the player's bucket)

Cada bucket permanece ordenado y las claves de los buckets pueden ocupar diferentes ranuras. Para cada versión, almacena los conteos de los buckets y una suma de prefijos de mayor a menor. El rango personal necesita entonces una búsqueda de jugador, un valor de prefijo y un conteo estrictamente mayor dentro del bucket. El Top 100 escanea solo los buckets no vacíos más altos y se materializa por separado. Los rangos fijos pueden crear un bucket de puntaje alto caliente. Divide ese rango aún más cuando se mida en producción, pero coloca los límites en el manifiesto versionado para que lectores y escritores usen la misma estructura.

Paso 5: Usar la publicación versionada para resolver la atomicidad entre buckets.

Cuando un jugador pasa de 39,000 a 51,000, el sistema lo elimina del bucket 3, lo agrega al bucket 5 y cambia dos conteos. Eliminar, agregar y cambiar contadores a través de ranuras de Redis no constituyen una operación atómica ordinaria única. Exponer el estado intermedio produce un duplicado, una desaparición o un rango global desfasado por uno.

Por lo tanto, los materializadores confirman instantáneas lógicas en épocas cortas. Cada shard de rango comienza desde la versión publicada actual, aplica un lote idempotente y prepara el siguiente índice ordenado y conteo. Una vez que todos los shards informan que han terminado, el coordinador verifica la membresía total, los conteos de cambios y las sumas de verificación (checksums) de los shards, y luego mueve atómicamente current_manifest de v a v+1. Una lectura obtiene primero el manifiesto y transporta esa versión a lo largo de todas las subconsultas. Una versión incompleta es invisible, los shards con fallas pueden reintentar y la versión anterior se mantiene hasta que finalicen las lecturas en curso.

Las instantáneas lógicas pueden reutilizar datos antiguos mediante MVCC, páginas de copia en escritura (copy-on-write) o una base más deltas, evitando una copia completa de 50 millones de miembros cada pocos segundos y preservando al mismo tiempo una versión externa inmutable. La duración de la época, el retraso de aplicación y el retraso de publicación deben mantenerse juntos por debajo de los 5 segundos. Si no lo hacen, informa la falta de frescura y deja de afirmar que el servicio es en tiempo real en lugar de publicar una versión a medio completar.

Paso 6: Separar tablas globales, regionales y de amigos.

La tabla global utiliza el índice por buckets. Un conjunto fijo y pequeño de regiones puede mantener vistas independientes de (season, region) a partir del mismo evento de puntaje. La región proviene de una versión del perfil del jugador mantenida en el servidor, de modo que un cliente no pueda cambiar de región durante una solicitud. Filtrar solo el Top-N global omitiría a un jugador regional fuerte que esté fuera de la página global; utiliza un índice regional o etiqueta el resultado como aproximado.

Una tabla de amigos suele ser pequeña. Obtén los ID de amigos versionados del grafo social, lee por lotes sus puntajes en la misma versión de la tabla de clasificación, luego ordena por score DESC, player_id ASC y calcula los empates en el servicio de aplicación. Mantener un ZSET de amigos por jugador causa una amplificación extrema de escritura: un solo cambio de puntaje se propaga a la tabla de cada amigo, y los cambios en las relaciones requieren recargas masivas.

Paso 7: Estimar el tráfico y luego dimensionar los shards a partir de mediciones.

Si un evento de puntaje durable incluyendo su envoltorio pesa 128 bytes, el límite inferior de escritura lógica pico es:

text
200,000 events/s × 128 bytes = 25.6 MB/s
25.6 MB/s × 86,400 s = 2.21184 TB/day
three-replica log lower bound = 6.63552 TB/day

Este límite inferior no comprimido excluye índices, sobrecarga de protocolo, lotes, reintentos y recuperación de réplicas. Solo un evento que mejore el mejor puntaje o lo corrija altera la clasificación, pero cada evento confiable aún requiere deduplicación y auditoría del lado del ledger. Un millón de lecturas por segundo no pueden llegar todas a los shards de rango. Almacena en caché el Top 100 por versión. Un resultado personal puede usar un TTL corto, pero la clave incluye al jugador y la versión; el shard luego lee por lotes las ventanas de vecindario.

No copies una cantidad fija de shards de un artículo. Realiza pruebas comparativas de memoria por miembro, ZADD, conteos estrictamente mayores, lecturas de rango y construcción de instantáneas con 50 millones de filas con forma de producción. Registra p50/p95/p99, CPU, memoria, retraso de replicación y tiempo de recuperación, y luego deriva las divisiones de buckets y la cantidad de nodos a partir de las escrituras pico y el margen de tolerancia a fallos. Si un ZSET se mantiene dentro de todos los presupuestos, es más confiable que un coordinador de buckets personalizado.

Paso 8: Cerrar temporadas y mantener la vista reconstruible.

Después de que una temporada entra en CLOSING, los resultados previos al corte ya aceptados continúan a través del flujo, mientras que los nuevos resultados no elegibles se rechazan. Registra una marca de agua alta (high watermark) de entrada. Una vez que cada materializador la alcanza, crea una versión final candidata. La liquidación compara el total de jugadores, la suma de los conteos de buckets, el Top 100, rangos muestreados aleatoriamente, el conteo de jugadores duplicados y la suma de verificación de cada shard. Si tiene éxito, marca el manifiesto como FINAL; el servicio de recompensas solo lee esa versión inmutable. Las disputas posteriores ingresan a un flujo de corrección auditado en lugar de reescribir silenciosamente la tabla premiada.

Si se pierde una caché de clasificación o todo el clúster, reproduce la tabla de puntajes actuales o el registro inmutable mediante score_version en un nuevo espacio de nombres. Construye y reconcilia un candidato completo, y luego intercambia el manifiesto atómicamente. La versión antigua permanece en solo lectura durante la reconstrucción. Si no existe ninguna, devuelve un estado explícito de temporalmente no disponible o desactualizado en lugar de presentar una tabla parcial como completa.

La matriz de fallos incluye eventos duplicados y fuera de orden; incrementos entre buckets y correcciones hacia abajo; caída de un shard a mitad de época; caída del coordinador antes y después del intercambio de manifiesto; pérdida de un nodo de caché; empates masivos en el límite del Top-100; 1 millón de QPS de lectura; resultados tardíos alrededor del corte; y reconstrucción completa con comparación en paralelo (shadow). Comprueba continuamente las cuatro invariantes y monitorea el p99 de evento a publicación, la antigüedad de la versión, el sesgo de buckets, los eventos rechazados, el progreso de reconstrucción y los fallos de suma de verificación.

Respuesta de ejemplo sólida

"Definiría primero el rango. Los jugadores empatados comparten rango aquí, por lo que el rango de un jugador es 1 + número de jugadores con una puntuación estrictamente mayor; la posición de visualización del Top-100 y el rango comercial son independientes. Los clientes no pueden escribir un puntaje confiable. Tras la validación de la partida, el escritor de la tabla de clasificación deduplica el ID del evento y establece condicionalmente un mejor puntaje absoluto por versión de puntaje del jugador. La tabla de puntajes actuales es la fuente de la verdad y su flujo de cambios confirmado impulsa la vista de rango.

"Si la tabla cabe en un solo shard de Redis, comenzaría con un Sorted Set por temporada. Establecer puntajes, leer el Top-N y contar por puntaje son operaciones directas. Pero la tabla global para 50 millones de jugadores es una clave caliente única, y Redis Cluster particiona por clave en lugar de por miembros dentro de una clave. Dado que el rango de puntaje está acotado en 0..1,000,000, escalaría a 101 buckets de rangos de puntaje fijos. El rango personal es el prefijo de conteo de los buckets superiores más los jugadores estrictamente superiores en el bucket local más uno. El Top 100 proviene de los buckets no vacíos más altos.

"Un movimiento entre buckets altera dos claves y conteos. Actualizar in situ expondría un estado intermedio, por lo que construiría el siguiente índice en épocas de como máximo unos pocos segundos. Solo después de que cada shard aplique cambios idempotentes y se supere la reconciliación de membresía/checksums, el coordinador intercambia atómicamente el manifiesto actual. Cada respuesta incluye versión y fecha/hora (as-of), y las lecturas de vecindario retienen esa versión. Las versiones publicadas son exactas a costa de un retraso de hasta 5 segundos.

"A 128 bytes por evento, las escrituras pico representan 25.6 MB/s de tráfico lógico, aproximadamente 2.21 TB por día, y el límite inferior de un registro con tres réplicas es de unos 6.64 TB. Los nodos de índice y los conteos de buckets se derivan de pruebas de rendimiento con datos representativos de producción. Almacena en caché el Top 100 por versión. Construye tablas de amigos obteniendo en lote los puntajes de los amigos y ordenándolos localmente, evitando la amplificación de escrituras en cada cambio de puntaje.

"Al cierre de la temporada, registraría una marca de agua alta de entrada, esperaría a que todos los shards se pongan al día y congelaría una versión final candidata. Reconciliaría membresía, sumas de buckets, Top 100, rangos muestreados y sumas de verificación de shards antes de que el servicio de recompensas lea un manifiesto FINAL. Una caché perdida se reconstruye a partir de la tabla de puntajes confiable o del ledger. Las pruebas de fallos cubren eventos duplicados/fuera de orden, correcciones entre buckets, caídas de shards y coordinadores, empates masivos, condiciones de carrera en el corte y reconstrucción completa".

Errores comunes

  • Aceptar un puntaje directamente del cliente → el almacenamiento de ordenamiento no puede determinar si es válido → consumir únicamente resultados auditados confirmados por un servicio de partidas confiable.
  • Usar ZREVRANK + 1 para rangos empatados → un ZSET asigna a puntajes iguales posiciones lexicográficas distintas, violando el contrato de rango compartido → calcular 1 + count(score > my_score).
  • Empaquetar despreocupadamente una marca de tiempo en un puntaje de punto flotante → los doubles tienen un límite de precisión y una codificación compuesta puede invertir la prioridad de la clave → definir primero los empates y luego usar una codificación entera probada o un índice de orden compuesto.
  • Asumir que más nodos de Redis Cluster dividen una clave de tabla global → Cluster asigna la ranura de hash de una clave, y la clave individual permanece en el primario de una sola ranura → medir el límite de la clave única y luego particionar según una dimensión comercial combinable.
  • Particionar jugadores por hash y dispersar (fan out) cada consulta de rango → el costo de la consulta crece con la cantidad de shards y explota a 1 millón de QPS de lectura → usar conteos sobre el rango de puntaje finito o devolver un rango aproximado cuando esté permitido.
  • Eliminar y luego agregar, o agregar y luego eliminar, entre buckets → los lectores ven una desaparición, un duplicado o un conteo erróneo → construir una versión completa y publicarla atómicamente a través de un manifiesto.
  • Usar ZINCRBY para cada resultado → los eventos duplicados se suman dos veces y la revocación de un puntaje fraudulento no puede reducirse de forma segura → establecer de manera idempotente un puntaje absoluto por evento y versión de jugador.
  • Emitir recompensas desde la caché en vivo al llegar al corte → los eventos aceptados antes del corte pueden seguir en tránsito y la caché no es la verdad durable → registrar una marca de agua alta, ponerse al día, reconciliar y congelar una versión FINAL.
  • Validar únicamente que el Top 100 se vea correcto → un error de conteo de bucket o un jugador duplicado pueden desplazar todos los rangos de la cola larga → verificar la conservación de la membresía, la unicidad, las fórmulas de rangos muestreados, las sumas de verificación de shards y una reconstrucción completa.

Preguntas de seguimiento y respuestas

Pregunta de seguimiento 1: ¿Podemos mantener la instantánea de cinco segundos si un jugador debe leer una escritura de inmediato?

Separa la experiencia de "lectura de tus propias escrituras" (read-your-writes) del escritor del rango publicado globalmente. La respuesta de escritura puede devolver el nuevo puntaje confirmado y la versión pendiente. Una actualización inmediata puede indicar que el puntaje está confirmado y el rango global se está actualizando, o mostrar el nuevo puntaje personal mientras el rango global aún hace referencia al último manifiesto completo. Si el producto requiere el nuevo puntaje y el rango global exacto en la misma respuesta sincrónica, la actualización debe ingresar a una ruta de ordenamiento coordinada globalmente. La latencia de escritura y el acoplamiento de fallos aumentan, por lo que debe reevaluarse el objetivo de rendimiento original.

Pregunta de seguimiento 2: ¿Qué sucede si se acumulan 10 millones de jugadores en el bucket de puntaje más alto?

La distribución de buckets es metadato versionado. Primero demuestra el punto caliente mediante QPS de escritura, membresía, CPU y p99; luego divide ese rango de puntajes en sub-buckets más estrechos. Dado que el rango compartido depende de puntajes estrictamente mayores, un puntaje exacto no se puede particionar arbitrariamente por jugador y sumar rangos locales; su conteo total debe permanecer agregado. Construye la nueva distribución en paralelo, compara la membresía y los rangos muestreados en la misma marca de agua alta de entrada, y luego publica un manifiesto que haga referencia a la nueva distribución.

Pregunta de seguimiento 3: ¿Qué cambia si el primer jugador en alcanzar un puntaje empatado es el ganador?

El rango deja de ser un simple conteo basado solo en el puntaje. La clave de ordenamiento se convierte en (score DESC, achieved_at ASC, player_id ASC), con achieved_at proveniente de la liquidación confiable. Un ZSET normal desempata únicamente por el orden lexicográfico del miembro. Un puntaje entero de ancho fijo y una codificación de miembro invertida pueden funcionar si se demuestra su precisión y orden, pero es frágil. A mayor escala, preferiría shards ordenados que soporten claves compuestas y estadísticas de orden, con pruebas para el mismo milisegundo, reintentos y tiempos de logro corregidos.

Pregunta de seguimiento 4: ¿Qué ocurre si el sistema antitrampas revoca al campeón después de haberse emitido las recompensas?

El sistema técnico no decide si se recuperan las recompensas. Acepta una corrección negativa con un score_version más alto, conserva el evento original, la evidencia, el aprobador y la hora, y produce un nuevo manifiesto corregido sin reescribir la instantánea FINAL original. El servicio de recompensas aplica la política operativa para congelar, recuperar o promover recompensas y asocia su acción con una versión de la tabla de clasificación. Esto preserva la trazabilidad tanto de la premiación original como de la corrección posterior.

Pregunta de seguimiento 5: ¿Cómo demuestras que una reconstrucción del ledger coincide con la tabla de clasificación en línea?

Reproduce las versiones de los eventos hasta la misma marca de agua alta de entrada en un espacio de nombres aislado. Compara el conteo de jugadores, los conteos por bucket de puntaje, las sumas y hashes de puntajes, el Top 100, múltiples jugadores muestreados estratificadamente usando 1 + count(higher score) y sumas de verificación deterministas para cada shard. Ejecuta en paralelo (shadow) ambas rutas de lectura y contabiliza cualquier diferencia de puntaje, rango o ventana de vecinos. Intercambia el manifiesto únicamente después de que se superen todos los umbrales. Que un trabajo de reconstrucción finalice con éxito no es evidencia de que su resultado sea correcto.

Fuentes públicas

Preguntas relacionadas

Herramienta de entrevista relacionada

Usa Resolver para una respuesta de diseño de sistemas

Aclara primero los requisitos y luego avanza a través de la escala, la arquitectura, la elección de componentes y las compensaciones (trade-offs).

Ver la herramienta