viernes, 20 de febrero de 2015

Recuperación de un Windows Server 2003

Hace poco tuvimos un problema, dejó de arrancar el Windows Server 2003 que actuaba de Controlador de Dominio. A veces lograba empezar a iniciar Windows pero antes de terminar de iniciar se apagaba sin ni siquiera una pantalla azul (nunca la extrañe tanto).
Al probar con un Live CD pasaba lo mismo con lo cual se confirmó que existía un problema de hardware, lo cual no era sorprendente ya que tenía unos cuantos años.

El plan era sencillo, comprar un servidor nuevo instalar un Windows Server nuevo y restaurar todo de los backups, pero nunca es tan sencillo, no?

Para poder migrar el Active Directory en un nuevo servidor debe hacerse con ambos servidores online, no se puede recuperar desde un archivo. A su vez como el hardware había fallado la única alternativa era iniciar en un hardware nuevo. El primer paso fue respaldar todo lo que había en los discos en otra PC. Luego utilizamos una PC en desuso para iniciar Windows, pero aparentemente su hardware era demasiado nuevo y no logró arrancar, con lo cual tuvimos que pasar a una PC más vieja y ahí empezamos a tener un poquito más de suerte, pero no se lograba completar el inicio de Windows.

Primer paso de riesgo, utilizando el CD de Windows 2003 hacer una reparación del sistema.
La primera vez tampoco logramos iniciar Windows, así que tuvimos que realizar una reinstalación completa de Windows (sin formatear), aquí empezamos a tener un poco más de suerte y el mismo arrancó pero totalmente limpio.
Algo importante es que este paso elimina la carpeta Windows, así que antes de ejecutarlo es impresindible respaldarla, aunque sea con un LiveCD, de lo contrario sería imposible recuperar la instalación original.
Otra cosa, creo que es importante (aunque no lo pude confirmar) es utilizar la misma contraseña que tenía originalmente o la contraseña de recuperación de Active Directory (DSRM), esta puede ser distinta a la contraseña del Administrador del Dominio.

Una vez que logramos iniciar Windows Server, se instaló el Service Pack 2 y se hizo una restauración del Respaldo. El Respaldo solo tenía el System-State, o sea algunas subcarpetas de Windows, Registro, Active Directory, etc. NOTA: Antes de hacer este paso restaurar manualmente System32 como se muestra más abajo.

Aquí empezaron de vuelta los problemas, al restaurar el estado anterior resultó que al System32 le faltaban montones de DLLs que eran necesarias para un inicio normal, incluso pedía reactivar Windows para poder iniciar sesión, pero fallaba en traer el diálogo de activación y se reiniciaba el sistema.
Después de varios intentos logramos iniciar en modo a prueba de fallos con símbolo del sistema y utilizando el comando explorer trajo el escritorio.
Aquí pudimos recuperar los archivos faltantes de System32 y de toda la carpeta Windows con el siguiente comando:
robocopy Windows\ C:\Windows\ /E /COPYALL /XC
Esto podría hacerse también con un LiveCD, lo importante es copiar solo los archivos faltantes y no reemplazar ningún archivo.

Además aprovechamos para instalar los drivers de la nueva tarjeta de red, que era distinta a la del hardware original.

Después de reiniciar Windwos arrancó normalmente, pidiendo reactivación por cambios de hardware, pero esta vez me daba 3 días para activarlo, así que se pudo iniciar sesión por más que aún no se tenía conectividad de red.

Active Directory levantó y pudimos configurar el nuevo servidor para unirse al dominio, luego se dio de baja el servidor viejo y se dejó el nuevo servidor como principal.

