Continuamos publicando videos y transcripciones de los mejores informes de la conferencia
PGConf.Russia 2019 .
En la primera parte de la charla
de Ivan Frolkov , se trataba de nombres inconsistentes, restricciones, dónde es mejor concentrar la lógica: en la base de datos o en la aplicación. En esta parte, encontrará manejo de errores de análisis, acceso concurrente, operaciones no cancelables, CTE y JSON.

Te contaré una historia así. Nuestro cliente dice: “La base de datos funciona lentamente, y nuestra aplicación se dedica a servir a la población. Tememos que nos críen aquí por tenedores ”. Resultó que tenían muchos procesos
inactivos en el estado de la
transacción . La aplicación inició la transacción, no hace nada, pero la transacción no se completa. Si interactúa con algunos servicios externos, entonces, en principio, esta es una situación normal. Otra cosa es que si su estado
inactivo en el estado de la
transacción dura mucho tiempo (ya es sospechoso durante más de un minuto), entonces esto es malo porque a PostgreSQL realmente no le gustan las transacciones largas: VACUUM no podrá borrar todas las líneas que podría ver y colgar por mucho tiempo La transacción efectivamente bloquea el VACÍO. Las tablas comienzan a hincharse, los índices son cada vez menos efectivos.

En este caso, las personas no escribieron las solicitudes correctamente y recibieron productos cartesianos; estas solicitudes se completaron durante varios días. Bueno, el usuario presionará el botón, esperará el resultado y, si no hay resultado, presionará el botón nuevamente.
Pero esto no explica por qué tienen tantos procesos
inactivos en la transacción . Y aparecieron en la siguiente situación: la aplicación se arrastra a la base de datos, comienza la transacción, se arrastra a algún servicio externo, recibe un error allí, y luego todo se desmorona, imprimimos en el registro de
seguimiento de la
pila y nos calmamos al respecto. La conexión permanece abandonada, colgando e interfiriendo.
¿Qué hacer al respecto? Primero, siempre debe manejar los errores. Si le llega un error, no lo ignore. Es bueno si PostgreSQL perdió la conexión: revierte la transacción, sobrevivimos. En esto me detendré. Bueno, si hay un código que no tiene tiempo para editar en absoluto, entonces todavía tenemos el
máximo inactivo en la transacción : puede ponerlo y simplemente eliminará las transacciones inactivas.

Un caso típico de manejo de errores es: EXCEPCIÓN CUANDO OTROS SON NULOS. Una vez discutíamos con un colega sobre terminología. Dije que se traduce como "quemarlo todo con una llama azul", y él quiere decir "todo se ha desperdiciado". Si algo malo nos sucedió, entonces, incluso si todo regañaba el registro, aún es mejor que el silencio completo, como aquí.

Si no sabe qué hacer con el error, no lo intercepte. Una práctica muy común: detectaron un error, lo registraron y siguieron corriendo como si nada hubiera pasado. Si, nuevamente, realiza transacciones de dinero y tiene un error que ignoró, los resultados pueden ser impredecibles. En los años 90 podían, por ejemplo, ser llevados al bosque en el tronco. Ahora los tiempos se han vuelto más suaves, pero tampoco muy agradables.

Si hacemos la operación en el cliente, generalmente, devolvemos el valor: todo salió bien o sin éxito. Y procesamos cada error. Vi cómo las personas escribieron especialmente el código
plpgsql , donde
detectaron un error, escribieron en el registro que, dicen, sí, hubo un error y bastante grosero, insertaron el texto de su mensaje. Pero SQLSTATE no regresó. Esto siempre se hace, así que si olvidaron verificar algo, entonces comenzaron a tener problemas.
Todos, por alguna razón, temen las excepciones, tanto en
plpgsql como en otros idiomas. Y si no inventa algo propio, pero usa las características estándar del lenguaje, todo generalmente funciona bien. Especialmente este problema a menudo ocurre cuando la conexión cae. Ha caído, el proceso está
inactivo en la transacción , la base de datos se está llenando, el rendimiento está cayendo. Por cierto, tal transacción aún puede dejar bloqueos, pero por alguna razón esto no es tan común. Por lo tanto, agregue
finalmente errores al código de procesamiento y allí, limpie la conexión y devuélvala al servidor.

Además, si tiene restricciones bien definidas, puede lanzar una excepción no desde la base de datos, sino desde la aplicación mientras procesa el error. En
primavera hay una
traducción de excepción ,
en php , respectivamente,
set_exception_handler . Preste atención a las herramientas que le proporciona su marco, que aparecieron allí por una razón.
Entonces: no detecte el error con el que no sabe qué hacer; nombrar errores con cuidado y precisión; clasificar errores

