Debido a que cada trabajador ejecuta la porción paralela del plan hasta completarla, no es posible simplemente tomar un plan de consulta ordinario y ejecutarlo utilizando múltiples trabajadores. Cada trabajador produciría una copia completa del conjunto de resultados de salida, por lo que la consulta no se ejecutaría más rápido de lo normal sino que produciría resultados incorrectos. En su lugar, la porción paralela del plan debe ser lo que internamente se conoce en el optimizador de consultas como un plan parcial (partial plan); es decir, debe construirse de modo que cada proceso que ejecute el plan genere solo un subconjunto de las filas de salida, de tal manera que se garantice que cada fila de salida requerida sea generada por exactamente uno de los procesos colaboradores. Generalmente, esto significa que el escaneo de la tabla que impulsa la consulta debe ser un escaneo compatible con paralelismo.
Actualmente se admiten los siguientes tipos de escaneos de tablas compatibles con paralelismo.
En un escaneo secuencial paralelo (parallel sequential scan), los bloques de la tabla se dividirán en rangos y se compartirán entre los procesos colaboradores. Cada proceso trabajador completará el escaneo de su rango de bloques asignado antes de solicitar un rango de bloques adicional.
En un escaneo de mapa de bits del heap en paralelo (parallel bitmap heap scan), se elige un proceso como líder. Ese proceso realiza un escaneo de uno o más índices y construye un mapa de bits que indica qué bloques de la tabla deben visitarse. Estos bloques se dividen luego entre los procesos colaboradores como en un escaneo secuencial paralelo. En otras palabras, el escaneo del heap se realiza en paralelo, pero el escaneo del índice subyacente no.
En un escaneo de índice en paralelo (parallel index scan) o un escaneo solo de índice en paralelo (parallel index-only scan), los procesos colaboradores se turnan para leer datos del índice. Actualmente, los escaneos de índice en paralelo se admiten solo para índices btree. Cada proceso tomará un único bloque de índice y escaneará y devolverá todas las tuplas referenciadas por ese bloque; otros procesos pueden al mismo tiempo estar devolviendo tuplas de un bloque de índice diferente. Los resultados de un escaneo btree en paralelo se devuelven en orden ordenado dentro de cada proceso trabajador.
Otros tipos de escaneo, como los escaneos de índices que no sean btree, podrían admitir escaneos paralelos en el futuro.
Al igual que en un plan no paralelo, la tabla conductora se puede unir a una o más tablas usando un bucle anidado (nested loop), una unión por hash (hash join) o una unión por fusión (merge join). El lado interno de la unión puede ser cualquier tipo de plan no paralelo admitido por el planificador, siempre que sea seguro de ejecutar dentro de un trabajador paralelo. Dependiendo del tipo de unión, el lado interno también puede ser un plan paralelo.
En una unión por bucle anidado (nested loop join), el lado interno siempre es no paralelo. Aunque se ejecuta por completo, esto es eficiente si el lado interno es un escaneo de índice, porque las tuplas externas (y, por lo tanto, los bucles que buscan valores en el índice) se dividen entre los procesos colaboradores.
En una unión por fusión (merge join), el lado interno siempre es un plan no paralelo y, por lo tanto, se ejecuta en su totalidad. Esto puede ser ineficiente, especialmente si se debe realizar una ordenación, porque el trabajo y los datos resultantes se duplican en cada proceso colaborador.
En una unión por hash (sin el prefijo "paralelo"), el lado interno es ejecutado por completo por cada proceso colaborador para construir copias idénticas de la tabla hash. Esto puede ser ineficiente si la tabla hash es grande o el plan es costoso. En una unión por hash paralela (parallel hash join), el lado interno es un hash paralelo que divide el trabajo de construir una tabla hash compartida entre los procesos colaboradores.
PostgreSQL admite la agregación en paralelo mediante una agregación en dos etapas.
Primero, cada proceso que participa en la porción paralela de la consulta realiza un paso de agregación,
produciendo un resultado parcial para cada grupo del que ese proceso tiene conocimiento. Esto se refleja
en el plan como un nodo Partial Aggregate. Segundo, los resultados parciales se transfieren
al líder a través de Gather o Gather Merge. Finalmente, el líder
vuelve a agregar los resultados de todos los trabajadores para producir el resultado final. Esto se refleja en el
plan como un nodo Finalize Aggregate.
Debido a que el nodo Finalize Aggregate se ejecuta en el proceso líder, las consultas
que producen un número relativamente grande de grupos en comparación con el número de filas de entrada parecerán
menos favorables para el planificador de consultas. Por ejemplo, en el peor de los casos, el número de grupos
visto por el nodo Finalize Aggregate podría ser tan grande como el número de filas de entrada
vistas por todos los procesos trabajadores en la etapa de Partial Aggregate. Para tales casos,
claramente no habrá ningún beneficio de rendimiento al usar la agregación paralela. El planificador de consultas
toma esto en cuenta durante el proceso de planificación y es poco probable que elija la agregación paralela en este escenario.
La agregación en paralelo no se admite en todas las situaciones. Cada agregación debe ser segura para el paralelismo y debe tener una función de combinación (combine function). Si la agregación tiene un estado de transición de tipo internal, debe tener funciones de serialización y deserialización. Consulta CREATE AGGREGATE para más detalles. La agregación paralela no se admite si alguna llamada a función de agregación contiene las cláusulas DISTINCT o ORDER BY, y tampoco se admite para agregaciones de conjuntos ordenados o cuando la consulta involucra GROUPING SETS. Solo se puede usar cuando todas las uniones involucradas en la consulta también forman parte de la porción paralela del plan.
Cada vez que PostgreSQL necesita combinar filas de múltiples fuentes en un solo
conjunto de resultados, utiliza un nodo de plan Append o MergeAppend.
Esto ocurre comúnmente al implementar UNION ALL o al escanear una tabla particionada. Estos
nodos se pueden usar en planes paralelos al igual que en cualquier otro plan. Sin embargo, en un plan paralelo,
el planificador puede usar en su lugar un nodo Parallel Append.
Cuando se usa un nodo Append en un plan paralelo, cada proceso ejecutará los planes hijos en
el orden en que aparecen, de modo que todos los procesos participantes colaboren para ejecutar el primer plan
hijo hasta que se complete y luego pasen al segundo plan aproximadamente al mismo tiempo. Cuando se usa un
Parallel Append in su lugar, el ejecutor distribuirá los procesos participantes de la manera más
uniforme posible entre sus planes hijos, de modo que se ejecuten múltiples planes hijos simultáneamente. Esto evita
la contención y también evita pagar el costo de inicio de un plan hijo en aquellos procesos que nunca lo ejecutan.
Además, a diferencia de un nodo Append normal, que solo puede tener hijos parciales cuando se
usa dentro de un plan paralelo, un nodo Parallel Append puede tener tanto planes hijos parciales
como no parciales. Los hijos no parciales serán escaneados por un solo proceso, ya que escanearlos más de una vez
produciría resultados duplicados. Los planes que implican añadir múltiples conjuntos de resultados pueden, por lo tanto,
lograr un paralelismo de grano grueso incluso cuando no se dispone de planes parciales eficientes. Por ejemplo, considera
una consulta contra una tabla particionada que solo se puede implementar de manera eficiente usando un índice que no
admite escaneos paralelos. El planificador podría elegir un Parallel Append de planes de
Index Scan regulares; cada escaneo de índice individual tendría que ser ejecutado hasta completarse por un único proceso, pero diferentes escaneos podrían realizarse al mismo tiempo por diferentes procesos.
Se puede usar enable_parallel_append para desactivar esta característica.
Si una consulta que se espera que lo haga no produce un plan paralelo, puedes intentar reducir parallel_setup_cost o parallel_tuple_cost. Por supuesto, este plan puede resultar ser más lento que el plan secuencial que prefirió el planificador, pero no siempre será el caso. Si no obtienes un plan paralelo incluso con valores muy pequeños de estos ajustes (por ejemplo, después de establecer ambos en cero), puede haber alguna razón por la cual el planificador de consultas es incapaz de generar un plan paralelo para tu consulta. Consulta Section 15.2 and Section 15.4 para obtener información sobre por qué puede ser este el caso.
Al ejecutar un plan paralelo, puedes usar EXPLAIN (ANALYZE, VERBOSE) para mostrar
estadísticas por trabajador para cada nodo del plan. Esto puede ser útil para determinar si el trabajo se
está distribuyendo uniformemente entre todos los nodos del plan y, de manera más general, para comprender las
características de rendimiento del plan.