Datos y herramientas que conocí durante el proceso:
  • Para conectar un Windows 2012 a Controlador de Dominio 2003 hay que asegurarse que "el bosque" esté en nivel funcional 2003. Esto requiere primero subir el DC a 2003 y luego el bosque.
    Si hay algún DC viejo desconectado no se permitirá subir el nivel funcional, con lo cual deberá eliminarse manualmente de la base de datos de Active Directory.
  • La base de datos de Active Directory se guarda por defecto en C:\Windows\NTDS\ntds.dit
  • ntdsutil: permite verificar integridad de los archivos de Active Directory, también permite hacer alguna corrección de datos. Por ejemplo fue necesaria para eliminar de la base de Active Directory metadatos obsoletos de un servidor viejo que ya no existía pero pretendía seguir replicando contra él.
  • esentutl: permite reparar un ntds.dit dañado, luego de reparar hay que quitar los .log de la carpeta de NTDS y reiniciar.
  • dsamain: permite levantar el ntds.dit como un LDAP, y se puede consultar con la herramienta ldp.exe, esto puede ser muy útil se se quiere consultar la información para crear un dominio desde cero.
  • ProfWiz: Afortunadamente no lo tuve que usar, pero me lo recomendaron para migrar perfiles de Windows de un dominio viejo a uno nuevo (en el caso de crearlo desde cero).
La lección principal aprendida es que conviene siempre tener más de un Controlador de Dominio, tal vez lo más sencillo sea tener una máquina virtual que funcione como controlador principal o de respaldo, esto es bueno ya que una máquina virtual debería ser bastante más fácil de restaurar.

viernes, 28 de noviembre de 2014

Genexus y nulos

Este análisis se centra en como definir nulos en bases de conocimiento Genexus pero es extrapolable a cualquier modelo de datos (RDBMS, SQL, NoSQL).


¿Qué es un NULL?
En Genexus estamos acostumbrados a que es algo parecido a un Cero o a un String vacío según el tipo de datos, ya que Genexus trataba de ocultarnos los nulos utilizando nullvalues de esos tipos. Más adelante el concepto de nulo se desdobló en 2 conceptos: el NULL y el EMPTY que viene a tomar el lugar del nullvalue cuyo uso es preferible evitar por lo confuso que resulta su nombre.

Entonces un NULL es un valor indefinido (información inexistente o faltante) y tiene mucho sentido en las bases de datos relacionales cuando una relación es opcional. Ej. una Factura puede o no estar asociada a Orden de Compra.

Como ejemplo de lo indefinido que es un valor NULL, algunos RDBMS ordenan los nulos al principio (MSSQL y MySQL) y otros al final (Oracle, PostgreSQL).
Lo cierto es que ambos están correctos, porque un Nulo es indefinido por lo tanto no es ni menor ni mayor que otros valores, por eso algunos DBMS agregan cláusulas NULLS FIRST y NULLS LAST para controlar donde queremos que nos ordenen los nulos.


¿Cuando tiene sentido usar Nulos?

En mi experiencia, en un modelo de datos los lugares donde tiene sentido la utilización de Nulos son los siguientes:
  • Claves Foráneas opcionales (FK). Ej: Una Factura puede tener nulo en su atributo Orden de Compra, ya que es opcional.>
  • Fechas opcionales. Ej: Fecha de cobrada una factura, mientras la factura no esté cobrada el valor es indefinido.
  • Textos o Blobs opcionales. Ej. Una descripción vacía.

¿Qué significa Empty as Null?

Esto es una propiedad que Genexus usa en los atributos para controlar si querémos que un valor vacío se almacene como NULL. Según lo expresado antes, solo algunos tipos de atributo deberían tener la propiedad Empty as NULL = Yes.


¿Y la columna Nullable en la estructura de las transacciones?

Define si el atributo admite Nulos en esa transacción. No es lo mismo que Empy as Null pero ambos juegan juntos para dar el comportamiento final.
Ej. El atributo Orden de Compra tiene Empy as null = Yes porque es clave. A su vez, es opcional en la transacción de Facturas, pero obligatorio en la transacción de Remitos.
Entonces el mismo atributo va a llevar Nullable = Yes en Facturas y Nullable = False en Remitos. Es decir, el atributo puede guardar como nulo, pero en la transacción de Remitos no admite Nulos.


Voy entendiendo la idea pero me gustaría algo más concreto

