sql >> Base de Datos >  >> RDS >> PostgreSQL

Una descripción general de la compilación Just-in-Time (JIT) para PostgreSQL

Históricamente, PostgreSQL ha proporcionado funciones de compilación en forma de compilación anticipada para funciones PL/pgSQL y la versión 10 introdujo la compilación de expresiones. Sin embargo, ninguno de ellos genera código de máquina.

JIT para SQL se discutió hace muchos años, y para PostgreSQL, la función es el resultado de un cambio sustancial en el código.

Para verificar si el binario de PostgreSQL se creó con soporte para LLVM, use el comando pg_configure para mostrar los indicadores de compilación y busque –with-llvm en la salida. Ejemplo para la distribución RPM PGDG:

omiday ~ $ /usr/pgsql-11/bin/pg_config --configure
'--enable-rpath' '--prefix=/usr/pgsql-11' '--includedir=/usr/pgsql-11/include' '--mandir=/usr/pgsql-11/share/man' '--datadir=/usr/pgsql-11/share' '--enable-tap-tests' '--with-icu' '--with-llvm' '--with-perl' '--with-python' '--with-tcl' '--with-tclconfig=/usr/lib64' '--with-openssl' '--with-pam' '--with-gssapi' '--with-includes=/usr/include' '--with-libraries=/usr/lib64' '--enable-nls' '--enable-dtrace' '--with-uuid=e2fs' '--with-libxml' '--with-libxslt' '--with-ldap' '--with-selinux' '--with-systemd' '--with-system-tzdata=/usr/share/zoneinfo' '--sysconfdir=/etc/sysconfig/pgsql' '--docdir=/usr/pgsql-11/doc' '--htmldir=/usr/pgsql-11/doc/html' 'CFLAGS=-O2 -g -pipe -Wall -Werror=format-security -Wp,-D_FORTIFY_SOURCE=2 -Wp,-D_GLIBCXX_ASSERTIONS -fexceptions -fstack-protector-strong -grecord-gcc-switches -specs=/usr/lib/rpm/redhat/redhat-hardened-cc1 -specs=/usr/lib/rpm/redhat/redhat-annobin-cc1 -m64 -mtune=generic -fasynchronous-unwind-tables -fstack-clash-protection -fcf-protection' 'PKG_CONFIG_PATH=:/usr/lib64/pkgconfig:/usr/share/pkgconfig'

¿Por qué LLVM JIT?

Todo comenzó hace aproximadamente dos años, como se explica en la publicación de Adres Freund, cuando la evaluación de expresiones y la deformación de tupla demostraron ser los obstáculos para acelerar las consultas grandes. Después de agregar la implementación JIT, "la evaluación de la expresión en sí es más de diez veces más rápida que antes", en palabras de Andrés. Además, la sección de preguntas y respuestas que finaliza su publicación explica la elección de LLVM sobre otras implementaciones.

Si bien LLVM fue el proveedor elegido, el parámetro GUC jit_provider se puede usar para apuntar a otro proveedor JIT. Sin embargo, tenga en cuenta que la compatibilidad con la inserción solo está disponible cuando se utiliza el proveedor LLVM, debido a la forma en que funciona el proceso de compilación.

¿Cuándo hacer JIT?

La documentación es clara:las consultas de ejecución prolongada que están vinculadas a la CPU se beneficiarán de la compilación JIT. Además, las discusiones de la lista de correo a las que se hace referencia en este blog señalan que JIT es demasiado costoso para las consultas que se ejecutan solo una vez.

En comparación con los lenguajes de programación, PostgreSQL tiene la ventaja de "saber" cuándo usar JIT, al confiar en el planificador de consultas. A tal efecto, se introdujeron una serie de parámetros GUC. Para proteger a los usuarios de sorpresas negativas al habilitar JIT, los parámetros relacionados con el costo se establecen intencionalmente en valores razonablemente altos. Tenga en cuenta que establecer los parámetros de costo JIT en '0' forzará que todas las consultas se compilen JIT y, como resultado, ralentizará todas sus consultas.

Si bien JIT puede ser generalmente beneficioso, hay casos en los que tenerlo habilitado puede ser perjudicial, como se explica en la confirmación b9f2d4d3.

