- Este debate está vacío.
-
AutorEntradas
-
19 abril, 2010 a las 9:19 pm #31381Javier AderParticipante
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: 638El 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: 81una 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: 81Corriendo 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 -
AutorEntradas
- Debes estar registrado para responder a este debate.