Ahí van mis sugerencias. En un modelo Genexus (extrapolable a otros modelos de datos), tengo que definir los atributos de esta forma:


Tipo de atributoEmpty as nullNullable
Identificador/Clave no importa de que tipo de datos YES YES - solo si es opcional
Fecha, Texto, Blob YES YES - solo si es opcional
Cantidad, Importe, Dimensión, Booleano, Flag NO NO


¿Para qué me sirve todo esto? ¿Por qué no defino todo nullable o todo no nullable?

Las ventajas de usar valores Nulos ya fueron explicadas, nos da una expresividad mayor, ¿Para qué voy a guardar una fecha 01/01/0001 o 31/12/1753? Es mucho mejor gurdar un NULL que deja mucho más claro que el valor está indefinido.
Otro ejemplo son los DBMS que no aceptan textos nulos, por ejemplo un VarChar en Oracle se debe guardar un nulo o al menos un espacio, pero no acepta textos vacíos.

Entonces: ¿Por qué no admitir nulos en todos lados?
La respuesta a esta pregunta tiene 2 puntas:
  • Integridad en las FK: Si yo definí que los Remitos siempre tienen que tener asociada una Orden de Compra, no quiero que se pueda agregar un registro sin Orden de Compra, entonces en ese caso defino que no puede ser Nulo. Esto es a nivel de la base de datos, pero además Genexus utiliza esta información para validar en el Form que el usuario ingrese una Orden de Compra y de lo contrario no lo deja avanzar.
  • Las operaciones aritméticas con nulos están indefinidas. Por lo tanto no es conveniento utilizarlo en atributos quantitativos ni booleanos.
    Por ejemplo: admito nulos en el Cantidad de un Producto. Cuando agregue un Producto nuevo, se va a guardar Cantidad = NULL.
    ¿Que pasa si recibo 5 unidades y las quiero sumar el Inventario?
    Aquí se abren 2 nuevas posibilidades:
    • Genexus lee el valor, suma y guarda el resultado:
      Aquí Genexus nos oculta que existía un nulo y cuando lee el valor lo lee como cero, entonces 0 + 5 = 5
    • Genexus optimiza el update y lo genera en SQL:
      Update Productos set Cantidad = Cantidad + 5 = NULL
      NULL + 5 está indefinido por lo que resulta en NULL

Conclusiones:

Es importante prestar atención a la obligatoriedad de cada atributo y el posible uso de nulos. Utilizando las reglas detalladas en el cuadro anterior se obtiene un modelo de datos y base de conocimiento enriquecidos.
Agregando:
  • Información sobre obligatoriedad de los atributos
  • Atributos sin valores por defecto no representativos (Fechas mínimas, textos con un blanco o Blobs con valor 0x00).
  • Valores cuantitativos siempre válidos y evita errores sobre operaciones aritméticas

viernes, 15 de febrero de 2013

Analizando Leaks

Hace unos años publiqué un artículo donde contaba como había encontrado leaks de una aplicación Genexus Java. Ahora me enfrento otro problema levemente distinto, nuevamente en Tomcat tengo una aplicación que aparentemente no tiene leaks, pero cuando actualizamos la versión de la aplicación (redeploy) la versión anterior queda colgada.

Esto apunta a otro tipo de leak, en el que se crea una referencia entre una clase de sistema (de las cuales es propietario el Tomcat) y un objeto de mi aplicación, con lo cual la memoria utilizada por la versión vieja de la aplicación no puede ser reciclada y a la larga se produce un error del tipo:
java.lang.OutOfMemoryError: PermGen space
  Los pasos que utilicé para detectar el problema son:
  1. Crear un dump y levantarlo en jhat explica mi post anterior.
  2. Buscar en jhat las instancias de org.apache.catalina.loader.WebappClassLoader.
  3. Abrir las instancias una por una y quedarme solo con las que tienen la ruta de mi aplicación
  4. Las instancias que no están referenciadas por la clase org.apache.catalina.loader.WebappLoader son leaks.
  5. Elegir una de las instancias para estudiarla y copiar el Id del objeto.
  6. En el menú de jhat abrir Execute Object Query Language (OQL) query y ejecutar:
    select heap.livepaths(o) from org.apache.catalina.loader.WebappClassLoader o where objectid(o) == "XXX"
    Sustituyendo XXX por el id que se quiere estudiar (ej: 0xa3ed2510).
  7. Copiar el resultado en un editor de texto
  8. Reemplazar ", " por ",\r\n" para obtener una línea por referencia
  9. Buscar referencias problemáticas:
    • Java Local Reference
    • System Class Reference
