Respuestas de foro creadas
-
AutorEntradas
-
22 abril, 2010 a las 4:47 pm en respuesta a: Componente Centro de Costos y Consulta sgtes pasos #34420
Javier Ader
ParticipanteEn cuanto a las modificacioens de árbol, si ahora que lo pienso mejor tenes cierta razón y tambien con lo de las validaciones “nodo-padre-nodo-hijo”; despues de todo se podría “confiar” un poco más en el usuario final y que no se ponga a mucho jugar con esas cosas. Igual, en el futuro se podría pasar a algo similar a tener varios arboles pero uno solo activo o por defecto (una vez activado este arbol las nuevas imputaciones se hacen contra este; por ej, al momento de hacer informes el usuario tiene que seleccionar una arbol dado). Esta último lo digo mas que nada porque se podría agregar esta flexibilidad sin mayores problemas en el futuro si es que es requerida.
En cuanto a lo del componente:
Si,para distribuirlo las modificaciones seguro que es mucho inteligente hacerlo via componentes. Ahora… lo que por ahora solo pueden hacer los componentes es aplicar modificaciones a nivel de metadatos y potencialmente instalar datos “de usuario” (por ej, al instalar este componente se puede no solo crear las tablas si no tambien agregarle por ej, un arbol de costo para qu ese tome como ejemplo o como base; ojo pero esto ultimo reuqiere otras columnas “especiales” relacionadas con los componentes en las tablas que agregues; pero aca me falta un poco entrar en detalles; en otro thread me dijeron como hacerlo, pero no lo testie; igual, eso también se puede hacer sobre el final); lo que no tiene (creo… por que lo veo “potencialmente” posibible) es la funcionalidad de agregar código java… esto que yo sepa require agregar codigo a mano y recompilar el proyecto (el chico que va a tomar el curso de programación lo va a saber hacer enseguida, es bastante simple usando Eclipse; de todas maneras, si no le adelanto un poco por msn para no hacer este thread larguiiiiiiiisimo; igual, en la wiki dice como compilar libertya; de ahi, agregar modificaciones, no hay mucha distancia). Este es un componente de ejemplo http://www.eltita.com.ar/libertya/jug01/files/jug01.jar (lo descomprimis como si fuera un zip normal si queres ver el contenido), si queres probalo instalar (es la versión “componnte” del ejemplo “Juguetes” que se usaron en los cursos anteriores y supongo que en lo siguen usando en los actuales); si no me equivoco, anda bien (es posible que tengas un problemita con los acentos y letras no ascii… pero bueno, ese es otro tema).
En conclusión: creo que no tiene muchas implicancias “dearrollarlo” como si fuese un componente desde el principio (solo se convierte en componente más adelante; mientras tanto se puede ir probando tal como esta) aunque se podría hacer como una forma de ir compartiendo los cambios (pero esto creo que termina complicando mas las cosas, al menos en este caso en que no hay muchas tablas). De todas maneras: si me hago un tiempito, desarrollo las ideas que te propuse como si fuese un componente (versión muy beta) y de ahi partimos como base.
–22 abril, 2010 a las 4:10 pm en respuesta a: Modificar el nombre a la organizacion por defecto. #34423Javier Ader
ParticipanteSi esta medio raro como esta hecho (el árbol a la izquierda oomo que esta un poco de más jajaj). Creo que tenes que usar las “flechas” (la hacia abajo); creo que de esa manera pasas de la organización “Defualt” de la compnia “System” a la organización “Default” de la copania “Libertya” (o como la hayas renombrado). Al árbol no lo mires mucho que confunde (el nodo raíz de árbol creo que tampoco cambia de nombre, es “asi”, adelantándome un poco por si te surge esa duda…)
21 abril, 2010 a las 11:33 pm en respuesta a: Componente Centro de Costos y Consulta sgtes pasos #34419Javier Ader
ParticipanteAntes que nada unas preguntas:
-¿Se puede imputar un costo a un nodo y otro a un nodo que es un hijo del anterior? Digamos en tu ejemplo: 10% a Sucusal A y 30 a Dtpo de Ventas que es “hijo” de Sucursal A.
Porque estas esta haciendo la diferenciación de si es de costo o ganancia a nivel de “centros de costos”? Para mi es mas simple tener un solo árbol de Centros de Costo Y Ganancias, y que la diferenciación se de a nivel de imputación.
También: tené en cuenta los casos en que los centros de costos se puede modificar…. por ej, debería poder haber mas de un arbol de centros de costos? Acá el tema viene porque es probable que surga algo como “vamos a refefinir el arbol de centros de costos”; pero lo que en realidad quieren es dejar de usar el anterior (pero mantenenrlo para que quede como referencia) y pasar a usar un nuevo arbol.Bueno igual: el tema de manejar arboles siempre es complicado en bases de datos, habría que ojear un poco el código que por ej muestra el árbol del menú; me da la sensación que hay un elemento gráfico reusable que se podría utilizar (a nivel de tablas o tabs, hay una variable que dice “contiene” arbol que creo que se podria usar).
De cualquier manera, tal vez la forma mas simple (al menos para un primer enfoque que despues se puede llegar a mejorar) que no requiere modificaciones a nivel de base de datos de la tabla de facturas (el botón que decís requiere una columna; podría ser un columna virtual pero al el hecho de ser virtual creo que lo hace que el botón quede deshabilitado) es agregar una tab para a la ventana factura de compras que apunte a la tabla de asociaciones de imputaciones. Antes de esto se crea la tabla “preliminar” (despues se la podría convertir en arbol…) C_CyG (Bajo mi enfoque el un centro siempre puede ser tanto de costo como de ganacias;):
C_CyG
C_CyG_ID int not null, clave primaria
IsActive, Name, Description, AD_Client_ID, AD_Org_ID, como todas las tablas,
Y NADA MAS por ahora…C_InvoiceImp
Esta tabla tendría esta definición:
C_InvoiceImp_ID int not null
AD_Client_ID, AD_Org_ID, IsActive
C_Invoice_ID int not null //referencia a C_Invoice
C_CyG_ID int not null referencia a C_CyG//
IsCost Y/N not null (si es una imputación de costo o ganancia)
Percentage numeric(5,2) not null,
//posiblemente un campo Obeservations no vendría mal aca…)
//también quizá un seqNo pero no lo veo muy necesario
C_InvoiceImp_ID clave primaria
C_Invoice_ID referencia a C_Invoice
C_CC_ID, referencia a C_CCSe crea un tabla para a nivel de libertya para C_CyG y para
C_InvoiceImp ; se crea una ventana con una sola pestaña para la edición de C_CyG (de nuevo…. por ahora, nada de arboles…).
Se crea una pestaña en la ventana de facturas de compra asociada al tabla de C_InoviceImp (creo que ahi hay que setear que la clave “padre” es C_Invoice_ID, aunque me parece que no es estrictamente necesario); esta pestaña que este con un nivel de anidación 2 y un nivel de secuencia determinado (al final puede ser tranquilamene). Para que la la imputaciones a una factura de compra siempre sean de costo se puede hacer este campo readonly y jugar con el IsSOTrx de la factura para setearle el valor por defecto (bien bien , no recuero como se hacer pero casi seguro que se puede)Así como esta, no hay casi nada de chequeos (por ej, no se verifica si la suma de los porcentajes excede 100 ) ni hay ningún árbol… Pero el funcionamiento sería asi:
-se selecciona el tab de Imputaciones de la factura
-se pone el porcentaje (creo que hay formas para que sugiera el porcentaje que falta)
-se selecciona el C_CyGOk, las validaciones y el arbol se pueden agregar mas adelante usando codigo:
-Al momento de guardar una nueva C_Ivoice_Imp, se verifica que el porcentaje total no exceda 100 y , en caso de que haya restricciones de arbol que te dije, se hacen tambien aca
-Al momento de guardar un C_Invoice_Imp modificada idem.
-A nivel de factura se puede agregar dos columnas virtual (y read only por lo tanto) que haciendo un simpel “select sum(* ) from C_Invoice_Imp where C_Invoce_ID =id de factura actal AND IsCost = Y, para la primera,N para la sengunda), permite visualizar facilamente que porcentaje del costo o ganancia se tienen actualmente imputado (en la ventana de compra solo se muestra la primera, en la de ventas, la segunda).
-Para mejorar la selección desde el punto de vista visual se puede agregar un boton “Seleccionar Centro de CyG” a la tabla C_Invoice_Imp que al ser disparado muestre un dialogo especializado y que permita selecionar algún nodo por el usuario; el proceso asociada al boton dispara este dialogo y a su retorno setea el el campo C_CyG_ID (que en este caso seria NO actualizable a nivel de pestaña). Este dilogo se podría reutilizar en todos los demás lugares en que sea requerido, pero su funcionalidad es permitir seleccionar SOLO un nodo.
-Y bueno, finalmente convertir a C_CyG en un arbol…Si te parece más o menos la idea, avanzamos en este sentido; pero la primer parte como te digo, se podría ir haciendo asi (incluso esto te permite, asumiendo que la tabla C_Invoice_Imp queda estable, ir haciendo los reportes o cosas por el estilo en paralelo; toda la lógica de verificación de imputaciones la dejas solo en las clases que hacen las verificaciones a nivel de C_Invoice_Imp y en el dialogo especializado, el cual potencialmente se puede ir “afinando” de poco y que ademas es reutilizable)
PD : para pasar todo estas modificaciones a la forma de componente es algo bastante trivial pero el tema que va haber mucha prueba y error, y las modificaciones intermedias no van a tener sentido al final; por eso creo que la mejor forma de hacerlo es: una vez que se definen y testean todos los cambios (y se va documentando los cambios que realizas) , en otra base de datos creas un nuevo componente y lo pones en estado desarrollo; una vez hecho esto, repetís todos los cambios que finalmente tomaron efectos; detenés el desarrallo del componente y lo exportas.
Javier Ader
Participantela verdad que es muy raro tu error, es muy poco probable que lo vuelvas a ver en bastante tiempo. De cualquier manera no te hagas problema que el error no lo estas produciendo vos y que ademas es casi seguro no tiene consecuencias (DB.isServerObjetcs te va responder lo que debe con error o sin error; salvo que hayas configurado el cliente para que use “objetos en el servidor”; pero no creo que hagas esto; en ese caso isServerObjects te va a retornar N ‘algunas’ veces y de manera casi aleatoria)
La excepción se esta disparando a partir de DB.isServerObjetcs(), un método que es llamado todo el tiempo (cada vez que se ejecuta una sentencia sql contra el servidor…). E.d tu problema, si no fuera lo que explico más adelante, debería ocurrirte a cada rato y tendría el log de errores con miles de ocurrencias del mismo error.
El problema subyacente es que el “chipher” (Secure.s_chipher) (algoritmo para encriptación y desencripción usando por ej, para buscar valores de libertya.properties ) parece no estar “inicializado”…. el tema es que el código justo antes de cada encriptación y desencriptación, lo inicializa!!! (Secure.decrypt(string) y Secure.encrypt(string)
Entonces? Lo único posible que veo (y que si creo que es un bug en Libertya) es que los métodos Secure.decrypt y Secure.encrypt no son accedidos sin control de concurrencia y pueden dar lugar a errores similares a los que comentas (el tema es que los dos métodos usan el mismo objeto chyper, y por ej, lo inicializan de manera distinta). En cualquier caso este error debería ocurrirte de manera un poco aleatoria y no siempre (en el fondo es un error de falta de chequeo de concurrencia). Las probabilidades de ocurra aumentan un poco si por ej estas corriendo el cliente en un sistema multi-nucleo, lo cual es común en estos días (es tu caso?)Ahora, algo que no viene al tema exactamente, pero Ini creo que debería cachear la propiedad ServerObjects (la propiedad que es accedida todo el tiempo) y no simplemente ir a desencriptar un “N” anteriormente encriptado…. (no se que tan pesado sera el algoritmo de descripción, pero me parece que no son cosas simples).
Para evitar problemas los problemas de concurrencia, se deberían poner locks en los accesos al cipher (se dan solo en Secure.decrypt y secure.encrypt), el tema es que esto puede llevar a cuellos de botellas si hay multiples threads concurrentes (lo cual es el caso cuando se cargan lo MLookups, como muestra la traza de sticuyo). Otro pequeña mejora para evitar esto último (que indirectamente solucionaría el tema de ServerObjecs) es que se Secure precalcule la encriptación de “Y” y “N”; asi por ej, decrypt antes de usar el chipher haria, sin usar ningun lock algo como
if value.equals(YEncriptado) return “Y”
if value.equals(NEcriptado) return “N”encrpyt por otra parte
if (“Y”.equals (YEncriptado) return YEncriptado
if (“N”.equals (NEncriptado) reutrn NEncriptadoEsto evitaria cuellos de botellas en los locks para strings N e Y que son usadas todo el tiempo.
Otra alternativa es que las strings Y y N nunca se encripten a partir de Ini (y en … no le veo mucho sentido desde el punto de vista de seguridad (uno fácilmente puede darse cuenta cual es valor encriptado para Y y para N)
Javier Ader
ParticipanteQue tal Federico. A nivel de programación, diseño de tablas, metadatos, etc no tengo ningún problema en intentar darles una mano (este en mi fuerte…. soy principalmente un programador). Ahora, a nivel de análisis del problema en sí (e.d los requerimientos de usuario finales) tal vez se me complique un poco sin un poco más de detalles (creo que te entendí más o menos a lo que apuntas; pero el concepto de “centros de costos” me lo acabas de hacer conocer!), así que el tema de crear un thread mas que nada para explayarte (y que te consulte mis dudas …) un poco más me parece perfecto (de paso, tal vez de esta manera, con aportes de otros usuarios, se pueda hacer una mejora más general y/o flexible y/o configurable a nivel de requerimientos finales)
Ya te agrego como contacto del MSN; cualquier cosa, contactame por ahí y claro, por este foro.
SaludosJavier Ader
ParticipanteQue tal. Si no podes instalar el JDK de Java es muy probable que te haga fracasar toda la instalación. Por otro lado es muy raro que no te lo permita instalar en el raíz (esto de todas maneras no es totalmente necesario; yo alguna vez lo instale bajo Archivos de Programas y no trajo problemas; puede llegar a traer problemas cuando uno intenta compilar los fuentes desde los scripts… pero no creo que sea tu idea, ademas que haciendolo desde Eclipse no trae problemas… igual, esto es otro tema); estas corriendo con una cuenta con permisos administrativos? Si no… casi seguro que te va a fallar casi cualquier cosa… (ojo , cuando te dice raíz NO se refiere a que seleccción C:; si no a C:AlgunNombreSinEspacios; por ej C:Java; exista o no este directorio; si lo queres isntalar en el raiz casi seguro que va a fallar). Proba simplemente crear de antemando el directorio C:Java; si vos lo podes hacer, el instalador tambien, y no tiene que haber problemas de permisos.
De cualquier manera, si te sigue ocurriendo el problema, lo que podes hacer es instalar exactamente el JDK que incluye el instalador de manera manual (después ejecutas el instalador como antes el cual te debería detectar que el JDK ya está instalado y no te lo debería querer reintentar instalar; por ej, no te debería pedir el directorio de instalación, si no , simplemente mostrártelo). Para esto:
– bajas 7zip (open source), o casi cualquier programa que abra zips (casi seguro que WinZip o WinRar también sirve; yo siempre use 7zip).
– abrís con el 7zip el archivo del instalador (libertya-10.03-xp.exe si no me equivoco) como si fuese un archivo comprimido normal (lo es!). Dentro de los archivo que te muestra el instalador del JDK es el que tiene nombre jdk-6u14xxxx.exe.
– descomprimis este archivo (unos 80 megas mas o menos) y lo ejecutas.
Esto te debería lanzar el instalador del JDK; si logras instalarlo, todo bien y volvés a repetir la isntalación de Liberyta; si no, por lo menos vas a ver de una manera mas clara cual es el error de instalación del JDK.Javier Ader
ParticipanteAntes que nada, edita tu post, es muy ancho y no se puede leer
correctamente (bueno, el error es en parte del foro…)
Que versión de libertya estás usando? Al menos en la 10.3 hay un
temita con le formato de importación actual; esta creado para
que en el CVS pongas el id del proveedor y el id de la “subfamilia”
(ojo, NO de la famiilia; esta definido el nombre en el importador).
Ninguno de estos datos es obligatorio, pero si los pones NO tenes
que poner 0; en vez no pongas nada (en vez de
#26 NEGRO,Cartucho Tinta #26,0,25,0,0
pone
#26 NEGRO,Cartucho Tinta #26,0,25,,
)Obvio que de esa manera cuando importes no va a tener asociado ni
una subfamilia ni un proveedor. Si queres solucionar esto tenes
dos opciones:
1)averiguas el id interno del proveedores y de la subamilias que uses
y las pones en el las últimas dos columnas del cvs
o
2) cambias el formato de importación para que las ultimas dos entradas
del cvs no solo sean strings si no que apunten a otra columnas (
en vez C_BPartner_ID_Entidad Comercial; pone BPartner_Value_Bussiness,
para el caso del proveedor y en lugar de M_Product_Category_ID_xxxx
pone ProductCategory_Value_Clave de Categoria; en el caso de la
subfamilia). Ok, pero en este caso tenes que saber el
busqueda del proveedor y de la subfamilia; estos los tenes
en Pestaña Subfamilia-> Valor de Busqueda y Entidades Comerciales->
Valor de busqueda. En cualquier caso, lo que tiene de bueno esta opción
es que aunque pongas cualuqier cosa en las utlimas dos columnss
no te va a tirar error (a lo sumo el producto no va a quedar asociado
a ningun proveedor ni a ninguna subfamilia)Javier Ader
Participanteno se porque te estas basando en la wiki; porque no bajas el instalador automático? Te instala el JDK de java, Postgres y Libertya (los dos primeros si no los tenes instalados) sin muchos pasos que seguir (lo unico raro que podes llegar a necesitar es cambiar los numero de ports usados). Esta en la sección de descargas y es mucho más simple de instalar.
En cualquier caso; los paths bajo XP se setean en Mi Pc -> click derecho Propiedades->Opcines avanzadas-> Varibles de Entorno. Tenes que agregar los directorios que dice la wiki a la variable Path (al final, separando por “;”). Setealos bajo las “variables” de sistema,no de “usuario”.
Javier Ader
ParticipanteOk, ahí hice el test… no parece haber “terribles” diferencias salvo que haya hecho algo mal…
El testing lo hice principalemnte a travez de AWIndow.initWindow y Env.getgetMWindowVO (este ultimo siempre es invoda indirectametne por initWindow; asi que el tiempo que este leva es siempre un parte de lo que lleva initWindow).
Lo hice en 3 pasos:
1) modificar DB para mantener contadores de rechequeos
DB.javaCode://se agregan estos campos para el testin
public static int vecesRechequeosConnRO = 0;
public static int vecesConnRONoEsReadOnly =0;
public static int vecesConnRONoEsReadCommited = 0;//cambiar el codigo origianal en DB.getConnectionRO()
else{
if( !connection.isReadOnly()) {
connection.setReadOnly( true );
}
if( connection.getTransactionIsolation() !=
Connection.TRANSACTION_READ_COMMITTED ) {
connection.setTransactionIsolation(
Connection.TRANSACTION_READ_COMMITTED );
}
}//por lo sig para llevar lo contadores y mostrar
//cuando se cambia realemten readOnly o el nivel de aislacion
else{
DB.vecesRechequeosConnRO++;
if( !connection.isReadOnly()) {
DB.vecesConnRONoEsReadOnly++;
System.out.println(“ConnRO NO isReadOnly”);
connection.setReadOnly( true );
}if( connection.getTransactionIsolation() !=
Connection.TRANSACTION_READ_COMMITTED ) {
System.out.println(“ConnRO NO READ_COMMITED”);
DB.vecesConnRONoEsReadCommited++;
connection.setTransactionIsolation(
Connection.TRANSACTION_READ_COMMITTED );
}
}Cargue una vez libertya para ver realemtne cuantas veces
se llamaba a connection.setReadOnly() y connection.setTransactionIsolation () y esto se ve que es llamado solo en tiempo de logueo y una sola vez (bueno, a mi me pararecion 5 veces “ConnRO NO isReadOnly” en la consola; no entendi muy bien porque fueron 5 y no 3 que creo que es el numero de conexiones RO en el pool…. raro, despues debuggueo un poco). En cualquier caso setTransactionIsolation no es llamado ni una sola vez (e.d, la conexiones son creadads en modo READ_COMMITED y esta propiedad no es cambiada mas; al menos despues de cargar liberyta y abrir un par de ventanas).
Esto me llevo a la siguiente modificacion para crear la conexiones del pool en modo read only de entrada (al principio de DB.getConnectionRO):Code:synchronized( s_cc ) // use as mutex as s_connection is null the first time
{
if( s_connections == null ) {
s_connections = createConnections(
Connection.TRANSACTION_READ_COMMITTED ); / see below//EDIT: este for tiene que estar dento del if anterior… si no todo el tiempo se setan a readonly…
//test: modificaciones para crear en modo read only de entrada
for (int i = 0; iVolví a cargar libertya y ahora no aparecio ni un solo “ConnRO NO isReadOnly”; lo cual me “probo” que las conexiones RO una vez que son creadas no pasan a ser de “escritura”.
Bueno, ahora agregue el sig. codigo para que me muestre por pantalla cuando acceso inncesarios se producen por getTransactionIsolation(), tanto a partir de Env.getMWIndowsVO como AWIndows.initWindow:
AWindow.initWindow (lo nuevo entre comentarios “test”)
Code:public boolean initWindow( int AD_Window_ID,MQuery query ) {
this.setName( “AWindow_” + AD_Window_ID );//test
long miliInicio = System.currentTimeMillis();
int vecesRechequeosConnROInicio = DB.vecesRechequeosConnRO;
//fin testboolean loadedOK = m_APanel.initPanel( 0,AD_Window_ID,query, m_realFrame );
commonInit();
//test
long miliFin = System.currentTimeMillis();
int vecesRechequeosConnROFin = DB.vecesRechequeosConnRO;
System.out.println(“Tiempo de carga de AWindow.initWindow (mls): ” +
(miliFin – miliInicio));
System.out.println(“Cantidades de recheos en DB.getConnectionRO: ” +
(vecesRechequeosConnROFin – vecesRechequeosConnROInicio) );
//fin testreturn loadedOK;
} // initWindowPara testear Env.getMWIndowVO modificando APanel.initPanel (linea 670 mas o menos)
Code://test
long miliInicio = System.currentTimeMillis();
int vecesRechequeosConnROInicio = DB.vecesRechequeosConnRO;
//fin testMWindowVO wVO = AEnv.getMWindowVO( m_curWindowNo,m_mWorkbench.getWindowID( wb ),0 );
//test
long miliFin = System.currentTimeMillis();
int vecesRechequeosConnROFin = DB.vecesRechequeosConnRO;
System.out.println(“Tiempo de carga de AEnv.getMWindowVO (mls): ” +
(miliFin – miliInicio));
System.out.println(“Cantidades de recheos en DB.getConnectionRO: ” +
(vecesRechequeosConnROFin – vecesRechequeosConnROInicio) );
//fin testBueno, recompile y cargue libertya; abri 4 veces la ventana Entidades Comerciales (previamente seteando “mostrar pestañas contables” asi se cargaban mas pestañas) y los resultados que me mostro al consola estan en *1
Ahora, para testear la mejora de no hacer los rechequeos simplemente comente en DB.getConnectionRO las siguientes lineas
Code:else{
DB.vecesRechequeosConnRO++;
/* COMENTADO PARA TESTEAR POSIBLES MEJORAS de no rechequear
if( !connection.isReadOnly()) {
DB.vecesConnRONoEsReadOnly++;
System.out.println(“ConnRO NO isReadOnly”);
connection.setReadOnly( true );
}if( connection.getTransactionIsolation() !=
Connection.TRANSACTION_READ_COMMITTED ) {
System.out.println(“ConnRO NO READ_COMMITED”);
DB.vecesConnRONoEsReadCommited++;
connection.setTransactionIsolation(
Connection.TRANSACTION_READ_COMMITTED );
}FIN DE COMENTARIO*/
}(DB.vecesRechequeosConnRO++; no lo comento)
Recompile y volvi a correr libertya y a abrir 4 veces seguidas la ventana entidad comerciales, los resultados en *2
Datos de testeo:
-XP Home; Celeron 2 Ghz, 640mb de ram (bastante viejita…)
-cliente y servidor en la misma maquina
-acceso via ip privada de red local (NO localhost; aunque es probable que usando localhost tarde mas; Windows es poco serio para ciertas cosas… )
-versión libertya 10.03, loc AR, loqueando en perfil Administrador, con lenguaje ES-AR
-carga del servidor: mínima (solo un cliente accediendo; servidor de aplicaciones no cargado)Resultados
*1) Permitiendo recheos (como se hace actualmente, salvo que las conexiones del pool se crea read only de entrada)
Entidades comerciales
1)
Tiempo de carga de AEnv.getMWindowVO (mls): 8453
Cantidades de recheos en DB.getConnectionRO: 252
Tiempo de carga de AWindow.initWindow (mls): 21703
Cantidades de recheos en DB.getConnectionRO: 421
2)
Tiempo de carga de AEnv.getMWindowVO (mls): 4000
Cantidades de recheos en DB.getConnectionRO: 238
Tiempo de carga de AWindow.initWindow (mls): 11735
Cantidades de recheos en DB.getConnectionRO: 4143)
Tiempo de carga de AEnv.getMWindowVO (mls): 4016
Cantidades de recheos en DB.getConnectionRO: 240
Tiempo de carga de AWindow.initWindow (mls): 13000
Cantidades de recheos en DB.getConnectionRO: 4074)
Tiempo de carga de AEnv.getMWindowVO (mls): 3578
Cantidades de recheos en DB.getConnectionRO: 238
Tiempo de carga de AWindow.initWindow (mls): 10047
Cantidades de recheos en DB.getConnectionRO: 407Promedio de initWindow a partir de 2 (ya que la carga de la primer ventana hace accesos a datos que luego son cacheados; por ej AD_Msg si mal no recuero es leído y cacheado por completo):
((11735 +13000 + 10047) /3) = 11594 milisegundos
(e.d, 11,594 segundos)*2)Evitando rechequeos
1)
Tiempo de carga de AEnv.getMWindowVO (mls): 6282
Cantidades de recheos en DB.getConnectionRO: 252
Tiempo de carga de AWindow.initWindow (mls): 17782
Cantidades de recheos en DB.getConnectionRO: 422
2)
Tiempo de carga de AEnv.getMWindowVO (mls): 3281
Cantidades de recheos en DB.getConnectionRO: 238
Tiempo de carga de AWindow.initWindow (mls): 9906
Cantidades de recheos en DB.getConnectionRO: 408
3)
Tiempo de carga de AEnv.getMWindowVO (mls): 3203
Cantidades de recheos en DB.getConnectionRO: 238
Tiempo de carga de AWindow.initWindow (mls): 9984
Cantidades de recheos en DB.getConnectionRO: 407
4)
Tiempo de carga de AEnv.getMWindowVO (mls): 2843
Cantidades de recheos en DB.getConnectionRO: 238
Tiempo de carga de AWindow.initWindow (mls): 9140
Cantidades de recheos en DB.getConnectionRO: 407
promedio a partir de 2:
(9906 + 9984 + 9140)/ 3=9676Conclusiones (medio apresuradas; lo debería haber ejecutado mas veces…):
-en promedio, para la ventana entidades comerciales se generan cerca de 410 accesos a la base de datos que bajo mi hipótesis son innecesarios (el valor varia en los tests al parecer por que existen otros threads corriendo concurrente que también usan el pool de conexiones esporadicamente para obtener info de la base de datos; esto incrementa el contador de manera variable, pero muy pocas veces)[obivamente estos accesos dependende de la “complejidad” de la ventana; en particular cada uno del campos que son de tipo table, tabledir, busqueda generan uno o dos accesos cada uno; testar cuantas veces se llama a MLookupFactory.getLookupInfo por ej).
-evitando los rechequeos bajo las condiciones dadas (y sin contar la primer carga), AWindow.initWindow da un mejora de aproximada de 11594 – 9676 = 1918 milisegundos; esto es un mejora en rendimiento del 1918 / 11594 = 16 %
Observaciones sobre otros escenarios de testeo:
-es posible que bajo las siguientes condiciones las diferencias sean mas visibles
a) mayor carga en el servidor (esto es, bajo mi escenario, el servidor respondía casi inmediatamente a los accesos generados por los rechequeos; no se si sera el caso cuando se le conecten concurretemente digamos 10 clientes y el servidor de aplicaciones)
b) clientes en otra maquina distinta a la que corre el posgress; bajo estas condiciones los accesos de rechequeo son mas costosos ya que entra a jugar el round-trip de la red (el cual es si mismo es peor cuando bajo la red hay mucha carga en general… ni hablar si el cliente corre externamente, por ej, desde Internet)– yo me enfoque solo en la carga inicial de las ventanas, pero esta situación muy probablemente exista en otros casos en haya un acceso masivo al servidor (excluyendo las partes en que se usan transacciones “not null”, ya que como puse al principio, usarlas implica que DB saltee “naturalmente” los chequeos); se me viene a la cabeza la generación de informe (aca los accesos innecesarios pueden ser mucho mas variables y depender principalmente del tamaño del informe y no tanto de su complejidad), sería interesante testear estos otros puntos “criticos”.
PD: En cuanto a la carga de ventanas se me viene ocurriendo una forma de recachear la info necesaria para cargar una ventana dada; por ej, los objetos MWIndowVO los veo “potencialmente” cacheables (la complejidad proviene de cuando dejar de cachearlos…. me imagine enfoques un par de enfoques, por ej usar triggers a nivel de base de datos que mantengan contadores que se incrementen cada vez que se modifica de alguna manera alguna de estas tablas, pero no los veo para nada simples); esto evita TODOs los acceso necesarios a los metadatos para generar la ventana en si (pero en este caso; me imagino incluso otra forma mas simple: que las ventanas por defecto no se cierren; se minimicen o se oculten; al querer reabrir otra vez una ventana anterior, simpelemente se muestra la ventana anterior, posiblemente dandole la info para que rellea los datos y se posiciones en la primer pestaña)… pero bueno, esto es un poco más ambicioso y no lo pense mucho (el tema es que si anduviese todo este tema de accesos innecesarios tendría mucho menos impacto, al menos con respecto a la carga de ventanas)
PD 2: espero que alguien realice estos testeos en sus propias instalaciones… por alguna razón tengo la sensación que mi escenario no es el más apropiado (principalmente por lo lento de mi maquina… entre esperar 11 segundos a esperar 9 no hay diferencia desde el punto de vista del usuario; hay un punto en que da lo mismo desde el punto de vista subjetivo… otra cosa sería entre esperar 3 segundos vs esperar 1)
Javier Ader
ParticipanteBueno, mi idea era evitar directamente tanto el get como el set; si se supone que las conexiones en el pool que busca getConnectionRO ya son readonly y READ_COMMITED, porque habría que hacer el “rechequeo y reseteo”.
La única forma que veo que estas conexiones pierdan estas características es que desde otro punto código haya algo comoConnection conn = DB.getConnectionRO();
conn.setReadOnly(false); O
conn.setTransactionIsolation(algo distinto a READ_COMMITED).Pero no entiendo porque se habría de hacer esto (si se necesita una conexión con parámetros especiales, entonces ese código especifico la debería crear de manera excepcional y no andar tocando las que están en el pool de DB).
Haciendo una búsqueda de llamadas a Connection.setTransactionIsolation se ve que es llamada solamente en las clases DB_Orable, DB_Sybase (que no deben estar en uso supongo), DB_Posgres , CConnection, DB (getConnectionRO y getConnectionRW) [no termino de entender bien estas clases, pero parece que las conexiones son cacheadas en mas de un lugar]. A Connection.setReadOnly es solo llamado desde DB.getConnectionRO (y seguro que es ejecutado solo una vez por cada conexión creada). DB.getConnectionRO es llamada a su vez en pocos puntos.Ahora me pongo a hacer un test: básicamente voy a comentar
la siguientes lineas en DB.getConnectionRO
if( !connection.isReadOnly()) {
connection.setReadOnly( true );
}if( connection.getTransactionIsolation() != Connection.TRANSACTION_READ_COMMITTED ) {
connection.setTransactionIsolation( Connection.TRANSACTION_READ_COMMITTED );
}y agregar un setReadOnly(true) al momento de crear el pool (porque eso si es necesario hacerlo al menos una vez). Si no me equivoco casi todas (por no decir todos) los accesos generados en al carga de ventanas usan la conexiones RO de DB.
Después voy a hacer un test de tiempo de carga de la ventana Endidades comerciales. Supongo que accediendo a un server en la misma maquina que usa el cliente (como es mi caso) tal vez no haya tanta diferencia (cuando hice el sniffeo, los paquetes generados por el “SHOW TRANSACTION…” tardan relativamente poco en la red; son paquetes chicos); ahora, en un red local, las cosas se deben hacer un poco más graves.PD : no entendí bien el miedo al setter que comentas (jajaj pero si, en algún momento lo vi en el código como comentario), pero el setReadOnly y el setIsolationLevel si hay que tenerles un poco de “miedo”; disparan excepciones cuando hay una transacción en curso (http://cvs.pgfoundry.org/cgi-bin/cvsweb.cgi/jdbc/pgjdbc/org/postgresql/jdbc2/AbstractJdbc2Connection.java?rev=1.54&content-type=text/x-cvsweb-markup ) pero no creo que te refieras a esto ya que las conexiones en el pool de DB son “autocommit” (y ahí nunca ha problemas… creo)
Javier Ader
ParticipanteBueno, intente probar usando CStatement en lugar de CPreparedStatement en PluginXMLUpdater.executeUpdate y con un par de modificaciones previas anduvo la ejecución en blogue de cualquier sentencia sql (incluso con referencias “internas”).
Lo que hice fue lo siguiente:
PluginXMLUpdater.java:Code:public static void executeUpdate(String sql, String trxName) throws Exception
{
if (sql != null && sql.length() > 0)
{
//anterior
//CPreparedStatement cs = new CPreparedStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_UPDATABLE, sql, trxName, true);
//cs.executeUpdate();
//cs.close();//MIO: agregado para probar CStatement en vez de CPreparedStatement
CStatement cs = new CStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_UPDATABLE,trxName);
cs.executeUpdate(sql,true);
cs.close();}
Agregue el método executeUpate(string,boolean) en CStatement para que no llame a DB.getDatabaseConvert (no se bien que significa este convert, pero cuando se usa CPreparedStatement se saltea pasando true a noConvert)
CStatement.java:Code://Copy and past de executeUpdtae(sql)
public int executeUpdate( String sql0, boolean noConvert ) throws SQLException {//MIO: Convert?
p_vo.setSql(noConvert?sql0: DB.getDatabase().convertStatement( sql0 ));
if( p_stmt != null ) { // local
return p_stmt.executeUpdate( p_vo.getSql());
}//TODO LO demas identico a CStatement.excuteUpdate(string)
y finalmente hice el fix para los finales de linea en la instalación de componentes (poniendo n como separador de linea
en readFromJar)
VPluginInstallerUtils.java:Code:public static void doPreInstall(Properties ctx, String trxName, String jarURL, String fileURL) throws Exception
{
/* Toma el archivo SQL correspondiente e impacta en la base de datos */
//String sql = readFromJar(jarURL, fileURL, “”);
//MIO:para que agregue este LF entre sentencias SQL
String sql = readFromJar(jarURL, fileURL, “n”);if (sql != null && sql.length() > 0)
PluginXMLUpdater.executeUpdate(sql, trxName);
}Despues instale este componente: http://www.eltita.com.ar/libertya/testCStatement/TestCStatement.zip
Que tiene un preinstall.sql basado en el componente PSP1.0 que postee en otro thread pero que ademas crea una tabla , inserta una fila en la misma, y despues crea un vista basada en esa tabla (e.d referencias internas dentro del mismo conjunto de sentencias a una tabla creada por el mismo archivo).
Pongo el código del preinstall.sql:Code:———————————————————————-
———- Nuevas Tablas y/o Vistas
———————————————————————-CREATE VIEW v_product_not_in_plv AS
SELECT plv.ad_client_id, plv.ad_org_id, plv.m_pricelist_version_id, p.m_product_id, NULL::timestamp without time zone AS created, 0 AS createdby, NULL::timestamp without time zone AS updated, 0 AS updatedby FROM (SELECT m_pricelist_version.m_pricelist_version_id, m_pricelist_version.ad_client_id, m_pricelist_version.ad_org_id FROM m_pricelist_version) plv, (SELECT m_product.m_product_id, m_product.ad_client_id FROM m_product WHERE ((m_product.producttype = ‘I’::bpchar) OR (m_product.producttype = ‘R’::bpchar))) p WHERE ((plv.ad_client_id = p.ad_client_id) AND (NOT (EXISTS (SELECT 1 FROM m_productprice pp WHERE ((pp.m_product_id = p.m_product_id) AND (pp.m_pricelist_version_id = plv.m_pricelist_version_id))))));–agregado para probar el chequeo de referencias dentro del preinstall
create table ejTabla2(
id integer not null,
name character varying(40));
— insert en la tabla creada justo antes
insert into ejTabla2(id,name) values (10,’nombre’);
create view ejView2 as select * from ejTabla2;———————————————————————-
———- Nuevas columnas en tablas y/o vistas
———————————————————————-———————————————————————-
———- Modificación de tablas y/o vistas
———————————————————————-(este preinstall.sql tiene algunos finales de linea con LF y otros con CRLF; no importa al parecer ya que después del readFromJar con n como separador todas las lineas pasan a terminar con este delimitador)
Lo instalo lo mas bien, y me creo la tabla y las dos vistas (la tabla ejTabla2 tenia ademas la fila insertada).
Me dije: Éxito!
Pero… ahora dije: bueno, vamos a recrear el tema de las referencias a tablas inexistentes usando el anterior CPreparedStatement, así que volví al anterior PluginXMLUpdater.executeUpdate (e.d, volvi a usar CPreparedStatement en vez de CStatement). Cree otra base de datos y repetí la instalación suponiendo que me iba a tirar un error de “tabla inexistente” al ejecutar el preinstall.sql…. pero no; lo ejecuto lo mas bien!!!! E.d, a CPreparedStatement tampoco le importan los comentarios, los finales de linea, las referencias internas!!!! Anda lo más bien! Esto “El problema es en el caso en que dichas sentencias hagan referencia a tablas todavía no existentes (escenario en el que te comentaba previamente).” no me ocurrió (todo el problema se solucionaba para el caso de los componentes llamado a readFromJar con n y CPreparedStatement no tenia nada que ver).
Por qué te puede haber ocurrido a vos tal vez provenga de la nota en este link http://jdbc.postgresql.org/documentation/83/server-prepare.html ; ahí dice que la versiones anteriores del driver JDBC para Postgress lleva a cabo la implementación de PreparedStatemet usaban las sentencias PREPARE a nivel SQL; pero estas sentencias NO soportan múltiples sentencias separadas por “;” http://www.postgresql.org/docs/8.1/static/sql-prepare.html ya que solo la primer sentencia va a quedar dentro del PREPARE (el primer “;” que encuentra finaliza la sentencia PREPARE en si misma) y las demas van a ser sentencias normales despues del prepare; bajo estas condiciones es claro que van a ocurrir problemas de referencias si el prepare define algo que es referenciado por las demás sentencias (el PREPARE no ejecuta la sentencia, solo la planifica…). Supongo que para esto haya ocurrido se tiene que estar ejecutando contra un versión del server menor o igual a 7.3 o explícitamente usando el protocolo ‘V2’ en vez de ‘V3’. Vendrá por este lado tu comentario acerca de las referencias a tablas inexistentes?PD : Bueno, en cualquier caso saco 2 conclusiones
1) mi solución usando funciones temporales no parece para nada necesaria jajaja…
2) usar CStatement en vez de CPreparedStatement no soluciona nada ya que no hay ningún problema! (ok, salvo en situaciones en que se tenga que usar versiones anteriores del server o el protocolo V2; ahí CPreparedStatement debería fallar). De cualquier manera en este caso en particular creo que de todas formas se debería usar CStatement (con la modificación que saltea la conversión del sql por DB), pero esta vez por una cuestión de performance, no de correctitud: el instalador de componentes crea un PreparedStatement sin parámetros por cada uno de los inserts,deletes y modificaciones cuando procesa el Changelog via el install.xml y el postinstall.sml y los descarta después de usarlos UNA sola vez! (esto debe estar creando una cantidad enorme de objetos del lado del servidor de manera innecesaria y ademas generando el doble de “round-trips” a la base de datos ya que ejecución de un PreparedStatment requiere dos pasos). Me dio la sensación de que al ejecutar la instalación usando CStatement tardo menos (pero bueno, muchos tests no hice).
3) usar CStatement en general en lugar de CPreparedStatement debe traer mejoras de performance en otras lugares, pero el tema es que se pierde los setsXXXX y se tiene que modificar muchísimas referencias a esta clase. La otra opción que se me ocurre es que se podría modificar CPreparedStatement para bajo ciertas circunstancias sea un wrapper de un Statement e implementar a ‘mano’ cada una de los setXXX manipulando las string sql y usando las APIs de “escapes” dadas por el driver de postgress para no reinventar la rueda. CPreparedStatement pasaría a ser un wrapper de un PreparedStatement o de un Statement; en el primer lugar delegaría los setXXX al PreparedStatement interno tal como lo hace ahora; en el segundo debería manipular la string sql dada en su constructor de manera manual. Después investigo un poco mas acerca de esto (se me ocurre sniffear un poco la red para ver cuantos accesos genera un PreparedStatement vs un Statement… tal vez no haya mejoras de peformance después de todo…)Javier Ader
ParticipanteBuenas Antonio. Exactamente eso es lo que “cuestiono”; no entiendo a nivel conceptual porque tiene que haber un relación uno-a-uno entre campos y columnas de una tabla. Los campos son conceptos visuales, mientras que las columnas son un modelo de datos; llevando a cabo:
1) que la forma de visualizar una columna este especificado a nivel de modelo de datos y no a nivel de modelo visual [se podría dar el tipo visual acá, pero pudiéndolo redefinir si es necesario a nivel de pestaña; e.d una forma de tipo “default”]
2) obligar a que un “objeto” visual (campo en este caso) tenga obligatoriamente asociado un columna
pienso que se esta generando un “acoplamiento” innecesario entre el modelo de datos y el modelo visual (en este caso entre una Tabla y una Pestaña); e.d hay un mezcla de responsabilidades entre los dos modelos (el segundo depende del primero, pero no necesita depender tanto).
Eso es lo que genera que cuando uno quiere agregar un botón en una pestaña (concepto visual) tenga que modificar los metadatos a nivel de tabla (concepto de datos). En particular, esta limitación especifica se puede saltear “un poco” usando columnas virtuales a nivel de tablas (así por lo menos no te obliga a hacer modificaciones a nivel de base de datos; eso ya seria demasiado), pero como dije esto cae dentro de la lógica “read-only” a nivel de pestaña o de tabla en caso de ser una vista (si esta viendo una pestaña en modo solo lectura, no puede presionar los botones ya que pasan a estar desactivados).
Ok, reconozco que no debe tan fácil permitir la extensión que propongo ya que en muchas partes del código se asume que un campo esta asociado a una columna y su tipo visual es extraído de la definición de esta última (creo que todos estos datos se sacan indirectamente a partir de la vista AD_Field_v(t) al momento de crear la pestaña) y la verdad no se si vale tanto la pena.
Tal vez otra opción menos ambiciosa es que las pestañas no solo tengan asociado campos si no también alguna forma de subpaneles de complejidad arbitraria que permitan agregar controles visuales de manera tan directa a pestaña sin tener una relación directa con una columna, pero que se puedan visualizar dentro de las pestañas (visualmente algo similar a las pestañas incluidas). Obviamente estos subpaneles en general van a tener que mostrar datos o acciones relativas a la fila actual (por ej, disparar distintos informes asociados a la fila actual) o incluso relativas a nivel de pestaña (por ej, una forma de “búsqueda rápida” dentro de los filas mostradas en la pestaña; e.d sin necesidad de releer los datos desde la base de datos).En cuanto a todos los tipos de datos que disponibles que mencionas creo que están dados en la clase DisplayType; después miro un poco más por ahí, pero esto aún sigue teniendo las limitaciones que menciono ya que este DisplayType esta asociado directamente a las columnas y no a los campos.
Javier Ader
ParticipanteAh, yo creía que directamente el driver JDBC para postgres no soportaba múltiples sentencias (se ve que existe esta limitación pero solo para ciertas tipos de ejecuciones; en particular para obtener datos basados en “cursores” http://jdbc.postgresql.org/documentation/83/query.html#query-with-cursor , pero no es el caso… creo).
Ahora me puse a mirar un poco el código de la clase CPreparedStatement que usa PluginXMLUpdater para llevar a cabo el preinstall, y veo, si no me equivoco, que en al fin y al cabo cuando uno ejecuta el executeUpdate sobre esta clase, esto termina llevando a cabo un executeUpdate sobre un objeto de la subclase especifica del driver de Postgres para la clase JDBC PreparedStatement. El tema es que los PreparedStatement son objetos en el “servidor” y son preprocesados (la idea si no entendí mal es que se suponen que van a ser llamados mas de una vez, y este preprocesamiento evita el “replaneamiento” y “recompilación” de la sentencia Sql a nivel de postgres y otras mejoras a nivel de performance; en este caso NO lo veo para nada necesario, ya que el preinstall no es una “sentencia” que se va ejecutar mas de una vez). Me pregunto si todo estos problemas no surgen porque se esta usando un PreparedStatement en vez de usar simplemente un Statement (este no es preprocesado). Tal vez usando esta clase en estos casos particulares se solucione no solo el tema de los comentarios si no también el tema de las referencias. Voy a intentar testearlo (si ya lo probaron y no anduvo avísenme así me ahorro el trabajo).[la clase CStatement debe hacer esto]PD : creo que Libertya usa PreparedStatement siempre… me pregunto si este uso es correcto, ya que la idea del usar estos es repetir la misma sentencia solo variando los parámetros (supongo que a nivel de conexión de red JDBC envia primero la sentencias, y después por cada ejecución solo envia los parámetros; de ahi una mejora en performance, PERO SOLO cuando la sentencia ser repite muchas veces). Digamos, al cargar la ventana Productos, el select que los trae, no tiene sentido que sea un PreparedStatement (no tiene ninguna mejora sobre el Statement simple y ademas debe generar un overhead innecesario).
PD 2: bueno, PreparedStatement nivel de API tiene la capacidad de setear parámetros via setXXXs, lo cual lo hace muy util a nivel de programación y esto es usado asiduamente en Libertya; Statement no tiene esta funcionalidad (no entiendo bien por qué pero bueno…). E.d Statement en vez de PreparedStatement puede ser lo más correcto desde el punto de vista de base de datos, pero a nivel de programación complica mucho las cosas.
Javier Ader
ParticipanteEn la wiki dice como hacerlo en general para cualquier distribución http://www.libertya.org/wiki/index.php/Servidor_de_aplicaciones_libertya ; basicamente hay que ejecutar
at now -f IniciarServidor.sh
(por qué usan el comando “at” en vez de ejecutar el comando el script IniciarServidor.sh realmente no se; supongo que al correrlo via at corre como root).
Para instalarlo como daemon (e.d, inicia en booteo) dan un script para Ubuntu y comandos de instalación; no se si te para CentOS sin modficaciones pero lo podes tomar como base.Javier Ader
Participantey… creo que lo que hiciste no se puede revertir desde libertya (para modificar la activación tenes que loguearte en la empresa, pero para loguearte en la empresa, tiene que estar activa…); lo vas a tener que revertir desde sql (usando pgAdmin o la linea de comandos) ejecutando
Code:update libertya.ad_client set isactive = ‘Y’ where name = ‘Libertya’AutorEntradas