¿Cómo JIT?

Como se mencionó anteriormente, los paquetes binarios RPM están habilitados para LLVM. Sin embargo, para que la compilación JIT funcione, se requieren algunos pasos adicionales:

A saber:

[email protected][local]:54311 test# show server_version;
server_version
----------------
11.1
(1 row)
[email protected][local]:54311 test# show port;
port
-------
54311
(1 row)
[email protected][local]:54311 test# create table t1 (id serial);
CREATE TABLE
[email protected][local]:54311 test# insert INTO t1 (id) select * from generate_series(1, 10000000);
INSERT 0 10000000
[email protected][local]:54311 test# set jit = 'on';
SET
[email protected][local]:54311 test# set jit_above_cost = 10; set jit_inline_above_cost = 10; set jit_optimize_above_cost = 10;
SET
SET
SET
[email protected][local]:54311 test# explain analyze select count(*) from t1;
                                                               QUERY PLAN
-----------------------------------------------------------------------------------------------------------------------------------------
Finalize Aggregate  (cost=97331.43..97331.44 rows=1 width=8) (actual time=647.585..647.585 rows=1 loops=1)
   ->  Gather  (cost=97331.21..97331.42 rows=2 width=8) (actual time=647.484..649.059 rows=3 loops=1)
         Workers Planned: 2
         Workers Launched: 2
         ->  Partial Aggregate  (cost=96331.21..96331.22 rows=1 width=8) (actual time=640.995..640.995 rows=1 loops=3)
               ->  Parallel Seq Scan on t1  (cost=0.00..85914.57 rows=4166657 width=0) (actual time=0.060..397.121 rows=3333333 loops=3)
Planning Time: 0.182 ms
Execution Time: 649.170 ms
(8 rows)

Tenga en cuenta que habilité JIT (que está deshabilitado de forma predeterminada después de la discusión de pgsql-hackers a la que se hace referencia en la confirmación b9f2d4d3). También ajusté el costo de los parámetros JIT como se sugiere en la documentación.

La primera pista se encuentra en el archivo src/backend/jit/README al que se hace referencia en la documentación de JIT:

Which shared library is loaded is determined by the jit_provider GUC, defaulting to "llvmjit".

Dado que el paquete RPM no incorpora la dependencia JIT automáticamente, como se decidió después de discusiones exhaustivas (consulte el hilo completo), debemos instalarlo manualmente:

[[email protected] ~]# dnf install postgresql11-llvmjit

Una vez que se completa la instalación, podemos probar de inmediato:

[email protected][local]:54311 test# explain analyze select count(*) from t1;
                                                               QUERY PLAN
-----------------------------------------------------------------------------------------------------------------------------------------
Finalize Aggregate  (cost=97331.43..97331.44 rows=1 width=8) (actual time=794.998..794.998 rows=1 loops=1)
   ->  Gather  (cost=97331.21..97331.42 rows=2 width=8) (actual time=794.870..803.680 rows=3 loops=1)
         Workers Planned: 2
         Workers Launched: 2
         ->  Partial Aggregate  (cost=96331.21..96331.22 rows=1 width=8) (actual time=689.124..689.125 rows=1 loops=3)
               ->  Parallel Seq Scan on t1  (cost=0.00..85914.57 rows=4166657 width=0) (actual time=0.062..385.278 rows=3333333 loops=3)
Planning Time: 0.150 ms
JIT:
   Functions: 4
   Options: Inlining true, Optimization true, Expressions true, Deforming true
   Timing: Generation 2.146 ms, Inlining 117.725 ms, Optimization 47.928 ms, Emission 69.454 ms, Total 237.252 ms
Execution Time: 803.789 ms
(12 rows)

También podemos mostrar los detalles JIT por trabajador:

[email protected][local]:54311 test# explain (analyze, verbose, buffers) select count(*) from t1;
                                                                  QUERY PLAN