En mi caso encontré clases referenciadas por java.lang.ThreadLocal, que es uno de los casos clásicos de Leaks en Java.

El problema en particular estaba en las clases del JDBC de Oracle: oracle.sql.AnyDataFactory y oracle.jdbc.driver.OracleDiagnosabilityMBean. Cómo no se que posible arreglo tendrá, decidí mover el driver al lib global de Tomcat y de esa forma puedo hacer redeploy de la aplicación sin que el driver deje la versión vieja colgada.

Nota:
Estoy usando Tomcat 6.0.24, en versiones anteriores de Tomcat no existía la detección de Leaks, a partir de esta versión se detectan automáticamente algunos leaks y dicha información queda en el log de Tomcat. En esta versión también se resuelve de forma automática el caso típico de leak debido a que no se desregistra el driver JDBC, pero otros leaks (como los que me enfrento ahora) no se resuelven automáticamente y Tomcat no es capaz de determinar con certeza cuales son las situaciones que efectivamente se convierten en un leak.

martes, 14 de septiembre de 2010

Cambio de nombre y url

Bueno, cambié el nombre del blog y la url, los anteriores me gustaban tan poco que no voy a decir cuales eran.

Ahora es mi nombre y simplemente refleja que en este blog hay artículos sobre temas de los que me interesan. Por otro lado, el blog solía ser un tanto anónimo, capaz que por eso lo tenía descuidado, de repente de esta forma me comprometo más con el mismo.

viernes, 22 de enero de 2010

Comprimiendo el acceso a base de datos


Problema
Con aplicaciones win, a menudo ocurre que tenemos el servidor de base de datos en otro lugar fisico, conectado a traves de VPN o similar. Para este tipo de aplicaciones en Genexus se incorporó la posibilidad de generar en 3 capas, donde hay un servidor de aplicaciones que hace el trabajo pesado contra la base de datos y al cliente solo llegan los datos que se muestran al usuario (al menos en teoría).

Cuando se tiene una aplicacion 2 capas, pasar a 3 capas puede implicar muchos dolores de cabeza, principalmente por procesos que no se pueden ejecutar en el servidor porque interactuan con el usuario, ademas de pantallas que se quedan bloqueadas y otros problemas.

En más de un cliente fue necesario instalar aplicaciones Java 2 capas que se conectaban a un servidor en VPN, pero fue hace muy poco que me puse a investigar el volumen de datos que pasaba por la VPN para ver si habia forma de optimizar la performance. Ahí encontré que hay volúmenes grandes, por ejemplo reportes que trasmiten más de 1MB de datos o transacciones que dada la cantidad de consultas que hacen a la base de datos generan mucho tráfico.

Solución
Dado que Genexus es quien genera los programas, a simple vista parece que no hay mucho que podamos hacer para optimizar el tráfico en una aplicación win 2 capas, pero dimos con una solución sencilla y con un costo de implementación muy bajo, grandes beneficios y estable. Se trata de entunelar y comprimir la conexión entre la aplicación y la base de datos.

Del lado del cliente se precisa un programa que pueda escuchar una conexión TCP/IP común, comprimirla y trasmitirla a un servidor de compresión/descompresión, el primero a su vez se encarga de descomprimir la respuesta del servidor. En la otra punta de la conexión lenta (VPN) está el servidor de descompresión que hace las operaciones análogas, descomprimir las conexiones entrantes para mandarlas limpias al DBMS y comprimir la respuesta del DBMS para que vuelva al cliente.

