Ejecuto un sitio de Django que tiene una vista de tipo ModelForm simple que genera errores de cursor. En los últimos dos días, esta vista se PUBLICÓ un par de cientos de veces y aproximadamente el 8 % de las veces generó un error. SOLO tengo este problema con esta vista, aunque tengo otra que es muy similar. Eso es lo frustrante es que no he descubierto qué tiene de especial. Empecé a ver estos errores después de actualizar a Django 2.1/2, pero creo que pueden haber existido previamente, pero no se vieron.
Rastreo completo de la pila aquí: https://gist.github.com/jplehmann/ad8849572e569991bc26da87c81bb8f4
Algunos ejemplos de registro de consulta [error] (internal users edit) OR (psycopg2 errors cursor)
con nombres de usuario redactados, para mostrar el tiempo:
Jun 04 12:42:12 ballprice app/web.1: [ERROR] Internal Server Error: /users/a/edit [log:228] Jun 04 12:42:12 ballprice app/web.1: psycopg2.errors.InvalidCursorName: cursor "_django_curs_140401754175232_2" does not exist Jun 04 12:42:12 ballprice app/web.1: psycopg2.errors.InvalidCursorName: cursor "_django_curs_140401754175232_2" does not exist Jun 04 12:42:27 ballprice app/web.1: [ERROR] Internal Server Error: /users/a/edit [log:228] Jun 04 12:42:27 ballprice app/web.1: psycopg2.errors.InvalidCursorName: cursor "_django_curs_140401754175232_3" does not exist Jun 04 12:57:51 ballprice app/web.3: [ERROR] Internal Server Error: /users/a/edit [log:228] Jun 04 12:57:51 ballprice app/web.3: psycopg2.errors.DuplicateCursor: cursor "_django_curs_140092205262592_2" already exists Jun 04 12:57:51 ballprice app/web.3: psycopg2.errors.InvalidCursorName: cursor "_django_curs_140092205262592_2" does not exist Jun 04 13:10:50 ballprice app/web.3: [ERROR] Internal Server Error: /users/b/edit [log:228] Jun 04 13:10:50 ballprice app/web.3: psycopg2.errors.DuplicateCursor: cursor "_django_curs_140092205262592_2" already exists Jun 04 15:19:36 ballprice app/web.9: [ERROR] Internal Server Error: /users/c/edit [log:228] Jun 04 15:19:36 ballprice app/web.9: psycopg2.errors.InvalidCursorName: cursor "_django_curs_140515167295232_1" does not exist Jun 04 17:28:22 ballprice app/web.5: [ERROR] Internal Server Error: /users/d/edit [log:228] Jun 04 17:28:22 ballprice app/web.5: psycopg2.errors.InvalidCursorName: cursor "_django_curs_140085445728000_2" does not exist Jun 04 17:28:22 ballprice app/web.5: psycopg2.errors.InvalidCursorName: cursor "_django_curs_140085445728000_2" does not exist Jun 04 22:49:15 ballprice app/web.1: [ERROR] Internal Server Error: /users/e/edit [log:228] Jun 04 22:49:15 ballprice app/web.1: psycopg2.errors.InvalidCursorName: cursor "_django_curs_139902341289728_2" does not exist Jun 04 22:49:15 ballprice app/web.1: psycopg2.errors.InvalidCursorName: cursor "_django_curs_139902341289728_2" does not exist Jun 04 23:43:26 ballprice app/web.1: [ERROR] Internal Server Error: /users/f/edit [log:228] Jun 04 23:43:26 ballprice app/web.1: psycopg2.errors.DuplicateCursor: cursor "_django_curs_139902341289728_2" already exists Jun 05 02:49:22 ballprice app/web.1: [ERROR] Internal Server Error: /users/g/edit [log:228] Jun 05 02:49:22 ballprice app/web.1: psycopg2.errors.InvalidCursorName: cursor "_django_curs_140092373694208_1" does not exist Jun 05 02:49:22 ballprice app/web.1: psycopg2.errors.InvalidCursorName: cursor "_django_curs_140092373694208_1" does not exist Jun 05 02:49:41 ballprice app/web.1: [ERROR] Internal Server Error: /users/g/edit [log:228] Jun 05 02:49:41 ballprice app/web.1: psycopg2.errors.DuplicateCursor: cursor "_django_curs_140092373694208_1" already exists
Sin embargo, no puedo reproducir este error. Un usuario con el que hablé dijo que lo intentó y se guardó la tercera vez.
Puede ver que los cursores con nombre se reutilizan bastante, con muchos minutos de diferencia, lo que solo puedo suponer que es normal.
Versiones:
Que podria causar esto?
Usamos PG bouncer, y el consejo para deshabilitar los cursores del lado del servidor fue sólido y parece haber funcionado.
Por lo general, este error se agrega cuando los modelos y la base de datos no son compatibles. Como si cambiaste un modelo agregando un campo pero no lo migraste.
Asegúrese de verificar que todas las aplicaciones de su sitio estén en su INSTALLED_APP. Este error puede deberse a que las migraciones no se aplican en una nueva aplicación no declarada. Luego
python manage.py makemigrations && python manage.py migrate
¿Está utilizando pgBouncer o algún otro mecanismo de agrupación? Por lo general, encontré este tipo de problemas cuando se utilizó alguna forma de agrupación de conexiones para disminuir la carga de conexión en la base de datos (lo cual está perfectamente bien y es recomendable, si tiene muchos clientes).
https://docs.djangoproject.com/en/3.0/ref/databases/#transaction-pooling-and-server-side-cursors
El uso de un agrupador de conexiones en el modo de agrupación de transacciones (por ejemplo, PgBouncer) requiere deshabilitar los cursores del lado del servidor para esa conexión.
Los cursores del lado del servidor son locales para una conexión y permanecen abiertos al final de una transacción cuando AUTOCOMMIT es True. Una transacción posterior puede intentar obtener más resultados de un cursor del lado del servidor. En el modo de agrupación de transacciones, no hay garantía de que las transacciones posteriores utilicen la misma conexión. Si se usa una conexión diferente, se genera un error cuando la transacción hace referencia al cursor del lado del servidor, porque los cursores del lado del servidor solo son accesibles en la conexión en la que se crearon.
Una solución es deshabilitar los cursores del lado del servidor para una conexión en BASES DE
DATABASES
configurandoDISABLE_SERVER_SIDE_CURSORS
en True.Para beneficiarse de los cursores del lado del servidor en el modo de agrupación de transacciones, puede configurar otra conexión a la base de datos para realizar consultas que usan cursores del lado del servidor. Esta conexión debe ser directamente a la base de datos o a un agrupador de conexiones en modo de agrupación de sesiones.
Otra opción es envolver cada QuerySet usando cursores del lado del servidor en un bloque atomic(), porque deshabilita la confirmación automática durante la transacción. De esta manera, el cursor del lado del servidor solo vivirá mientras dure la transacción.
Entonces, si esto se aplica a su conexión, sus opciones son:
deshabilitar cursores
DATABASES = { 'default': { 'ENGINE': 'django.db.backends.postgresql', 'DISABLE_SERVER_SIDE_CURSORS': True, } }
envolver en transacción
(no se garantiza que funcione, depende de la configuración de la agrupación)
with transaction.atomic(): qs = YourModel.objects.filter() for values in qs.values('id', 'x').iterator(): pass
conexión adicional
También puede usar una conexión directa adicional a la base de datos si necesita cursores del lado del servidor y luego usar la conexión directa para esas consultas.
YourModel.objects.using('different_db_connection_id').filter().iterator()