Rendimiento de la función

46

Viniendo de un entorno MySQL, donde el rendimiento del procedimiento almacenado (artículo anterior) y la usabilidad son cuestionables, estoy evaluando PostgreSQL para un nuevo producto para mi empresa.

Una de las cosas que me gustaría hacer es mover parte de la lógica de la aplicación a los procedimientos almacenados, por lo que estoy pidiendo DO y NO (mejores prácticas) sobre el uso de funciones en PostgreSQL (9.0), específicamente con respecto a las dificultades de rendimiento.

Derek Downey
fuente
¿Quieres decir que no quieres que las respuestas mencionen nada que no esté relacionado con el rendimiento?
Jack Douglas el
Chris Travers bloguea mucho sobre las ventajas de usar procedimientos almacenados, por ejemplo, aquí: ledgersmbdev.blogspot.de/2012/07/… y aquí: ledgersmbdev.blogspot.de/2012/07/… solo hojee su blog, hay un Muchos artículos interesantes sobre este tema.
a_horse_with_no_name

Respuestas:

51

Estrictamente hablando, el término "procedimientos almacenados" apunta a procedimientos SQL en Postgres, introducido con Postgres 11. Relacionado:

También hay funciones que hacen casi, pero no exactamente lo mismo, y que han estado allí desde el principio.

Las funciones con LANGUAGE sqlson básicamente archivos por lotes con comandos SQL simples en un contenedor de funciones (y, por lo tanto, atómico, siempre se ejecutan dentro de una sola transacción) que aceptan parámetros. Todas las declaraciones en una función SQL se planifican a la vez , lo que es sutilmente diferente de ejecutar una declaración después de la otra y puede afectar el orden en que se toman los bloqueos.

Para cualquier otra cosa, el lenguaje más maduro es PL / pgSQL ( LANGUAGE plpgsql). Funciona bien y se ha mejorado con cada lanzamiento en la última década, pero sirve mejor como pegamento para los comandos SQL. No está destinado a cálculos pesados ​​(que no sean con comandos SQL).

Las funciones PL / pgSQL ejecutan consultas como declaraciones preparadas . Reutilizar los planes de consulta en caché reduce algunos gastos generales de planificación y los hace un poco más rápidos que las declaraciones SQL equivalentes, lo que puede ser un efecto notable dependiendo de las circunstancias. También puede tener efectos secundarios como en esta pregunta relacionada:

Esto conlleva las ventajas y desventajas de las declaraciones preparadas, como se explica en el manual . Para consultas en tablas de datos con distribución irregular y parámetros variables SQL dinámico con EXECUTEpuede funcionar mejor cuando la ganancia de un plan de ejecución optimizado para el parámetro determinado (s) es mayor que el costo de la replanificación.

Dado que los planes de ejecución genéricos de Postgres 9.2 todavía se almacenan en caché para la sesión pero, citando el manual :

Esto ocurre inmediatamente para declaraciones preparadas sin parámetros; de lo contrario, ocurre solo después de que cinco o más ejecuciones producen planes cuyo costo promedio estimado (incluida la sobrecarga de planificación) es más costoso que el costo estimado del plan genérico.

Obtenemos lo mejor de ambos mundos la mayor parte del tiempo (menos algunos gastos generales adicionales) sin (ab) usar EXECUTE. Detalles en Novedades de PostgreSQL 9.2 de PostgreSQL Wiki .

Postgres 12 presenta la variable de servidorplan_cache_mode adicional para forzar planes genéricos o personalizados. Para casos especiales, usar con cuidado.

Puede ganar en grande con las funciones del lado del servidor que evitan los viajes de ida y vuelta adicionales al servidor de la base de datos desde su aplicación. Haga que el servidor ejecute tanto como sea posible a la vez y solo devuelva un resultado bien definido.

Evite anidar funciones complejas, especialmente funciones de tabla ( RETURNING SETOF recordo TABLE (...)). Las funciones son cajas negras que se presentan como barreras de optimización para el planificador de consultas. Se optimizan por separado, no en el contexto de la consulta externa, lo que simplifica la planificación, pero puede dar como resultado planes menos que perfectos. Además, el costo y el tamaño del resultado de las funciones no se pueden predecir de manera confiable.

La excepción a esta regla son las funciones simples de SQL ( LANGUAGE sql), que pueden " integrarse " , si se cumplen algunas condiciones previas . Lea más sobre cómo funciona el planificador de consultas en esta presentación de Neil Conway (material avanzado).

En PostgreSQL, una función siempre se ejecuta automáticamente dentro de una sola transacción . Todo tiene éxito o nada. Si ocurre una excepción, todo se revierte. Pero hay un manejo de errores ...

Esa es también la razón por la cual las funciones no son exactamente "procedimientos almacenados" (aunque ese término se usa a veces, de manera engañosa). Algunos comandos gusta VACUUM, CREATE INDEX CONCURRENTLYo CREATE DATABASEno se puede ejecutar dentro de un bloque de transacción, por lo que no están permitidos en funciones. (Ni en los procedimientos SQL, todavía, a partir de Postgres 11. Eso podría agregarse más adelante).

