[Aporte] Agregando caches a MLookupFactory

Inicio Foros Foro principal Desarrolladores [Aporte] Agregando caches a MLookupFactory

  • Este debate está vacío.
Viendo 5 entradas - de la 1 a la 5 (de un total de 5)
  • Autor
    Entradas
  • #31472
    Javier Ader
    Participante

    Bueno, el titulo no se si es el mejor pero es mas o menos asi:
    Al crearse en modelo en memoria de los campos (clases MFieldVO), sobre el final de la carga, si el tipo del campo “es lookup” (esto es, lista, busqueda, tabla o “tableDir”), se crea un “submodelo” solo para estos tipos de campos (MFieldVo.lookupInfo, de tipo MLookupInfo). La creación del objeto MLookupInfo en si mismo es delegado al MLookupFactory.getLookupInfo(….).
    Ok, este metodo de MLookupFactory, cachea ciertos MLookupInfo en un cache local, pero otros no….
    Es medio raro que no cachee todos (espcificamente solo cachea los que son de tipo Table o Search; pero no List o TableDir), ya que los MLookupInfo “deberían” ser basicametne estaticos (depende de los datos en el Dicc. que en el uso corriente no se modifican).
    [OBS: los MLookupInfos sufren un postprocesamiento, que por ej, dependen del contexto; pero MLookupFactory prevee esto, y el postprocesamiento lo hace aún cuando el MLookupInfo proviene de la cache; esto,debería ser equivalente]
    Bueno, básicamente lo que hice fue que los métodos que crean los MLookups para los tipos List y TableDir también miren en la cache antes de intentar acceder al servidor (y obvaimente, en caso de que accedan al servidor, cachean el resultado para futuros usos).

    Los metodos modificados son MLookupFactory.getLookup_List(….) y MLookupFactory.getLookup_TableDir (el getLookup_Table pienso que hay que modificarlo, pero para que no “asuma que no cambio el lenguaje”; no se porque me da la sensación que es supuoción hace que que ciertas datos desde System aparezcan traducidos y otro no).

    Son básicamente 4 lineas en cada métodos; la modificación es simple, así que paso a poner el código directamente:

    http://www.eltita.com.ar/libertya/caching/MLookupFactory.java

    Lo unico raro es que uso la conveción de claves en la cache
    “List_”+nombre de lenguadje+AD_Reference_Value_ID
    para las listas
    y
    “TableDir_”+nombre del lenguaje+ Nombre de columna

    Haciendo un par de pruebas me dio mejoras “respetables” (no era para salir a festejar pero bueno), y me da la sensación de que el codigo deberia ser correcto (mucho no chequie, mas que nada la parte en que los lookupsInfo son usados para representar los de los procesos y en par de lados especificos mas…).

    Un buen caso de estudio es la carga de la ventana de entidades comerciales; por ej, usando la sig sentencia sql

    Code:
    select * from (
    select * from ad_field_vt where ad_language = ‘es_AR’
    and ad_tab_id = 224
    union all
    select * from ad_field_vt where ad_language = ‘es_AR’
    and ad_tab_id = 439
    union all
    select * from ad_field_vt where ad_language = ‘es_AR’
    and ad_tab_id = 212
    union all
    select * from ad_field_vt where ad_language = ‘es_AR’
    and ad_tab_id = 496
    union all
    select * from ad_field_vt where ad_language = ‘es_AR’
    and ad_tab_id = 222
    union all
    select * from ad_field_vt where ad_language = ‘es_AR’

    and ad_tab_id = 225
    union all
    select * from ad_field_vt where ad_language = ‘es_AR’
    and ad_tab_id = 213
    union all
    select * from ad_field_vt where ad_language = ‘es_AR’
    and ad_tab_id = 220
    union all
    select * from ad_field_vt where ad_language = ‘es_AR’
    and ad_tab_id = 223
    ) todoFieldEC
    where
    (
    — DisplayType.isLookup
    ad_reference_id = 17 –listas
    or
    ad_reference_id = 18 –table
    or
    ad_reference_id = 19 –tableDir
    or
    ad_reference_id = 30 –search
    )
    and
    –ad_reference_id = 17 –> 33 de 144 Listas NO CACHEADAS por MLookupFactory.getLookup_List
    (
    (ad_reference_id = 18 — table
    or
    ad_reference_id = 30 — search
    )
    and ad_reference_value_id != 0
    ) — 40 de 144 MLookupInfo SI CACHEADAS por MLookupFactory.getLookup_Table

    — Por lo tanto 144 MookipInfos NO CACHEADOS 67 No CACHEADAS Por MLookupFactory.getLookup_TableDir

    — en conclusion solo de 144 accesos se evitan 40

    Se ve que la cache en MLookupFactory tal como esta ahora, solo cachea 40 MLookupInfo, cuando en realidad podría cachear 144. Esto representa, al menos 104 accesos “seriales” (e.d, no se hacen ni siquiera en distintos threads; la logica de carga inicial de una ventana solo puede serguir despues que estos 144 accesos hayan terminado) de más la servidor de base de datos (pueden ser potencialmente muchos mas, ya que MLookupFactory, hace cosas relativamente complejas para llegar al MLookupInfo inferido del diccionario de datos).
    También hay que tener en cuenta que esta cache favorece a todas las ventanas.

    Finalmente otras ideas:
    -hacer una cache de MFieldVO; ya la tengo casi finalizada, pero la idea es que aca se cachea toda la listas de campos asociados una AD_Tab_ID en particular; e.d no es un cache por MFieldVO.

    -estas caches tienen para el usuario final de que la primer carga de una ventana, en general es muyy lenta cuando la ventana es muy compleja a nivel de metadatos (bueno, es medio obvio, no hay nada en las caches). Estoy pensando en hacer una suerte de proceso (un thread que corre en background) que se dispara al final del login para ir haciendo las cargas de ciertas ventanas “de uso corriente” (esto habria que ver como se define y donde; pero ECs y Productos estan en mi lista de preferidas), pero lo que hace es basicamente “simular la carga de una ventana” como si se usase un “zoom” que siempre evalue a falso (esto, para que la simulación no sea costosa, y que traiga innecesariamente los datos dentro de las ventanas; lo que tiene que hacer es obligar a que estas caches en memoria se precarguen). Esto es mas que nada porque el tiempo de carga de una ventana compleja por primera vez muy “trabada”.

    #34804
    Federico Cristina
    Superadministrador

    Javier,

    Muchas gracias por tu aporte!

    Vamos a incorporarlo al proyecto a fin de verificar su estabilidad para luego incluirlo en el próximo release de Libertya.

    Saludos!
    Federico

    #34805
    Javier Ader
    Participante

    (Haaaa se me borro el post que estaba escribiendo…)
    Gracias fcristina; en cuanto tenga un poco de tiempo pongo la cache de MFields asociadas a una determianda pestaña (el tema es que aca no es tan simple y tuve que hacer un par de modificaiones; agregar un par de campos en MField); con esta cache y un par de otras mejoras logro crear un MWIndowVO usando solo 2 accesos a la base de datos (que tambien se podrían evitar con otras caches!); paso de cerca de 6 segundos para la ventana a EC a algo asi como 0.5 segundos (ok, despues de esto, hay otros accesos a la base de datos, por lo cual la carga no es tan rapida como desearia).

    De cualquier manera pienso que hay que modificar o extender la funcionalidad de CCache, por que todas estas mejoras son buenas para el usuario final, pero para el desarrollador pueden joder (ya que la cache evita ver rapidamente los cambios de diccionario…). El proceso “Vaciar cache” debería andar; el tema es que tal vez sea bueno mejorar un poco esto para que una cache pueda declarar multiples tablas de pendientes (no solo una) y que el proceso (probablemente un Form mas que un proceso) permita visualizar datos de estas caches y opcionalmente vaciar solo algunas.
    Otro tema, que dificulta este tipo de caches es que el codigo a medida que va construyendo estos objetos hace uso del contexto para ciertas cosas; esto esta bien, pero el codigo debería modificarse para que en general
    1) busque en una cache determinada
    2) si no esta en cache, genere el objeto, pero SIN tener en cuenta el contexto
    3) guarde en cache el objeto si se ejecuto el punto
    2 (esto es, la cache refleja algo inferido solo del diccionario de datos independientemente del los valores en el contexto
    4) haga todas las modificacioens en los objetos que provengan del contexto

    Si no se hace en estos pasos, uno no puede cachear el objeto, ya que este tiene datos inferidos del contexto actual (el cual puede cambiar la proxima vez que se acceda a la cache). Esto es, la cache es una forma de cachear partes de la base de datos; no tiene que tener ninguna información que pueda, directa o indirectamente provenir del contexto. Muchas partes del codigo va “mechando” un poco de aca otro poco de alla…

    OTRO TEMA (relacionado):

    Ok, el tema es que MLookupFactory con o sin cache sigue accediendo una vez por cada MLookupInfo (240 veces en la ventana de EC, de manera serial y solo para generar los MLookupInfo); esto se da cuando se llama a MRol.addAccessSql; el cual parte de la información la cachea (la que proviene de AD_Record_Access), pero otra, es delegada a MPrivateAccess.getLockedRecord(AD_Record_ID, AD_Table_ID).
    La idea de getLockedRecord retorne algo como “(1,23,33)” [estos seria los ids lockeados por otros usuarios para una determinada tabla), para que pueda usarse en un select de la forma “NOT in (1,23,33)”.
    Para mi esto o no debería usarse para los Lookups (en general los Lookups se usan para editar datos de otras tablas relacionados, no para editar los datos del lookup en si) o usar una forma de cache temporal (la cache es temporal, pero es flusheada de manera “deteminisitica”; esto es, se hace explicitamente desde ciertos puntos del codigo) para almanecar entradas de esta forma

    Code:
    AD_Table_ID Where
    ——————–
    23322 –> “(123,22,232)”
    222 –> “12”
    1002 –> “” (esto significaría que no hay restricción)

    (offtopic: etiqueta code dejo de andar?)
    Esta chache esta asociada a un deteminada AD_User_ID; el tema es que se precalacula al momento de cargar una ventana, haciendo UN SOLO acceso al base de datos; se “flushea” al finalizar la carga.

    Ahora, MRol (o MPrivateAccess; habria que ver donde queda mejor), al buscar el where para una determinada tabla lo que hace es mirar en esta cache “temporal”; si encuentra una entrada todo bien, si no, accede a la base de datos (salvo en el enfoque “trae todo” que planteo mas abajo; ahi, si hay una cache para el usuario, y esta cache no contiene entradas, se asume que no hay registros lookeados).

    Veo dos enfoques para precalcular esta cache; uno mucho mas simple, el otro un poco más complejo pero puede ser un poco mas eficiente si AD_Private_Access esta muy poblada (no creo que sea el caso general….)

    El primero, mucho mas simple, basicamente ejecuta la siguiente sentencia sql
    “SELECT AD_Table_ID, RecordId FROM AD_Private_Access Where Ad_User_ID <> “id del para el que se construye la cache” “
    Posiblemente, si se quiere, ordenado por AD_Table_ID.

    Con estos datos va construyendo un hashmap de Int a List, (desde AD_Table_ID a listas de RecordID); despues de tranforma esta mapa en uno de la forma que puse antes (de int a String con el where ya generado). Si esta cache no tiene una entrada para una determianda tabla, es porque no hay lookeos de registros en la misma.

    La otra, es que al principio de la carga de una ventana determinada, se “precalculen” la lista de los ids de una y cada una de las tablas que van a ser refenciadas (lo que pasa es que esto no es tan simple) desde la ventana y el select inicial seria algo como “SELECT AD_Table_ID,RecordID From AD_Private_Access where Ad_User_ID<>“id del usuaria” AND AD_Table_ID IN “lista de tablas que van a ser usadas en la carga de la ventana” “. La cache generada de esta manera, TIENE que generar entradas vacias para los ids de aquellas tablas que no tiene restriccion de acceso, pero que estaban en la lista IN (esto es diferenciar el caso “la tabla no tiene restricciones” de “la cache no tuvo en cuenta esta tabla al cargarse”). Lo que tiene de bueno esto este enfoque es no va a generar entradas en la cache para tablas que no tienen nada que ver con la ventana en cuestion (digamos, si uno carga la ventana Bancos, no me va a generar una entrada para Productos si es que algun otro usuario lookeo un producto…). Acá, si uno no encuentra nada en la cache temporal, debería ir a buscar a la base de datos (esto por que por un lado, el calculo de “tablas” dependientes puede haber sido incompleto quedando algunas tablas sin tener en cuenta y la otra porque potencialmente se pueden estar cargando otras ventanas o desde algun otro lugar solicitando información para otra tabla que no tiene ninguna relacion con la ventana para la que se genero la cache)

    Me imagino que el primer enfoque es el mas simple y posiblemente el mas eficiente en aquellos escenarios en que la restricción de acceso privado se use poco o nada, en los escenarios en que el lockeo privado se de mucho (y por lo tanto la tabla AD_Access_Private puede ser bastante grande), es posible que el segundo enfoque sea mejor.

    Ninguna de estas dos ideas las implemente, porque por ahora corte por lo sano y getLookedRecord retonar siempre null en mi codigo (e.d, efectivamete deshabilite la funcionalidad de acceso privado…). De cualquier manera, lo veo bastante simple de implementar; después si me hago un tiempito, implemento el enfoque simple.

    #34862

    Esto optimizaría la renderización de las ventanas? y donde se cachea? en el cliente o en el Servidor de Aplicaciones?

    #34806
    Javier Ader
    Participante

    Todo se cachea en el cliente; el servidor de aplicaciones no se usa. Optimiza el tiempo de carga inicial de las ventanas y el sistema en general (ya que se evitan muchos accesos al servidor; esto mejora a todos los clientes, ya que compiten menos a nivel de postgres y de red); la contra de esto es que puede generar una carga de memoria un poco mas grande por parte de los clientes y que como dije, para el desarrollo, puede molestar un poco.
    Todo este tipo de optimizaciones las encuentro intentando ver porque tarda en mi maquina cargar las ventanas; esta bien que mi maquina es lenta, pero accede a un servidor que recide en la misma (e.d casi no hay penalidades de red ni compentencia con otros clientes). Algunos tests me llevaban a que la ventana de EC tarde en abrirse cerca de 20 segundos; despues de la primer apertura (por que el codigo acutal algunas cosas si cachea) cerca de 10 segundos; con las mejoras bajaba a 14 para la primer carga y algo asi como 6.5.

    Bueno, aprovecho y pongo otra mejora de caches: la función de acceso a DB es MLookup.isRecordID; en el caso de la carga de la ventana EC esta funcion genera algo asi como 60 accesos al servidor. Esta función es llamada solo desde MLookup.getDisplay (asi que seguro que genera accesos innesarios incluso despues de la carga de la ventana). EL codigo proviene de una modificación de Dataware (al menos eso dice el coentario) y mas alla de si es de dudosa utilidad o no , la idea de isRecordID es obviamente cachaeble: es el nombre de una columna “Record_ID”? Los nombres de las columnas dificilmente cambien, asi que ir al servidor a cada rato para verificar esto es costo innecesario. Lo que hice fue modificar isRecordID y delegar siempre este chequeo a la clase UpdateRecord_IDReports (por que supongo que esta relacionada con la modificación hecha; pienso que es un lugar correcto). El nuevo isRecordID() es simplemente

    Code:
    private boolean isRecordID() {

    ///Ader: mejora: MLookup.isRecordID
    //se delega SIEMPRE a UpdateRecord_IDReports
    return UpdateRecord_IDReports.isRecordID(m_info.Column_ID);
    }

    E.d no genera un acceso al servidor.
    UpdateRecord_IDReports tiene el nuevo metodo isRecordID que tiene una cache de cuales ids de columnas tienen el nombre “Record_ID” (son muy pocas, asi que mantenerla en memoria es poco costoso). También tiene otra cache, pero esta vez asociada al metodo UpdateRecord_IDReports.HaveNameColumn ya existente (este metodo es llamado menos veces que el MLookup.isRecordID, pero bueno, de nuevo, no tiene sentido no cachear esta info).

    Acá les pongo el UpdateRecord_IDReports directamente; la modificación para la primer cache es poco mas complicada (UpdateRecord_IDReports se tiene que dar cuenta cuando la cache es vaciada; tengo un usar un Listener del evento que dispara CCache).
    http://www.eltita.com.ar/libertya/caching/UpdateRecord_IDReports.java

Viendo 5 entradas - de la 1 a la 5 (de un total de 5)
  • Debes estar registrado para responder a este debate.