Personalmente, clasifico según tales criterios: la operación puede repetirse (por ejemplo, tuvimos un punto muerto); la operación no puede repetirse, ya se ha completado; La operación no puede realizarse en principio.
Paradójicamente, desde el punto de vista de la aplicación, las situaciones en las que se produce un punto muerto, cuando se pierde la conexión y cuando nos hemos quedado sin dinero para pagar son las mismas situaciones: el controlador de errores intentará realizar la operación nuevamente después de un tiempo.

Por otro lado, lo que escriben en la aplicación, en general, no es asunto mío: estoy involucrado en la base. Solo le recomiendo que maneje los errores con cuidado, de lo contrario: inactivo en la transacción, líneas bloqueadas, bases de datos hinchadas, etc.
La mayoría de los desarrolladores creen que trabajan solo con la base de datos, y su aplicación realiza operaciones estrictamente secuenciales. Y esto es una ventaja para todos los DBMS relacionales porque, por extraño que parezca, todo funciona, por regla general, muy bien, incluso con el nivel de aislamiento estándar LEÍDO COMPROMETIDO, y no SERIALIZABLE. Al mismo tiempo, se producen situaciones en las que se pierden las actualizaciones: una carga el formulario, la otra carga el mismo formulario, una escribió y guardó, la otra guardó la anterior; los cambios se borraron. El primero llegó a jurar: "cómo es eso, escribí tanto y todo está perdido".

Según mi experiencia: una vez a la semana los viernes, dos gerentes realizaban pagos. Deberían
estaban cambiando cada dos veces, pero, sin embargo, una vez subieron al mismo tiempo y realizaron dos pagos por persona. Si tiene al menos alguna posibilidad de un error de acceso competitivo, sucederá tarde o temprano. La pregunta es cuándo.
Además, llamo su atención sobre las limitaciones. He visto repetidamente cómo intentaron proporcionar unicidad con desencadenantes. No proporcionará unicidad en la tabla con desencadenantes. Necesitará bloquear toda la mesa o hacer otros gestos complejos. Encontrarás esto tarde o temprano.

Un par de veces me encontré con una pesadilla: se llama a un servicio web externo desde la base de datos. Hubo algunas operaciones que cambian las entidades externas. Esto es malo porque una transacción puede revertirse en la base de datos, pero las operaciones en el servicio remoto no serán rechazadas.
Un punto aún más sutil es el punto muerto. Imaginemos: procesamos una transacción, llamamos a un servicio web externo, cambiamos algo, después de eso tenemos un punto muerto y retrocedemos, luego intentamos realizar la operación nuevamente, llamamos nuevamente, en buenas circunstancias, el punto muerto ocurre nuevamente, nuevamente retroceder - puede
sucede muchas veces (me encontré con un par de cientos de repeticiones). Y ahora procesa estos puntos muertos más o menos correctamente, repite las operaciones y de repente descubre que ha estado pagando una cantidad doble a alguien en dos meses.

Me reuní con servicios de pago que tenían una API pobre: "pagar tal cantidad a tal o cual usuario"; la función devuelve el resultado - pagado / no pagado. En primer lugar, hay un problema en el caso de una repetición, y en segundo lugar, no está claro qué hacer si se interrumpe la conexión. Por alguna razón, muy pocas personas se molestan en este tema tampoco.

Un ejemplo está en la diapositiva: dicha operación debe llevarse a cabo en dos etapas: como si fuera una advertencia: "haremos algo ahora"; La operación en sí.

Si interrumpimos repentinamente, nunca se sabe, apagamos la alimentación, podemos volver a realizar la operación. Si morimos en la segunda etapa, entonces, en todo el mundo, la segunda vez no lo haremos, y esto se puede desmontar manualmente. De hecho, la gran mayoría de tales operaciones normalmente funcionan por primera vez, pero estas medidas no son fabricaciones teóricas. Todo puede funcionar normalmente durante meses, y de repente el administrador comienza a ser más sabio con la red, el servicio comienza a parpadear activamente y los problemas comenzaron.

Hay 4 tipos de operaciones no cancelables en la diapositiva. El último es operaciones no idempotentes. Este es un caso muy triste. Al principio hablé de un compañero que hizo todo lo posible por los disparadores precisamente para garantizar la idempotencia de sus operaciones.

