• Este debate está vacío.
Viendo 1 entrada (de un total de 1)
  • Autor
    Entradas
  • #31381
    Javier Ader
    Participante

    Buenas. Mirando un poco el código de carga de ventanas, como puse en otro mensaje en este foro, se crean 147 trheads concurrentes para cargar los MLookups (MLookup.MLoader), cada uno de los cuales ejecuta una sentencia sql contra el server. Ahora, bien, intente varias cosas para mejorar el tiempo de carga de las ventanas (en mi maquina es muy malo para ciertas ventanas) que no me dieron tan buenos resultados como esperaba y empecé a darme cuenta que por más que une use muchos threads, los puntos de contención son las conexiones al servidor; y estas estan en un número fijo (3 actualmente para los acceso de solo lectura). Esto es: si uno crea 100 thread de manera concurrente que acceden al servidor, basicamente solo 3 van hacerlo de manera simultanea (todos los demas van a estar “encolados” atras de estos 3). Ok, la idea es buena de todas maneras (en un mundo ideal uno haria los accesos 3 veces mas rapidos que si todos los thread usasen una misma conexión , o si directamente no hubiese threads encargados de la carga en ‘background’). Mas detalles de como esto ocurre a nivel de driver JDBC sobre el final.
    Ahora bien, esto significa que las conexiones en el pool de DB son el objeto cuyo acceso se debería planificar para intentar ‘alcanzar’ el ideal, pero actualmente el manejo del pool en DB es básico (retorna las conexiones ‘en ronda’ sin importar cuales de las conexiones esta en uso y cuales no….).
    Tengo varias ideas para mejorar esto, y otorgar las conexiones de un manera un poco más ‘juiciosa’.
    1) Contadores de uso: por cada conexión en el pool se mantiene un contador de uso; cada vez que se le solicita a DB una conexión (getConnectionRO), retorna la conexión que tiene menor contador (la primera que encuentra si hay más de una con el mismo contador). Justo antes de que CPreparedStatement o CStatement vaya a ejecutar el execute.Query sobre el PreparedStatmet “wrapeado”, le solicita a DB que incremente el contador para la conexion asociada al PreparedStatement; justo despues haberlo usado , le solicita a la DB que decremente el mismo contador.
    2) 1 + Contadores estimados: el punto anterior tiene la falencia que el incremento del contador es siempre en 1 (este contador puede pensarse como una “burda” estimación del uso que se va a dar al conexión). Asi por ej, el lookup que carga la organizaciones ( muy pero muy liviano) pasa a tener el mismo “peso” que un lookup que carga digamos los productos (potencialmente muuchos….); o incluso va a tener el mismo peso que la PreparedStatement que carga todos los productos en si. Esto obviamente no es lo mejor (por ej, hay 3 conexiones; todas con contador 1, la primera cargado TODA la tabla de productos; las otras dos están cargando el lookup de clientes y organizaciones; en este caso, el algoritmo anterior retornaría la primer conexión, aun cuando es muy probable que siga en uso por cierto tiempo, mientras que las otras dos muy probable terminen inmediatamente….). Así que propongo agregar una variable “costo estimado” a CPreparedStatement y a CStatement (este último no tiene mayor importancia ya que no se usa mucho en Libertya), y que se puede setear antes de llamar a executeQuery; este costo estimado, y no simplemente 1 se usaria como valor de incremento/decremento. Como agregar esta estimación dentro del código no es un tema menor pero se puede ir agregando sin hacer muchos disturbios y guardando compatibilidad con el código que no se modifique (e.d las modificaciones se pueden ir haciendo de a poco). Por ej, el las queries que cargan las tablas completas debería tener casi por definción un costo estimado que sea siempre mayor que el de los lookups. Los MLookups por otra parte tienen un costo variable dependiendo que a que tabla “indexan” y con que “filtros”; pero por ej, se podría realizar alguna forma de estimación en base a la cantidades de filas previas traidas para ese MLookup sobre una tabla data; por ej, una tabla hash estática en MLookup indexada por query que ejecuta el MLookup.Loader usa el valor de esta hash para ejecutar esta query y luego actualiza el mismo para guarde un promedio “ponderado” (no se si el termino correcto….) de los acceso previos y el acceso recientemente llevado a cabo (esta tabla hash probablemente se pueda incluso precargar desde la base de datos, lo cual permitiría un “tunneo” inicial por parte del administrador; pero claro, tendría que saber exactamente que query es); esto es, si no me equivoco “proximo costo estimado” = (“costo estimado actual” + “costo actualmetne visto” ) / “cantidad de veces que se ejecuto la query dada”.
    De cualquier manera esto lo pense medio por arriba y quiza haya otras formas de hacerlo.

    Finalmente pongo unos párrafos que entran un poco más en detalles sobre el concepto de “Serializablidad a nivel de conexiones”:

    Serializablidad a nivel de conexiones

    Al parecer si dos threads ejecutan dos PreparedStatemet.executeQuery() sobre la misma Connection uno de los dos se va a bloquear completamente antes de que te el otro termine (e.d, conceptualmente, la conexión es un objeto
    de acceso excluyente). La razón a nivel de diseño es que el protocolo de comunicación V3 subyancente aun cuando podría permitir múltiples queries sobre una misma conexión física no lo alienta en lo más mínimo (http://developer.postgresql.org/pgdocs/postgres/protocol-flow.html). Esto se ve reflejado en la implementación del driver JDBC para Postgres; acá hay una stack trace que se dispara por la típicas sentencias: PreparedStatement p = DB.prepareStatement(….);

    ResultSet r = p.executeQuery();

    Code:
    Thread [main] (Evaluating)
    QueryExecutorImpl.processResults(ResultHandler, int) line: 1163
    QueryExecutorImpl.execute(Query, ParameterList, ResultHandler, int, int, int) line: 188
    Jdbc3PreparedStatement(AbstractJdbc2Statement).execute(Query, ParameterList, int) line: 437
    Jdbc3PreparedStatement(AbstractJdbc2Statement).executeWithFlags(int) line: 353
    Jdbc3PreparedStatement(AbstractJdbc2Statement).executeQuery() line: 257
    NativeMethodAccessorImpl.invoke0(Method, Object, Object[]) line: not available [native method]
    NativeMethodAccessorImpl.invoke(Object, Object[]) line: not available
    DelegatingMethodAccessorImpl.invoke(Object, Object[]) line: not available
    Method.invoke(Object, Object…) line: not available
    PooledConnectionImpl$StatementHandler.invoke(Object, Method, Object[]) line: 467
    $Proxy1.executeQuery() line: not available
    CPreparedStatement.executeQuery() line: 141
    Msg.getElement(String, String, boolean) line: 611
    Msg.translate(String, boolean, String) line: 786
    Msg.translate(Properties, String) line: 836
    StatusBar.jbInit() line: 141
    StatusBar.(boolean) line: 66
    StatusBar.
    () line: 52
    ALogin.
    (Frame) line: 264
    AMenu.initSystem(Splash) line: 221
    AMenu.
    () line: 103
    NativeConstructorAccessorImpl.newInstance0(Constructor, Object[]) line: not available [native method]
    NativeConstructorAccessorImpl.newInstance(Object[]) line: not available
    DelegatingConstructorAccessorImpl.newInstance(Object[]) line: not available
    Constructor.newInstance(Object…) line: not available
    Class
    .newInstance0() line: not available
    Class
    .newInstance() line: not available
    OpenXpertya.main(String[]) line: 638

    El tema es que el metodo QueryExecutorImpl.execute(…)(paquete org.postgresql.core.v3)es marcado como sincronizado (synchronized) y el objeto que lo implementa es compartido por todos los PreparedStatemet (también los Statement; en realidad casi cualquier cosa que use la conexión…). Finalmente
    el primer método que muestra la stack (processResults) es el que recoje el resultado de la query leyendo del strema TCP; y no finaliza (cuando lo hace de manera sin ocurrencia de errores) hasta que el server le envía el mensaje “Ready For Query”, el cual es solo enviado despues que el server rertona todos los datos generados por la query. (http://cvs.pgfoundry.org/cgi-bin/cvsweb.cgi/jdbc/pgjdbc/org/postgresql/core/v3/QueryExecutorImpl.java?rev=1.48&content-type=text/x-cvsweb-markup )
    Esto ultimo significa que incluso no se puede enviar al server la solicitud de una nueva sentencia mientras este esta retornando lo resultados de la anterior (todo esto lleva a una acceso tipo Solicitud, Respueta completa, Solitud, Respuesta Completa, etc…)

    Por las dudas, cree el siguiente test, que debe ser corrindo setenando previamente a 1 la variable DB.s_conCacheSize a 1 (para que se vea claramente el bloqueo a nivel de conexión ya que en este caso habrá solo una en la que se ejecutarán todos las queries):

    http://www.eltita.com.ar/libertya/TestConexionesCompThreads.java.html

    Suspendiendo desde eclipse el primer thread creado (el primero que de a lista que comienza con el nombre Thread-xxx), se ve esta stack

    Code:
    Thread [Thread-10] (Suspended)
    SocketInputStream.socketRead0(FileDescriptor, byte[], int, int, int) line: not available [native method]
    SocketInputStream.read(byte[], int, int) line: not available
    BufferedInputStream.fill() line: not available
    BufferedInputStream.read() line: not available
    PGStream.ReceiveIntegerR(int) line: 275
    PGStream.ReceiveTupleV3() line: 369
    QueryExecutorImpl.processResults(ResultHandler, int) line: 1283
    QueryExecutorImpl.execute(Query, ParameterList, ResultHandler, int, int, int) line: 188
    Jdbc3PreparedStatement(AbstractJdbc2Statement).execute(Query, ParameterList, int) line: 437
    Jdbc3PreparedStatement(AbstractJdbc2Statement).executeWithFlags(int) line: 353
    Jdbc3PreparedStatement(AbstractJdbc2Statement).executeQuery() line: 257
    NativeMethodAccessorImpl.invoke0(Method, Object, Object[]) line: not available [native method]
    NativeMethodAccessorImpl.invoke(Object, Object[]) line: not available
    DelegatingMethodAccessorImpl.invoke(Object, Object[]) line: not available
    Method.invoke(Object, Object…) line: not available
    PooledConnectionImpl$StatementHandler.invoke(Object, Method, Object[]) line: 467
    $Proxy1.executeQuery() line: not available
    CPreparedStatement.executeQuery() line: 141
    TestConexionesCompThreads$ThreadQuery.run() line: 81

    una vez suspendido este thread, uno se puede ir a dormir si quiere y el thread t2 no va a terminar; eclipse lo muestra como Running, pero si uno lo suspende y ve la stack es claro que esta detenido en QueryExecutorImpl.execute(…):

    Code:
    Thread [Thread-11] (Suspended)
    QueryExecutorImpl.execute(Query, ParameterList, ResultHandler, int, int, int) line: 166
    Jdbc3PreparedStatement(AbstractJdbc2Statement).execute(Query, ParameterList, int) line: 437
    Jdbc3PreparedStatement(AbstractJdbc2Statement).executeWithFlags(int) line: 353
    Jdbc3PreparedStatement(AbstractJdbc2Statement).executeQuery() line: 257
    NativeMethodAccessorImpl.invoke0(Method, Object, Object[]) line: not available [native method]
    NativeMethodAccessorImpl.invoke(Object, Object[]) line: not available
    DelegatingMethodAccessorImpl.invoke(Object, Object[]) line: not available
    Method.invoke(Object, Object…) line: not available
    PooledConnectionImpl$StatementHandler.invoke(Object, Method, Object[]) line: 467
    $Proxy1.executeQuery() line: not available
    CPreparedStatement.executeQuery() line: 141
    TestConexionesCompThreads$ThreadQuery.run() line: 81

    Corriendo el test varias veces ‘siempre’ va a tener esta salida
    4 – Terminado con query: select * from ad_field union all select * from ad_field
    4 – Terminado con query: select * from ad_user where ad_user_id = 0
    3 – Terminado con query: select * from ad_field union all select * from ad_field
    3 – Terminado con query: select * from ad_user where ad_user_id = 0
    2 – Terminado con query: select * from ad_field union all select * from ad_field
    2 – Terminado con query: select * from ad_user where ad_user_id = 0
    1 – Terminado con query: select * from ad_field union all select * from ad_field
    1 – Terminado con query: select * from ad_user where ad_user_id = 0
    0 – Terminado con query: select * from ad_field union all select * from ad_field
    0 – Terminado con query: select * from ad_user where ad_user_id = 0

Viendo 1 entrada (de un total de 1)
  • Debes estar registrado para responder a este debate.