Las 2 partes de la solución (servidor y cliente) fueron implementadas en Java, que gracias a su excelente manejo de streams y threads obtuvo una implementación muy sencilla, estable y de buen rendiminto. El algoritmo de compresión fue zlib que es parte de la api de Java, lo cual también ayudo a no agregar dependencias.

Cliente
El cliente está embebido en la aplicación y levanta automáticamente al poner el parámetro compression=true en la url de conexión. Simplemente parsea la url buscando el parámetro y si lo encuentra levanta una instancia del cliente en la PC cliente y cambia la url de conexión para que se conecte a esta instancia en vez de conectarse al servidor real.

Servidor
Se instala en el servidor de DBMS o en otro servidor que esté en la misma LAN. Para que esté siempre disponible se instaló como servicio de Windows utilizando JSL (altamente recomendable). El único requerimiento es tener una JVM instalada.

Resultados
Los resultados fueron muy buenos, en términos generales puede decirse que se bajaron a la tercera parte los tiempos de respuesta y como ya se anticipó, con una solución muy estable y con un costo bajo. Hay que tener en cuenta que en todos los clientes la velocidad no superaba los 256kbps es de suponer que a medida que se mejora la velocidad los beneficios disminuyen.

También hay que notar que estos algoritmos agregan procesamiento, especialmente al servidor que escucha las peticiones de todos los clientes. En nuestro caso no era un problema, en una parte porque los clientes no son muchos y en otra parte porque los servidores tienen buena capacidad de procesamiento.

Links de interés
IPTunnelManager - Una herramienta paga que permite hacer túneles comprimidos y encriptados, instalando uno en cada punta de la conexión lenta se podrían comprimir las conexiones al DBMS.

miércoles, 13 de mayo de 2009

Memory leaks en Java

Hace poco tuve un inconveniente desarrollando una aplicación web con Genexus/Java, la memoria que consumía el tomcat crecía hasta que daba un error java.lang.OutOfMemoryError: Java heap space.

El problema se daba durante la ejecución de un proceso batch muy largo y era claro que una vez que terminaba no liberaba la memoria que había utilizado, entonces ¿Como identificar que es lo que está quedando colgado?

Lo primero que usé fue JConsole, que una vez habilité JMX en el tomcat, me permitió ver el consumo de memoria global, como crecía y decrecía, pero seguía sin ver que objetos eran los que estaban quedando colgados.

Busqué varios profilers para Java y encontré que principalmente apuntan a mostrar los tiempos de ejecución de las operaciones, así que en este caso no me servían y me di cuenta que tenía que buscar alguna herramienta para analizar la memoria. Estoy seguro de que existen muchas y hay mejores soluciones que la que utilicé, pero lo que encontré sirvió a mis propósitos y tiene la ventaja y contra de no tener que estar analizando los datos en tiempo real.

Solución: JConsole + jhat (Java Heap Analysis Tool), ambas son herramientas que vienen con el JDK de SUN, simplemente hay que buscarlos en la carpeta bin.

jhat es un programa que dado un "memory dump" levanta un servidor web que permite navegar la memoria en dicho dump, además de presentar útiles histogramas de instancias y cantidad de memoria por clase. Pero hay que tener en cuenta que para un dump grande 100MB+ hay que pasarle parámetros para agrandar el heap del propio jhat, sino se queda sin memoria y hay que sentarse a esperar porque se toma su tiempo para procesar los datos.