He escrito miles de funciones plpgsql a lo largo de los años.

Erwin Brandstetter
fuente
2
@nhahtdh: "transacción automática" no es un término técnico. Era simplemente una forma poco elegante de decir ... lo que dice ahora después de mi aclaración. No es una transacción autónoma en absoluto. "autónomo" resulta ser una palabra similar.
Erwin Brandstetter
44
Sus respuestas se compilaron desde aquí y SO podría ser un épico manual de mejores prácticas de PostGreSQL.
Davos
10

Algunas DO:

  • Utilice SQL como lenguaje de funciones cuando sea posible, ya que PG puede alinear las declaraciones
  • Use INMUTABLE / STABLE / VOLATILE correctamente, ya que PG puede almacenar en caché los resultados si es inmutable o estable
  • Utilice STRICT correctamente, ya que PG puede devolver nulo si alguna entrada es nula en lugar de ejecutar la función
  • Considere PL / V8 cuando no pueda usar SQL como lenguaje de funciones. Es más rápido que PL / pgSQL en algunas pruebas no científicas que ejecuté
  • Use ESCUCHAR / NOTIFICAR para procesos de ejecución más larga que pueden ocurrir fuera de transacción
  • Considere el uso de funciones para implementar la paginación, ya que la paginación basada en claves puede ser más rápida que la paginación basada en LIMIT
  • Asegúrese de probar sus funciones unitarias
Neil McGuigan
fuente
Es la primera vez que veo la afirmación de que PL / V8 es más rápido que PL / pgSQL. ¿Tiene alguna cifra (publicada) para respaldar eso?
a_horse_with_no_name
@a_horse_with_no_name no, no lo hago. Como dije, hice algunas pruebas no científicas. Eran principalmente lógicos, no acceso a datos. Intentaré hacer algunas pruebas repetibles sobre Navidad y volver a publicar aquí.
Neil McGuigan
@a_horse_with_no_name aquí hay un ejemplo rápido y sucio para FizzBuzz plv8 vs plpgsql: blog.databasepatterns.com/2014/08/plv8-vs-plpgsql.html
Neil McGuigan
8

En términos generales, mover la lógica de la aplicación a la base de datos significará que es más rápido; después de todo, se ejecutará más cerca de los datos.

Creo (pero no estoy 100% seguro) de que las funciones del lenguaje SQL son más rápidas que las que usan otros idiomas porque no requieren cambio de contexto. La desventaja es que no se permite ninguna lógica de procedimiento.

PL / pgSQL es el más maduro y completo de los lenguajes integrados, pero para el rendimiento, se puede usar C (aunque solo beneficiará a funciones computacionalmente intensivas)

Jack Douglas
fuente
7

Puede hacer cosas muy interesantes utilizando funciones definidas por el usuario (UDF) en postgresql. Por ejemplo, hay docenas de idiomas posibles que puede usar. Los pl / sql y pl / pgsql integrados son capaces y confiables y utilizan un método de sandbox para evitar que los usuarios hagan algo demasiado peligroso. Los UDF escritos en C le brindan lo último en potencia y rendimiento, ya que se ejecutan en el mismo contexto que la base de datos. Sin embargo, es como jugar con fuego, porque incluso los pequeños errores pueden causar grandes problemas, con fallas en el backends o la corrupción de los datos. Los personalizados idiomas pl, como pl / R, pl / ruby, pl / perl, etc., le brindan la capacidad de escribir capas de bases de datos y aplicaciones en los mismos idiomas. Esto puede ser útil, ya que significa que no tiene que enseñarle a un programador perl java o pl / pgsql, etc. para escribir un UDF.

Por último, está el lenguaje pl / proxy . Este lenguaje UDF le permite ejecutar su aplicación en docenas o más servidores back-end postgresql para fines de escala. Fue desarrollado por la gente buena de Skype y básicamente permite la solución de escalado horizontal de un hombre pobre. Es sorprendentemente fácil escribir también.

Ahora, en cuanto al problema de rendimiento. Esta es un área gris. ¿Estás escribiendo una aplicación para una persona? ¿O por 1,000? o por 10,000,000? La forma en que crea su aplicación y utiliza UDF dependerá MUCHO de cómo está tratando de escalar. Si está escribiendo para miles y miles de usuarios, entonces lo principal que desea hacer es reducir la carga en la base de datos tanto como sea posible. Los UDF que reducen la cantidad de datos que se mueven y regresan a la base de datos ayudarán a reducir la carga de E / S. Sin embargo, si comienzan a aumentar la carga de la CPU, pueden ser un problema. En términos generales, reducir la carga de E / S es la primera prioridad, y asegurarse de que las UDF sean eficientes para no sobrecargar sus CPU es el siguiente.

Scott Marlowe
fuente