En la conferencia, la gente hablará sobre expresiones comunes de la tabla, sobre lo bueno que es. Desafortunadamente, los CTE de PostgreSQL no son gratuitos: requieren work_mem para sí mismos. Si tiene una muestra pequeña, entonces, en general, está bien. Y si de repente lo tienes grande, entonces comienzan tus problemas. Muy a menudo, las personas usan CTE como una especie de mini-vistas, para que de alguna manera pueda estructurar la aplicación. CTE tiene mucha demanda.


Puede hacer vistas temporales, pero, desafortunadamente, cada una toma una línea en pg_class, y si se usa de manera muy activa, puede haber problemas con la expansión del directorio.
En este caso, puede aconsejar hacer una vista parametrizada, o formar dinámicamente una consulta, pero, desafortunadamente, en PostgreSQL desde adentro, esto no es muy bueno.

Por lo general, se habla de JSON en tonos excelentes, pero hay una tendencia en la aplicación en JSON a impulsar cualquier cosa. En principio, todo funciona bien. Por otro lado, los datos se recuperan de JSON, aunque rápidamente, pero no tan rápido como las columnas. Peor aún, si tiene un JSON grande y se emite en TOAST. Para obtener JSON a partir de ahí, debe recogerlo de TOAST.
Si todas las columnas están en JSON, incluso se crea un índice funcional sobre ellas, entonces aún necesita sacarlo de allí. Empeora aún más con un gran volumen, cuando la base de datos es grande, cuando tiene un
escaneo de índice de mapa de bits . Luego tenemos enlaces no a cadenas, sino a toda la página, y para comprender qué tomar de la página, PostgreSQL hará
Recheck , es decir, levanta una línea de TOAST y comprueba si este valor está ahí o no, y en consecuencia ya salta o no salta. Si con columnas pequeñas esto funciona bien, entonces con JSON este es un gran problema. No hay necesidad de dejarse llevar por los JSON.
- ¿Cómo verificar cuando varios usuarios trabajan con una cadena? ¿Qué opciones hay?- En primer lugar, puede restar los valores de todas las columnas y asegurarse de que no hayan cambiado antes de mostrar la línea en el formulario. La segunda opción, más conveniente: calcular el hash
columnas, especialmente porque las columnas pueden ser grandes y gruesas. Y el hash no es tan grande.
- Usted dice que las restricciones deben llamarse buenos nombres para que el usuario pueda entender lo que está sucediendo. Pero hay un límite de 60 caracteres por nombre de restricción. Esto a menudo no es suficiente. ¿Cómo lidiar con eso?- Creo que pelear por moderación. En PostgreSQL, este es un tipo especial de longitud 64. En principio, puede volver a compilar a una longitud más larga, pero esto no es muy bueno.
- En el informe, nos intrigó con el hecho de que necesitamos hacer algo con los archivos. ¿Qué mecanismo se considera el más correcto para el archivo obsoleto?- Como dije al principio, con la debida diligencia todo funciona. Qué método es más conveniente para usted, así que úselo.
Tiempo: la parte 2 del informe comienza a las 25:16- Hay un cierto procedimiento que varios usuarios llaman en paralelo. Cómo limitar la ejecución paralela de este procedimiento, es decir, construir todo
usuarios en la cola para que hasta que uno termine el procedimiento, el siguiente no pueda comenzar a usarlo?- Precisamente el procedimiento? ¿O es suficiente transacción?
- Es el procedimiento que se llama en alguna transacción.- Puedes poner un candado en el objeto. Sería difícil si tuviera una afección, por ejemplo, no más de 3 al mismo tiempo. Pero esto es realizable. Usualmente uso bloqueos transaccionales, pero también son posibles los no transaccionales.
- Todavía me gustaría volver una vez más a los datos de archivo. Hablaste de
opciones de almacenamiento de archivos para que los datos de la aplicación también estén disponibles. Se me ocurrió simplemente crear una base de datos de archivo separada. ¿Qué otras opciones hay?- Sí, puedes hacer una base de datos de archivo. Puede escribir una función y envolverla en una vista. En una función, puede hacer lo que sea necesario: puede ir a la base de datos de archivo, puede recoger algunos archivos del disco, puede ir a un servicio web externo, puede combinar todo esto, puede generar algunos datos aleatorios usted mismo - elección limitado solo por la imaginación.
- A la pregunta sobre datos de archivo: puede usar particiones: nuevos chips de la 11ª versión, cuando hacemos la partición de toda la tabla, y luego simplemente detallamos la partición y la dejamos como un archivo. También se puede acceder."Por supuesto, por qué no". Doy paso al siguiente orador.