Planteamiento y contexto
Diseña el manejo de conflictos de eventos para un producto similar a Google Calendar. Los usuarios crean, actualizan y eliminan eventos con asistentes, horas de inicio y fin, zonas horarias, reglas de recurrencia, recordatorios, visibilidad y enlaces de reuniones. Una creación o actualización debe detectar conflictos y admitir políticas de rechazo, advertencia y continuación, o anulación. Asume que las lecturas de disponibilidad (free/busy) superan ampliamente a las escrituras, que las comprobaciones comunes requieren decenas de milisegundos y que los calendarios privados solo exponen el estado de ocupado.
Qué evalúa el entrevistador
Una respuesta sólida define la semántica de conflicto antes de nombrar una base de datos. Las señales clave son una regla de intervalos semiabiertos que evite falsos conflictos en los límites; un modelo canónico de eventos separado de un índice de solapamiento; límites explícitos para RRULE, excepciones y materialización; conversión de reglas de hora local a instantes absolutos; una ruta con patrón outbox o CDC para el índice derivado; y una estrategia de concurrencia para dos escritores que reservan el mismo recurso. Limitarse a decir "consultar la base de datos para ver solapamientos" deja sin resolver estos modos de falla.
Preguntas de clarificación
- ¿Los intervalos adyacentes entran en conflicto? Si no es así, usa el intervalo semiabierto
[start, end). - ¿Hasta qué punto debe expandirse una serie recurrente? Una serie sin límite necesita una ventana deslizante o expansión en tiempo de consulta.
- ¿Cualquier asistente ocupado bloquea el evento, o solo los asistentes obligatorios o el organizador? Esto define el fan-out de la consulta y la política.
- ¿La reserva duplicada se rechaza, se advierte o se permite? Una sala de conferencias y un calendario personal pueden usar políticas diferentes.
- ¿Otro usuario debería ver los detalles completos, un intervalo ocupado o nada? Esto determina las ACL y la estructura de la respuesta.
Una respuesta de 30 segundos
"Mantengo registros canónicos de eventos y asistentes, además de un índice de disponibilidad (free-busy) particionado por calendario. Existe un conflicto solo cuando existing.start < new.end y new.start < existing.end; los eventos libres no ocupan tiempo. Almaceno las reglas de recurrencia en la zona horaria del evento, materializo un horizonte acotado y registro excepciones. Las comprobaciones de versión o los bloqueos de recursos protegen las escrituras, y un outbox actualiza el índice derivado después del commit. Las respuestas solo exponen información de ocupación permitida por ACL, mientras que el monitoreo cubre la desviación del índice, las reconstrucciones y el retraso en las notificaciones."
Solución paso a paso
1. Definir el tiempo y el predicado de conflicto
Representa cada ocurrencia como [start_utc, end_utc), conservando la zona horaria original y la regla local para visualización y expansión. Dos ocurrencias entran en conflicto exactamente cuando a.start < b.end y b.start < a.end. De este modo, 10:00–11:00 y 11:00–12:00 son adyacentes, no solapadas; rechaza intervalos de longitud cero o invertidos. Convierte los eventos de todo el día a límites en la zona horaria del calendario antes de aplicar el mismo predicado.
2. Separar el modelo canónico del índice de disponibilidad
La capa canónica almacena eventos, asistentes, RRULE, excepciones, ACL y versiones para edición y auditoría. El índice de disponibilidad almacena únicamente calendar_id, límites de la ocurrencia, estado de ocupación, ID de evento y una etiqueta de visibilidad, ordenados por calendario y hora de inicio. Las consultas de conflicto escanean la ventana solicitada en lugar de un JSON de evento voluminoso. Particiona por mes o tenant; los calendarios con alto tráfico pueden requerir un shard o caché dedicados.
3. Manejar recurrencia y excepciones
Almacena RRULE al estilo iCalendar y expande las ocurrencias para un horizonte solicitado. Una materialización deslizante puede generar los próximos 90 días y extenderse a medida que se acerca el límite; las consultas más lejanas se expanden bajo demanda y almacenan en caché el resultado. this occurrence, this and future y las ediciones de toda la serie crean registros de excepción o una nueva versión de la serie en lugar de reescribir ocurrencias pasadas. Cada ocurrencia participa de forma independiente en las comprobaciones de conflicto y las notificaciones.
4. Ruta de escritura y control de concurrencia
Valida el tiempo, las ACL y la política de asistentes, y luego verifica la versión actual del índice en la misma transacción. Los calendarios personales pueden usar versiones optimistas y pedir a los clientes que reintenten; las salas estrictamente exclusivas o los espacios de citas pueden usar bloqueos por recurso o restricciones de exclusión en la base de datos. Haz commit del evento y de un registro outbox juntos; los consumidores actualizan el índice y envían notificaciones. Si el índice se retrasa, lee del almacén canónico o marca la respuesta como incierta; nunca afirmes silenciosamente que no hay conflicto.
5. Privacidad, consultas y escalabilidad
Estratifica las respuestas por ACL: los usuarios autorizados ven títulos y asistentes, mientras que otros solo ven intervalos ocupados y los eventos privados aparecen como bloques opacos. Mapea los estados de libre, tentativo y fuera de la oficina a ocupación o advertencias según la política. Almacena en caché las ventanas comunes para tráfico intensivo de lectura; incluye una versión de calendario o una marca de invalidación en la clave. Las llamadas de disponibilidad entre organizaciones necesitan tiempos de espera (timeouts) y semántica de resultados parciales para que un calendario externo no bloquee a todos los asistentes.
6. Consistencia, reparación y rutas de fallo
El evento canónico es la fuente de la verdad y el índice es reconstruible. Un outbox o CDC garantiza que un commit exitoso produzca eventualmente una actualización del índice; los consumidores procesan las versiones de manera idempotente para que un mensaje antiguo no sobrescriba uno más reciente. Compara periódicamente las ocurrencias canónicas con hashes o conteos del índice, reconstruye un calendario cuando haya discrepancias y registra el rango afectado. Reintenta las notificaciones fallidas sin revertir un evento que ya fue confirmado en el commit.
Respuesta de muestra de alta calidad
Primero definiría los conflictos con [start, end): solo la intersección estricta genera conflicto, los eventos adyacentes no, y los eventos libres no consumen disponibilidad. El modelo de eventos canónico almacena la zona horaria original, la RRULE, las excepciones, los asistentes y las ACL. Un índice de ocurrencias separado y particionado por calendario almacena únicamente límites, ocupación e ID del evento para escaneos rápidos de ventanas de tiempo. Las RRULE permanecen en la zona horaria del evento; materializo los próximos 90 días, expando rangos más lejanos bajo demanda y represento las ediciones de una sola ocurrencia, de ocurrencias futuras y de toda la serie como excepciones o versiones explícitas.
La ruta de escritura valida permisos y políticas, y luego utiliza una versión optimista o bloqueo de recurso para ediciones concurrentes. El evento y el registro outbox se confirman juntos en el commit; los consumidores conscientes de la versión actualizan el índice de forma idempotente y entregan notificaciones. El índice es derivado y se puede reconstruir a partir de los datos canónicos, y las respuestas de conflicto se filtran mediante ACL. Emitiría una advertencia en lugar de bloquear una reserva doble en un calendario personal, pero usaría una restricción de exclusión o bloqueo para una sala. Monitorearía la discrepancia del índice, versiones obsoletas, latencia de consultas y timeouts de disponibilidad externa.
Errores comunes
- Error: usar
start <= other.endpara solapamientos → los eventos adyacentes se convierten en falsos conflictos → especifica la regla de intervalos semiabiertos y rechaza eventos no válidos de longitud cero. - Error: expandir una serie recurrente ilimitada en cada solicitud → la latencia y el procesamiento no tienen límite superior → utiliza un horizonte de materialización deslizante y expansión bajo demanda más allá de este.
- Error: usar el JSON completo del evento como índice de conflictos → las lecturas escanean campos grandes y filtran detalles privados → mantén una proyección de tiempo y ocupación y censura mediante ACL.
- Error: revertir el evento cuando falla la actualización del índice → la fuente de la verdad queda acoplada a las búsquedas y notificaciones → haz commit en un outbox, reintenta de forma asíncrona y proporciona mecanismos de reconstrucción.
- Error: tomar un bloqueo global para cada calendario → los calendarios de alto tráfico ralentizan todo el servicio → bloquea solo recursos estrictamente exclusivos y usa comprobaciones de versión para calendarios ordinarios.
Preguntas de seguimiento y respuestas
¿Cómo encontrarías un horario en el que todos los asistentes estén libres?
Consulta los intervalos ocupados de cada asistente obligatorio para la ventana objetivo, fusiona los intervalos en una línea de tiempo común y toma el complemento de su unión. El fan-out aumenta con la cantidad de asistentes, por lo que debes ejecutar consultas en paralelo, limitar la ventana y devolver resultados parciales o inciertos para calendarios no autorizados o con timeout en lugar de exponer detalles privados.
Una reunión recurrente entra en conflicto solo en algunas fechas después de un cambio de horario de verano (DST). ¿Cómo lo depuras?
Inspecciona el identificador de zona horaria de la RRULE, la hora local original, la versión de la biblioteca de expansión y los registros de excepciones. No conviertas la hora local a UTC descartando la zona antes de expandir. Reproduce un caso de prueba que cruce el límite de DST y compara la hora local mostrada de cada ocurrencia, los límites UTC y el predicado de solapamiento para separar los errores de expansión de los errores de materialización del índice.
¿Qué cambia cuando una sala nunca debe reservarse dos veces?
Dos solicitudes pueden observar la sala como libre al mismo tiempo. Protege el rango de tiempo del recurso con una restricción de exclusión de base de datos, una transacción serializable o un bloqueo de recurso corto, y devuelve la ocurrencia conflictiva en caso de fallo. El bloqueo cubre únicamente la ventana de commit, nunca una caché; particiona recursos calientes y limita los reintentos para que una cola de bloqueo no deje sin recursos a otros calendarios.