Buscar/Desarrolla una extensión de índice para bases de datos que pase las pruebas y resista caídas

Desarrolla una extensión de índice para bases de datos que pase las pruebas y resista caídas

Úsalo cuando una IA cree o cambie una extensión de índice de base de datos. Trata cada fallo de prueba como real, sigue un orden fijo antes del commit y registra cada cambio de página para que la recuperación y los servidores en espera funcionen.

Para usar estas reglas de IA
## Reinicia el servidor tras reinstalar y lee primero la especificación de almacenamiento Colaboras en una extensión de base de datos que implementa un método de acceso por índice respaldado por almacenamiento en disco estructurado en capas dentro de los propios archivos de relación de la base de datos. El trabajo está muy cerca de la durabilidad, la recuperación y la concurrencia, por lo que la corrección prevalece sobre la rapidez de entrega en cada decisión. Supuestos operativos que debes mantener en todo momento: - La extensión se carga al iniciar el servidor. Es necesario añadirla a la configuración de precarga del servidor y reiniciar antes de poder crear la extensión, y se requiere otro reinicio tras reinstalar el binario durante el desarrollo. Un binario cargado obsoleto produce resultados de prueba engañosos; cuando el comportamiento contradiga el código fuente que acabas de compilar, sospecha primero de un reinicio omitido. - Instala los objetos de la extensión en un esquema dedicado en lugar del espacio de nombres público predeterminado siempre que el proyecto lo permita, para mantener limpio el espacio de nombres. - Prefiere el cambio más pequeño que resuelva el problema por completo. No refactorices código adyacente que la tarea no requería. - Las especificaciones de diseño del repositorio son vinculantes. Lee la especificación de almacenamiento antes de tocar la ruta de escritura, de lectura o de volcado, y actualiza la especificación en el mismo cambio cuando el propio diseño se modifique. ## Investiga cada fallo de prueba en lugar de llamarlo ajeno a tu cambio La rama principal se mantiene continuamente en verde. Todo fallo de prueba importa. - Nunca descartes un fallo como preexistente o ajeno a tu cambio. En la práctica casi nunca lo es. - Si de verdad crees que un fallo no está relacionado con tu cambio, verifícalo: cambia a la rama principal y ejecuta allí la misma prueba. - Reproducirlo en la rama principal no exime del fallo. Sigue siendo necesario investigarlo y corregirlo, no archivarlo ni anotarlo como conocido. - Nunca marques una prueba como omitida, relajes una aserción ni edites un archivo de salida esperada solo para que una ejecución en rojo se vuelva verde. Cambia una expectativa únicamente después de poder explicar con precisión por qué la nueva salida es correcta. - Informa de los fallos con honestidad y cuanto antes, incluidos los que hayas provocado tú. Ocultar una ejecución en rojo es peor que el propio error. ## Ejecuta las pruebas de regresión y revisa el archivo de diferencias antes de cada commit Completa todos los pasos siguientes antes de confirmar un cambio. No reordenes ni omitas pasos porque un cambio "parezca trivial". 1. Compila la extensión. 2. Ejecuta el conjunto de pruebas de regresión declarativas contra una compilación instalada. 3. Inspecciona el archivo de diferencias de regresión que produce la ejecución, no solo el resumen de éxito o fallo. Un conjunto de pruebas puede informar de éxito y a la vez enmascarar una diferencia que tú introdujiste. 4. Si modificaste algún mensaje visible para el usuario, actualiza los archivos de salida esperada correspondientes para que coincidan, e indica en el commit por qué cambió el mensaje. 5. Ejecuta la comprobación de formato. 6. Vuelve a ejecutar el conjunto de pruebas de regresión después de actualizar cualquier archivo de expectativas, para que la ejecución final registrada sea la que refleja el árbol confirmado. Más allá del conjunto de pruebas habitual, ejecuta los niveles de concurrencia, recuperación ante caídas, múltiples procesos de servidor y estrés siempre que un cambio afecte al bloqueo, a la ruta de escritura, a la recuperación o al ciclo de vida de los segmentos. Indica con claridad qué niveles ejecutaste y cuáles no. ## Pon cada cambio en la capa de código más baja que lo resuelva El código fuente está organizado en capas, y cada cambio pertenece a la capa más baja que lo resuelva por completo. No filtres detalles de almacenamiento hacia arriba, a la capa de interfaz con la base de datos, ni alcances hacia abajo saltándote una abstracción para atajar una corrección. - Capa uno, interfaz con la base de datos: el gestor del método de acceso, la construcción del índice, los puntos de entrada de exploración y vacuum; los tipos de datos personalizados; los enganches del planificador y la estimación de costes. - Capa dos, coordinación del índice: clasificación y puntuación, optimización top-k, ciclo de vida del estado del índice, registro, manejo de la metapágina y la abstracción sobre las fuentes de listas de publicación. - Capa tres, almacenamiento: el búfer de escritura de nivel cero, los segmentos inmutables, la fusión y la compresión. - Transversal: utilidades de depuración y volcado, e inicialización de módulos. Cuando un cambio abarque varias capas, describe explícitamente el cruce de fronteras en la descripción del cambio. Una corrección que ensancha una interfaz de forma silenciosa es un cambio de diseño y debe revisarse como tal. ## Entiende el búfer de escritura y los segmentos inmutables antes de tocar el almacenamiento El índice sigue un enfoque de almacenamiento en capas y estructurado como registro. Comprende el modelo antes de alterar cualquier parte de él. - El nivel cero es un búfer de escritura: una cadena de páginas orientada a la anexión, almacenada dentro de la propia relación del índice y anclada en una metapágina. Las escrituras añaden a la página final registros que contienen el identificador de fila, una longitud y una carga útil empaquetada. Todas las mutaciones se realizan bajo los bloqueos de búfer estándar y quedan registradas en el registro de escritura anticipada. - Los segmentos son estructuras inmutables en disco organizadas en bloques que incluyen entradas de salto para que una exploración pueda situarse en un bloque sin recorrer todas las publicaciones. - El volcado desde el búfer de escritura hacia un nuevo segmento se activa por umbral, y los segmentos se compactan por nivel cuando un nivel acumula suficientes. - Las consultas top-k usan cotas superiores a nivel de bloque, de modo que los bloques que no pueden contribuir al resultado se omiten por completo, con terminación anticipada en cuanto la cota restante no puede superar el conjunto de resultados actual. Una caché en memoria puede atender las lecturas de consulta por rapidez, pero la cadena duradera en disco sigue siendo la única fuente de verdad, y los servidores en espera de solo lectura leen siempre la cadena duradera en lugar de una caché. ## Registra cada cambio de página con el registro de escritura anticipada estándar, no con uno propio Registra cada mutación de página mediante el mecanismo genérico de registro de escritura anticipada de la base de datos en lugar de registrar un gestor de recursos propio para la extensión. Por qué existe esta restricción: con el registro genérico, la maquinaria de reproducción estándar de la base de datos reconstruye cada página durante la recuperación ante caídas y en un servidor en espera con replicación en streaming, incluidas las páginas de la cadena del búfer de escritura, las páginas de segmento y la metapágina. Eso es exactamente lo que permite que funcionen correctamente los ayudantes de rehacer de página única y cualquier otro contexto de reproducción que no pueda cargar la biblioteca compartida de la extensión. Consecuencias que hay que respetar: - Introducir un gestor de recursos propio es una decisión de diseño de calado, no un detalle de implementación. Rompe todos los contextos de reproducción que no pueden cargar la extensión. Nunca lo introduzcas de forma implícita como parte de un cambio no relacionado; plantéalo como una propuesta aparte con su propia revisión. - Toda estructura nueva en disco debe poder reconstruirse mediante la reproducción estándar únicamente a partir de los registros escritos en el log. Si una estructura solo puede reconstruirse con código de la extensión, el diseño es erróneo. - Nunca eludas el gestor de búferes ni escribas páginas fuera de una mutación registrada, ni siquiera para una estructura que consideres desechable. ## Retén las páginas liberadas por una fusión hasta que los servidores en espera dejen de leerlas Una fusión no debe devolver de inmediato al mapa de espacio libre las páginas de origen desplazadas. Hacerlo es seguro en el servidor primario, pero inseguro para las consultas de lectura en curso en un servidor en espera activo, porque sin un gestor de recursos propio no existe mecanismo para resolver conflictos de recuperación durante la reproducción: el servidor en espera reutilizaría páginas que una consulta en ejecución todavía está leyendo. El patrón requerido: - Aparca las páginas desplazadas en una cadena de lápidas registrada en el log y anclada a la metapágina, marcada con el identificador de transacción de la fusión. - Devuelve las páginas aparcadas al mapa de espacio libre solo cuando un vacuum posterior, o la siguiente fusión, observe que la marca precede al horizonte de transacciones no eliminables más antiguo. Ese horizonte es la frontera de recuperación segura para los servidores en espera. - Exige que en el despliegue esté habilitada la retroalimentación desde los servidores en espera activos que atienden consultas. Su instantánea más antigua retiene entonces el horizonte del primario hasta que terminan de leer las páginas aparcadas. - Expón una función de diagnóstico que informe de cuántas páginas hay aparcadas en cada momento, para que los operadores puedan observar el crecimiento del acumulado en lugar de adivinarlo. Generaliza el principio: cuando un componente libere un recurso compartido que un lector remoto o diferido pueda seguir reteniendo, aplaza la liberación tras un horizonte observable y da a los operadores una forma de medir el conjunto aplazado. ## Ordena por la puntuación de clasificación en lugar de filtrar con ella Las puntuaciones de clasificación existen para ordenar, no para filtrar. Sus valores numéricos son opacos y no llevan asociado ningún umbral con sentido, por lo que comparar una puntuación con una constante es un antipatrón siempre que se use como filtro de conveniencia en pruebas, ejemplos o documentación. Reglas: - No escribas una condición de filtro que compare una puntuación de clasificación con un número para seleccionar filas "coincidentes". - Una condición de filtro que contenga el operador de clasificación desencadena una puntuación autónoma mediante una exploración secuencial en lugar de una exploración por índice. Las exploraciones por índice requieren una cláusula de ordenación. - El patrón correcto es ordenar por la expresión de clasificación y aplicar un límite de filas. - Para contar los documentos coincidentes, envuelve una subconsulta ordenada y cuenta sus filas, en lugar de filtrar por la puntuación. - Excepción: una prueba cuyo objetivo explícito sea la ruta de puntuación autónoma, por ejemplo una que compare los resultados de la exploración por índice con los resultados autónomos, puede usar el operador en una condición de filtro. Tales pruebas deben declarar esa intención en un comentario para que un lector posterior no copie el patrón por error. La lección general: cuando una API tiene una única forma de uso prevista, haz que la forma incorrecta resulte visiblemente incorrecta en todos los ejemplos que publique el equipo, y admite excepciones solo con una intención documentada. ## Documenta cada ajuste en una sola tabla con un valor predeterminado seguro Todo parámetro ajustable en tiempo de ejecución está documentado en una única tabla con su nombre, su descripción y su valor predeterminado. Un parámetro ajustable sin documentar se considera un defecto. - Elige valores predeterminados que sean seguros y neutros en cuanto al comportamiento para un despliegue corriente. Usa el cero con el significado de "desactivado" en los ajustes de tipo umbral y de "sin límite" en los ajustes de tipo presupuesto, e indícalo así en la descripción. - Los ajustes que inyectan fallos o fuerzan errores existen solo para pruebas, deben restringirse a usuarios privilegiados y deben etiquetarse en su descripción como exclusivos para pruebas. - Los interruptores de registro para el detalle de puntuación, las estadísticas de optimización y los resultados de la caché están desactivados de forma predeterminada; son ayudas de diagnóstico, no operación normal. - Los presupuestos de memoria son escalonados en lugar de un único muro rígido: un tope flexible por índice degrada ese índice a la ruta duradera de reserva, un tope flexible global expulsa al mayor consumidor distinto del solicitante, y un tope rígido global rechaza directamente las nuevas asignaciones. Cada nivel debe ser observable en los registros. - Las opciones a nivel de índice, como la configuración de análisis de texto y los parámetros de saturación y de normalización por longitud del modelo de clasificación, se documentan con el mismo rigor, y las opciones obligatorias se marcan como obligatorias en lugar de recibir un valor predeterminado silencioso. ## Marca los documentos muertos y anota que las estadísticas quedan desfasadas hasta la próxima fusión Vacuum marca los documentos muertos en un mapa de bits de documentos vivos por segmento, un bit por documento, en lugar de reconstruir el segmento. El coste es proporcional al número de documentos muertos y no al corpus completo. Los documentos muertos se filtran durante la puntuación y se eliminan físicamente en la siguiente fusión de segmentos. El compromiso conocido debe permanecer documentado y no oculto: marcar documentos como muertos no actualiza el recuento total de documentos del segmento, ni las estadísticas de longitud total, ni la frecuencia de documentos por término. Por tanto, la clasificación utiliza estadísticas del corpus ligeramente desactualizadas hasta que la siguiente fusión o compactación las corrige. Con un número pequeño de borrados el efecto es insignificante; un borrado masivo puede afectar de forma visible a la calidad de la clasificación hasta la siguiente fusión. Trata esto como una plantilla. Cuando una optimización sacrifique exactitud o frescura a cambio de coste, deja el compromiso por escrito donde el responsable de mantenimiento lo vaya a encontrar, indica las condiciones bajo las cuales pasa a ser significativo y nombra el evento que lo resuelve. Una aproximación sin documentar se convierte más adelante en un informe de error inexplicable. ## Mantén niveles de prueba separados y funciones de diagnóstico propias Mantén niveles de prueba diferenciados y ten claro qué protege cada uno: - Pruebas de regresión declarativas para la funcionalidad principal, la corrección de la puntuación, el almacenamiento y los segmentos, las rutas de optimización de consultas, las variantes de tablas como las particionadas y las heredadas, y los casos límite, con las salidas de referencia bajo control de versiones. - Pruebas basadas en scripts para la concurrencia entre varias sesiones, la recuperación ante caídas, el comportamiento de los segmentos con múltiples procesos de servidor y el estrés de larga duración. La integración continua cubre todas las versiones mayores de base de datos admitidas, compilaciones con sanitizadores, validación del formato, pruebas de actualización de versión, empaquetado de la release, pruebas de rendimiento y una ejecución nocturna de estrés con detección de fugas de memoria. Publica los diagnósticos como funciones de primera clase, no como impresiones improvisadas: vuelca la estructura interna con su diccionario de términos, sus listas de publicación y sus entradas de documento; vuelca el índice completo a un archivo; resume estadísticas de alto nivel; fuerza un volcado e informa de cuántas entradas se movieron; informa del recuento de páginas aparcadas; y genera una visualización de la disposición de las páginas. Mantén reproducibles las pruebas de rendimiento: una pequeña colección clásica de recuperación de información para obtener señal rápida, conjuntos de datos públicos más grandes para escala, un script de estrés de memoria y capacidad, y una comparación frente a un motor externo consolidado para validar que los resultados no son meramente autoconsistentes. ## Usa el formateador automático y haz rebase sobre main antes de hacer push - El formato está automatizado. Proporciona y utiliza comandos para formatear el árbol, comprobar el formato sin modificar nada, mostrar las diferencias y formatear un único archivo. Nunca formatees a mano en contra de la herramienta. - Sigue las convenciones de codificación y los patrones de asignación de memoria del proyecto de base de datos anfitrión en lugar de inventar los tuyos propios, y ajusta las líneas al ancho configurado en el proyecto. - Invariantes de concurrencia: toda estructura compartida está protegida por un bloqueo adecuado, las estructuras de internado se hacen seguras mediante bloqueos de tabla hash, y el aislamiento transaccional se mantiene con una limpieza disciplinada tanto en la ruta de confirmación como en la de cancelación. - Después de que se integre una solicitud de incorporación de cambios, comprueba siempre si la rama principal se ha movido, incluso cuando la integración la haya hecho otra persona. Trae los cambios y rebasa las ramas activas sobre la rama principal actualizada para evitar acumular conflictos. - Antes de subir cambios, verifica que la rama está al día con la principal, de modo que unos historiales divergentes no conviertan un cambio pequeño en una integración plagada de conflictos. - Sigue el procedimiento de publicación documentado del repositorio en lugar de improvisar etiquetas o artefactos.