------------------------------------------------------------------------------------------------------------------------------------------------
Finalize Aggregate  (cost=97331.43..97331.44 rows=1 width=8) (actual time=974.352..974.352 rows=1 loops=1)
   Output: count(*)
   Buffers: shared hit=2592 read=41656
   ->  Gather  (cost=97331.21..97331.42 rows=2 width=8) (actual time=974.166..980.942 rows=3 loops=1)
         Output: (PARTIAL count(*))
         Workers Planned: 2
         Workers Launched: 2
         JIT for worker 0:
         Functions: 2
         Options: Inlining true, Optimization true, Expressions true, Deforming true
         Timing: Generation 0.378 ms, Inlining 74.033 ms, Optimization 11.979 ms, Emission 9.470 ms, Total 95.861 ms
         JIT for worker 1:
         Functions: 2
         Options: Inlining true, Optimization true, Expressions true, Deforming true
         Timing: Generation 0.319 ms, Inlining 68.198 ms, Optimization 8.827 ms, Emission 9.580 ms, Total 86.924 ms
         Buffers: shared hit=2592 read=41656
         ->  Partial Aggregate  (cost=96331.21..96331.22 rows=1 width=8) (actual time=924.936..924.936 rows=1 loops=3)
               Output: PARTIAL count(*)
               Buffers: shared hit=2592 read=41656
               Worker 0: actual time=900.612..900.613 rows=1 loops=1
               Buffers: shared hit=668 read=11419
               Worker 1: actual time=900.763..900.763 rows=1 loops=1
               Buffers: shared hit=679 read=11608
               ->  Parallel Seq Scan on public.t1  (cost=0.00..85914.57 rows=4166657 width=0) (actual time=0.311..558.192 rows=3333333 loops=3)
                     Output: id
                     Buffers: shared hit=2592 read=41656
                     Worker 0: actual time=0.389..539.796 rows=2731662 loops=1
                     Buffers: shared hit=668 read=11419
                     Worker 1: actual time=0.082..548.518 rows=2776862 loops=1
                     Buffers: shared hit=679 read=11608
Planning Time: 0.207 ms
JIT:
   Functions: 9
   Options: Inlining true, Optimization true, Expressions true, Deforming true
   Timing: Generation 8.818 ms, Inlining 153.087 ms, Optimization 77.999 ms, Emission 64.884 ms, Total 304.787 ms
Execution Time: 989.360 ms
(36 rows)

La implementación JIT también puede aprovechar la función de ejecución de consultas en paralelo. Para ejemplificar, primero deshabilitemos la paralelización:

[email protected][local]:54311 test# set max_parallel_workers_per_gather = 0;
SET
[email protected][local]:54311 test# explain analyze select count(*) from t1;
                                                      QUERY PLAN
----------------------------------------------------------------------------------------------------------------------
Aggregate  (cost=169247.71..169247.72 rows=1 width=8) (actual time=1447.315..1447.315 rows=1 loops=1)
   ->  Seq Scan on t1  (cost=0.00..144247.77 rows=9999977 width=0) (actual time=0.064..957.563 rows=10000000 loops=1)
Planning Time: 0.053 ms
JIT:
   Functions: 2
   Options: Inlining true, Optimization true, Expressions true, Deforming true
   Timing: Generation 0.388 ms, Inlining 1.359 ms, Optimization 7.626 ms, Emission 7.963 ms, Total 17.335 ms
Execution Time: 1447.783 ms
(8 rows)

El mismo comando con consultas paralelas habilitadas se completa en la mitad de tiempo:

[email protected][local]:54311 test# reset max_parallel_workers_per_gather ;
RESET
[email protected][local]:54311 test# explain analyze select count(*) from t1;
                                                               QUERY PLAN
-----------------------------------------------------------------------------------------------------------------------------------------
Finalize Aggregate  (cost=97331.43..97331.44 rows=1 width=8) (actual time=707.126..707.126 rows=1 loops=1)
   ->  Gather  (cost=97331.21..97331.42 rows=2 width=8) (actual time=706.971..712.199 rows=3 loops=1)
         Workers Planned: 2
         Workers Launched: 2
         ->  Partial Aggregate  (cost=96331.21..96331.22 rows=1 width=8) (actual time=656.102..656.103 rows=1 loops=3)
               ->  Parallel Seq Scan on t1  (cost=0.00..85914.57 rows=4166657 width=0) (actual time=0.067..384.207 rows=3333333 loops=3)
