El módulo GEQO aborda el problema de la optimización de consultas como si fuera el conocido problema del viajante (TSP). Los planes de consulta posibles se codifican como cadenas de enteros. Cada cadena representa el orden de join desde una relación de la consulta hasta la siguiente. Por ejemplo, el árbol de join
/\ /\ 2 /\ 3 4 1
se codifica mediante la cadena de enteros '4-1-3-2', lo que significa: primero unir la relación '4' y '1', luego '3' y luego '2', donde 1, 2, 3, 4 son IDs de relación dentro del optimizador de PostgreSQL.
Las características específicas de la implementación de GEQO en PostgreSQL son:
El uso de un GA de estado estacionario (steady state) (reemplazo de los individuos con menor aptitud en una población, en lugar del reemplazo de generaciones completas) permite una convergencia rápida hacia planes de consulta mejorados. Esto es esencial para el manejo de consultas en un tiempo razonable;
El uso de la recombinación de bordes (edge recombination crossover), la cual es especialmente adecuada para mantener bajas las pérdidas de bordes en la resolución del TSP mediante un GA;
La mutación como operador genético está obsoleta, por lo que no se necesitan mecanismos de reparación para generar rutas del TSP válidas.
Partes del módulo GEQO están adaptadas del algoritmo Genitor de D. Whitley.
El módulo GEQO permite al optimizador de consultas de PostgreSQL soportar grandes consultas con joins de manera efectiva a través de una búsqueda no exhaustiva.
El proceso de planificación de GEQO utiliza el código del planificador estándar para generar planes para escaneos de relaciones individuales. Luego, los planes de join se desarrollan utilizando el enfoque genético. Como se mostró anteriormente, cada plan de join candidato está representado por una secuencia en la que se unen las relaciones base. En la etapa inicial, el código de GEQO simplemente genera algunas secuencias de join posibles al azar. Para cada secuencia de join considerada, se invoca el código del planificador estándar para estimar el costo de realizar la consulta utilizando esa secuencia de join. (Para cada paso de la secuencia de join, se consideran las tres estrategias de join posibles; y todos los planes de escaneo de relaciones determinados inicialmente están disponibles. El costo estimado es el más barato de estas posibilidades). Las secuencias de join con menor costo estimado se consideran “más aptas” que aquellas con mayor costo. El algoritmo genético descarta los candidatos menos aptos. Luego, se generan nuevos candidatos combinando genes de los candidatos más aptos — es decir, utilizando partes elegidas al azar de secuencias de join de bajo costo conocidas para crear nuevas secuencias para su consideración. Este proceso se repite hasta que se ha considerado un número preestablecido de secuencias de join; luego, la mejor encontrada en cualquier momento de la búsqueda se utiliza para generar el plan terminado.
Este proceso es inherentemente no determinista debido a las decisiones aleatorias
tomadas tanto durante la selección inicial de la población como en la posterior
“mutación” de los mejores candidatos. Para evitar cambios sorprendentes
en el plan seleccionado, cada ejecución del algoritmo GEQO reinicia su generador de
números aleatorios con la configuración actual del parámetro geqo_seed.
Siempre que geqo_seed y los demás parámetros de GEQO se
mantengan fijos, se generará el mismo plan para una consulta dada (y otras entradas del
planificador como las estadísticas). Para experimentar con diferentes rutas de búsqueda,
prueba a cambiar geqo_seed.
Aún se necesita trabajar para mejorar la configuración de los parámetros del algoritmo
genético. En el archivo src/backend/optimizer/geqo/geqo_main.c,
en las rutinas gimme_pool_size y
gimme_number_generations, tenemos que encontrar un compromiso
para la configuración de los parámetros para satisfacer dos demandas competitivas:
Optimalidad del plan de consulta
Tiempo de cálculo
En la implementación actual, la aptitud de cada secuencia de join candidata se estima ejecutando desde cero el código de selección de join y estimación de costos del planificador estándar. En la medida en que diferentes candidatos utilicen sub-secuencias similares de joins, se repetirá una gran cantidad de trabajo. Esto podría hacerse significativamente más rápido reteniendo las estimaciones de costos para sub-joins. El problema es evitar gastar cantidades irrazonables de memoria para mantener ese estado.
A un nivel más básico, no está claro que resolver la optimización de consultas con un algoritmo genético diseñado para el problema del viajante (TSP) sea lo más adecuado. En el caso del TSP, el costo asociado con cualquier subcadena (ruta parcial) es independiente del resto de la ruta, pero esto ciertamente no es cierto para la optimización de consultas. Por lo tanto, es cuestionable si el cruce por recombinación de bordes (edge recombination crossover) es el procedimiento de mutación más efectivo.