Entonces los pasos a seguir son:
  1. Habilitar JMX en el Tomcat, agregar a la configuración los siguientes parámetros:
    -Dcom.sun.management.jmxremote
    -Dcom.sun.management.jmxremote.port=”9004″
    -Dcom.sun.management.jmxremote.authenticate=”false”
    -Dcom.sun.management.jmxremote.ssl=”false”
  2. Ejecutar los procesos que se sabe que dejan memoria colgada o usar la aplicación hasta que la memoria crezca considerablemente.
  3. Iniciar JConsole y conectarse al servidor en el puerto 9004.
  4. En el tab MBeans ejecutar com.sun.management.HotSoptDiagnostic.dump usando como parámetro "C:\dump.bin" (o la ruta que venga bien).
  5. Ejecutar jhat pasando como parámetro el archivo a procesar, opcionalmente se le puede agrandar la memoria de heap porque con archivos grandes puede caer.
    jhat -J-Xmx400m dump.bin
  6. Entrar a la url http://localhost:7000/ donde debería aparecer la página de bienvenida de jhat, con un resumen de las clases que hay en memoria y al final unos links a consultas preparadas. La que me resultó más útil es el histograma.
Una vez que uno entra al histograma presentado por jhat, seguramente va a encontrar algún tipo básico con mayor cantidad de instancias, por ejemplo int o char[], es lógico ya que cualquier objeto está compuesto por tipos básicos, pero inmediatamente abajo se pueden encontrar otras clases con cantidad un poco menor de instancias y probablemente éstas sean las que nos interesan.

Ahora llegamos al punto en el que identificamos cuales son los objetos que están ocupando más memoria, al hacer click en la clase que nos interesa analizar, podemos ver algunos datos interesantes de la misma y debajo una lista de todas las instancias de dicha clase, así que elegimos una instancia cualquiera y podemos empezar a navegar a través de sus referencias, para encontrar el objeto raíz que está dejando las instancias colgadas.

Una vez identificado el objeto, estamos listos para corregir el error, o como en mi caso quejarnos a soporte y tratar de encontrar un workaround!

jueves, 11 de diciembre de 2008

Streaming en Genexus

Bueno, en serio, prometo escribir más seguido. Hace unos meses un colega me dijo que tenía que empezar a escribir, que es sencillo, todos los días uno siempre resuelve algún problema y bueno escribir sobre eso.

Ahora escribo sobre algo que me pareció importante hace unos meses, no se si el término Streaming aplica totalmente, pero pienso que se acerca bastante.

Cuando usamos Genexus para realizar reportes en web, lo más común es realizar los reportes en PDF, ya que de está forma el usuario puede verlo y después decidir que hacer con él, guardarlo, imprimirlo o descartarlo.

Ahora, las aplicaciones que hacemos no son de juguete, son de negocio y a veces tenemos reportes de algunos cientos de páginas.

Por suerte Genexus nos ofrece la opción de generar los reportes directo por el protocolo HTTP y uno piensa, ¡que suerte! no tengo que guardar un archivo grandecito en el servidor, para que después el usuario lo baje, lo cual generaría un overhead bastante grande en el servidor.

Lamentablemente, si vien Genexus nos ofrece la opción de generar los reportes por HTTP, no hace streamming. ¿Que quiero decir con esto? Bueno, Genexus calcula todo el reporte en memoria y luego lo devuelve de un saque, lo cual puede ser aún peor para la escalabilidad del servidor. ¡Por favor corriganmé si me equivoco, pero busqué por todos lados y aparentemente este siempre es el comportamiento!

Lamentablemente esto no se limita sólo a los reportes PDF, supongamos que queremos exponer un procedimiento que devuelve un XML "gigante" para hacer interfaz con otro sistema. Bueno lógicamente empezamos por abrir un stream XML en el Response HTTP y luego vamos escribiendo ese XML, en este caso sería muy lógico que el XML se fuera mandando al cliente a medida que se va generando, pero nuevamente Genexus genera todo el XML en memoria y luego lo devuelve.

De repente lo ideal no es que el comportamiento siempre sea hacer streaming, ya que una de las principales virtudes de Genexus es ocultar tecnicismos al programador. Y por ejemplo cuando se trabaja con streaming hay que tener cuidado de escribir los cabezales antes de empezar a hacer el streaming. Pero si estaría bueno tener una opción de streaming (debidamente documentada con esos tecnicismos).