Planning Time: 0.158 ms
JIT:
   Functions: 9
   Options: Inlining true, Optimization true, Expressions true, Deforming true
   Timing: Generation 3.709 ms, Inlining 142.150 ms, Optimization 50.983 ms, Emission 33.792 ms, Total 230.634 ms
Execution Time: 715.226 ms
(12 rows)

Encontré interesante comparar los resultados de las pruebas discutidas en esta publicación, durante las etapas iniciales de la implementación de JIT versus la versión final. Primero asegúrese de que se cumplan las condiciones de la prueba original, es decir, la base de datos debe caber en la memoria:

[email protected][local]:54311 test# \l+
postgres  | postgres | UTF8     | en_US.UTF-8 | en_US.UTF-8 |                       | 8027 kB | pg_default | default administrative connection database
template0 | postgres | UTF8     | en_US.UTF-8 | en_US.UTF-8 | =c/postgres          +| 7889 kB | pg_default | unmodifiable empty database
          |          |          |             |             | postgres=CTc/postgres |         |            |
template1 | postgres | UTF8     | en_US.UTF-8 | en_US.UTF-8 | =c/postgres          +| 7889 kB | pg_default | default template for new databases
          |          |          |             |             | postgres=CTc/postgres |         |            |
test      | postgres | UTF8     | en_US.UTF-8 | en_US.UTF-8 |                       | 2763 MB | pg_default |


[email protected][local]:54311 test# show shared_buffers ;
3GB

Time: 0.485 ms
Descargue el documento técnico hoy Administración y automatización de PostgreSQL con ClusterControlObtenga información sobre lo que necesita saber para implementar, monitorear, administrar y escalar PostgreSQLDescargar el documento técnico

Ejecute las pruebas con JIT deshabilitado:

[email protected][local]:54311 test# set jit = off;
SET
Time: 0.483 ms

[email protected][local]:54311 test# select sum(c8) from t1;
   0

Time: 1036.231 ms (00:01.036)

[email protected][local]:54311 test# select sum(c2), sum(c3), sum(c4), sum(c5),
   sum(c6), sum(c7), sum(c8) from t1;
   0 |   0 |   0 |   0 |   0 |   0 |   0

Time: 1793.502 ms (00:01.794)

A continuación, ejecute las pruebas con JIT habilitado:

[email protected][local]:54311 test# set jit = on; set jit_above_cost = 10; set
jit_inline_above_cost = 10; set jit_optimize_above_cost = 10;
SET
Time: 0.473 ms
SET
Time: 0.267 ms
SET
Time: 0.204 ms
SET
Time: 0.162 ms
[email protected][local]:54311 test# select sum(c8) from t1;
   0

Time: 795.746 ms

[email protected][local]:54311 test# select sum(c2), sum(c3), sum(c4), sum(c5),
   sum(c6), sum(c7), sum(c8) from t1;
   0 |   0 |   0 |   0 |   0 |   0 |   0

Time: 1080.446 ms (00:01.080)

¡Esa es una aceleración de alrededor del 25 % para el primer caso de prueba y del 40 % para el segundo!

Por último, es importante recordar que para declaraciones preparadas, la compilación JIT se realiza cuando la función se ejecuta por primera vez.

Conclusión

De manera predeterminada, la compilación JIT está deshabilitada y, para los sistemas basados ​​en RPM, el instalador no sugerirá la necesidad de instalar el paquete JIT proporcionando el código de bits para el proveedor predeterminado LLVM.

Al compilar a partir de fuentes, preste atención a los indicadores de compilación para evitar problemas de rendimiento, por ejemplo, si las aserciones LLVM están habilitadas.

Como se discutió en la lista de pgsql-hackers, el impacto de JIT en los costos aún no se comprende por completo, por lo que se requiere una planificación cuidadosa antes de habilitar la función en todo el clúster, ya que las consultas que de otro modo podrían beneficiarse de la compilación pueden ejecutarse más lentamente. Sin embargo, JIT se puede habilitar por consulta.

Para obtener información detallada sobre la implementación de la compilación JIT, revise los registros Git del proyecto, los Commitfests y el hilo de correo de pgsql-hackers.

¡Feliz JIT!