Archivo por meses: agosto 2015

APEX y ORA-02020: too many database links in use

Introducción

El entorno APEX en el que se produjo el error está compuesto por varios espacios de trabajo (o workspaces) independientes entre sí. Cada espacio de trabajo cuenta con un enlace de base de datos para conectarlo a la instancia de la que extrae los datos de trabajo. De esta forma, tanto los desarrolladores APEX como los usuarios finales de las aplicaciones web solo tienen acceso a los datos a través de la instancia en la que se ejecuta APEX y se puede controlar de forma centralizada la seguridad en lo relativo al acceso a los datos.

Esta arquitectura nos permite interponer una capa entre el acceso web de los usuarios finales y las bases de datos de producción, además de descargar a estas bases de datos de la tarea de generar el contenido web y controlar la navegación de los usuarios. En definitiva, separamos por capas cada tarea:

  • Navegación: servidor web (cualquiera de los soportados por APEX).
  • Generación de contenidos HTML: instancia APEX.
  • Repositorios de datos: instanicas de producción (bases de datos remotas).

Arquitectura APEX con acceso remoto a los datos

Error ORA-02020: too many database links in use

A medida que los usuarios accedían a los distintos espacios de trabajo iba aumentando el número de conexiones que se establecían entre la instancia de base de datos que aloja APEX y las distintas instancias a las que se conecta para obtener los datos. Esto no debía constituir ningún problema ya que se utlizaba un servidor web con un pool de conexiones para gestionar las peticiones HTML y las conexiones a través de data base links para acceder a los datos. El entorno debería responder bien ante la concurrencia de conexiones propia de los entornos web. Pero algo no iba bien. Empecé a recibir correos de los usuarios quejándose de que les aparecía el siguiente error en sus navegadores:

ORA-20001: get_dbms_sql_cursor error ORA-02020: too many database links in use

Nos pusimos a investigar las causas el problema.

Primer intento: aumentar el número de conexiones permitido

Lo primero fue acudir a la documentación oficial sobre errores Oracle. Y en ella se puede leer lo siguiente:

ORA-02020: too many database links in use
Cause: The current session has exceeded the INIT.ORA open_links maximum.
Action: Increase the open_links limit, or free up some open links by committing or rolling back the transaction and canceling open cursors that reference remote databases.

Estaba claro que se abrían muchos enlaces de base de datos. Nuestra arquitectura lo propiciaba. Pero por el mismo motivo, metiante el pool de conexión, el sistema debería compartir conexiones y nos sorprendía que se acabara produciendo este error. La primera medida fue seguir la recomendación y aumentar el número de conexiones permitidas que define el parámetro open_links. Durante unos días dejamos de recibir correos de los usuarios con la incidencia, pero la tregua no duró mucho. Con el paso de los días y el aumento en el uso de las aplicaciones APEX instaladas, los correos con el error por exceso de conexiones abiertas volvieron a llegar.

Estaba claro que aumentar el límite de conexiones no era la solución. Siempre se puede poner un número máximo de conexiones desorbitado, y no preocuparse más. Pero no es una solución muy adecuada si queremos crear un entorno robusto y seguro. La segunda alternativa que propone la documentación oficial del error tampoco nos daba muchas pistas. Nuestras conexiones desde APEX a las instancias remotas eran casi en su totalidad conexiones para consulta de datos: vistas locales que lanzaban selects sobre el modelo de datos remoto. No abríamos transacciones remotas y, por lo tanto, descartamos la necesidad de incluir sentencias commit o rollback.

Segundo intento: apex_util.close_open_db_links

Así que seguimos investigando. Esta vez acudimos a My Oracle Support (MOS), antiguo Metalink, y allí encotramos la nota Doc ID 1950408.1 de la que resumo el apartado que utilizamos para aplicarlo a nuestro entorno:

Oracle REST Data Services (ORDS) / APEX Listener

1. Add the following entries to the defaults.xml associated with your ORDS / APEX Listener configuration.

<entry key="procedure.postProcess">apex_util.close_open_db_links</entry>
<entry key="procedure.preProcess">apex_util.close_open_db_links</entry>

2. Restart ORDS / the APEX Listener where it is deployed, so the new entries will be recognized.

Parecía algo sencillo de aplicar, que atacaba exactamente el problema que teníamos y que lo hacía mediante un paquete PL/SQL oficial de APEX. Nos pusimos manos a la obra: incluimos las entradas en el fichero de configuración y realizamos una primera batería de pruebas. Esta vez parecía que todo iba a funcionar bien. Pero pronto observamos que en algunos apartados de las aplicaciones que íbamos probando aparecían nuevos errores aunque, esta vez, no se mostraban en pantalla. Tras un examen del log del servidor web encontramos las siguientes trazas:

oracle.dbtools.rt.web.WebErrorResponse internalError
ORA-02080: database link is in use
[...]
java.sql.SQLSyntaxErrorException: ORA-02080: database link is in use
[...]

El proceso apex_util estaba intentando cerrar conexiones cuando todavía se encontraban en uso. Como ya hemos comentado, las aplicaciones que ejecuta nuestra instancia APEX apenas generan inserciones, actualizaciones o borrado de datos en las instancias remotas. Por lo tanto, no deberían quedarse transacciones abiertas. ¡Qué estaba pasando!

Tercer y último intento: una modificación del MOS ID 1950408.1

Volvimos a buscar en MOS y Google para intentar acotar el origen del problema. Las vistas que utilizan nuestras aplicaciones APEX para acceder a las tablas de las instancias remotas, a pesar de ser una select, parece que si generan una transacción y esta impide el cierre de la conexión si no ejecutamos previamente un commit o un rollback. Es un efecto de utilizar data base links: la instancia remota asigna un bloque de undo a la conexión abierta y no se libera hasta recibir un comando de finalización de transacción.

La alternativa de invocar el cierre de los enlaces de base de datos desde un procedimiento dentro de cada aplicación APEX (un procedimiento de aplicación o un procedimiento que se ejecutara en las páginas que abrieran conexiones remotas) había sido descartada desde el principio porque nos obligaba a modificar el código de cada una de las aplicaciones y, además, identificar el nombre de cada database link en los distintos entornos. Resultaba poco práctico y difícil de mantener. Pero ante el problema de las transacciones abiertas y los nuevos errores que generaba la segunda solución que aplicamos, decidimos montar un escenario de test. Y funcionó a la perfección.

De todas formas, el cierre de los enlaces desde el interior de cada aplicación APEX seguía sin convencernos. Así que decidimos hacer una prueba más. Desactivamos el proceso de aplicación con el cierre de conexiones remotas que habíamos creado en el escenario de pruebas. Y volvimos a activar el parámetro en el fichero defaults.xml del servidor web. Pero en esta ocasión solo lo activamos para el preprocesado:

<entry key="procedure.preProcess">apex_util.close_open_db_links</entry>

Con esta configuración por fin logramos solucionar el problema de los database links abiertos sin tener que modificar cada una de las aplicaciones APEX.