La forma correcta de realizar el SWEEP

Deja un comentario

Una de las tareas de mantenimiento que se deben realizar en las bases de datos, especialmente en las que tienen tablas con muchos millones de filas, es la del sweep.

Pero ¿cuál es la manera correcta de realizar esa tarea?

Primero, debemos recordar que el sweep se encarga de eliminar toda la basura que se haya acumulado dentro de la Base de Datos a causa de la ejecución de los comandos UPDATE y DELETE. Siempre que se ejecuta el comando UPDATE o el comando DELETE se deja basura dentro de la Base de Datos, sin importar que la transacción haya finalizado con un COMMIT o con un ROLLBACK; por lo tanto, en algún momento deberemos eliminar esa basura.

Segundo, un sweep automático es realizado por el Firebird cada vez que creamos un backup usando el programa GBAK. Por lo tanto, no siempre es necesario realizar un sweep manual, aunque debemos recordar que el sweep automático también puede fallar y por lo tanto también deberemos verificarlo.

Tercero, tanto si realizamos un sweep manual como un sweep automático debemos comprobar que fue completado exitosamente. ¿Por qué? Porque si no fue completado exitosamente entonces dentro de nuestra Base de Datos habrá quedado mucha basura y eso hará que las tareas que se realicen en ella sean mucho más lentas de lo que deberían ser.

Pasos a seguir:

  1. Verificar si es necesario realizar el sweep
  2. Realizar el sweep
  3. Comprobar si el sweep fue completado exitosamente
  4. Si descubrimos que hay problemas, buscar y corregir esos problemas

Paso 1. Verificar si es necesario realizar el sweep

Desde luego que este paso solamente lo hacemos antes de realizar un sweep manual. El programa GSTAT con la opción -h nos da la información que estamos necesitando.

sweep01

Captura 1. Si haces clic en la imagen la verás más grande

En la Captura 1. vemos que la diferencia entre la Oldest Transaction y la Next Transaction es muy grande. Eso puede ocurrir normalmente y no está mal que ocurra cuando hay muchos usuarios conectados y trabajando con la Base de Datos. Pero si no hay otros usuarios conectados o hay muy pocos usuarios conectados es un indicador de que hay mucha basura acumulada dentro de la Base de Datos. Una regla rápida que podemos usar es la siguiente: “si hay hasta 10 transacciones faltantes por cada usuario conectado, es aceptable”. ¿Por qué? porque en general los usuarios ejecutan muchos comandos INSERT y muchos comandos SELECT, y muy pocos comandos UPDATE o DELETE. Estos dos últimos comandos son los que colocan basura dentro de la Base de Datos. Desde luego que esta regla rápida no se aplica a todos los casos, ya que cada caso es un caso, y habrá aplicaciones que realizan legítimamente muchos UPDATE y muchos DELETE y así en lugar de 10 transacciones faltantes por cada usuario la cantidad podría ser de 100, de 1000, o incluso más.

En el caso de la Captura 1. vemos que la Oldest Transaction es 60325 y que la Next Transaction es 71820, siendo la diferencia entre ellas de 11495, un número muy grande de transacciones faltantes. Como la aplicación que usa esa Base de Datos realiza pocos UPDATE y pocos DELETE entonces es un indicador de que un sweep es requerido.

Paso 2. Realizar el sweep

Un sweep manual se realiza mediante el programa GFIX con la opción -sweep

sweep02

Captura 2. Si haces clic en la imagen la verás más grande

Como puedes ver, el programa GFIX no te da información sobre la tarea que realizó. Lo positivo es que si no muestra un mensaje significa que finalizó ok.

Paso 3. Comprobar si el sweep fue completado exitosamente

Después de haber ejecutado el programa GFIX con la opción –sweep debemos conectarnos a nuestra Base de Datos, iniciar una transacción, y finalizar esa transacción con el comando COMMIT. Esto es necesario para que se muevan los identificadores de las transacciones.

 sweep04

Captura 3. Si haces clic en la imagen la verás más grande

Desde luego que no es necesario usar el programa ISQL para iniciar una transacción y finalizarla con un COMMIT. Cualquier programa que pueda conectarse a la Base de Datos servirá muy bien. Lo importante es iniciar una transacción y finalizarla con un COMMIT, para así mover los identificadores de las transacciones. Cual programa usar es lo de menos.

sweep05

 

Captura 4. Si haces clic en la imagen la verás más grande

En la Captura 4. hemos vuelto a ejecutar el programa GSTAT con la opción -h y así podemos observar que los identificadores de las transacciones están todos ok. Los identificadores están todos ok cuando la diferencia entre ellos es de 1 ó máximo de 2.

Paso 4.  Si descubrimos que hay problemas, buscar y corregir esos problemas

Si los identificadores de las transacciones no están todos ok (o sea, la diferencia entre ellos es más que 2 y no había otros usuarios conectados a la Base de Datos cuando se realizó el sweep), eso significa que el sweep no eliminó a toda la basura. ¿Y por qué no eliminó a toda la basura? La razón más frecuente es que hay una transacción (o más de una transacción) cuyo acceso es READ WRITE (o sea, que la transacción puede escribir en la Base de Datos) y esa transacción hace mucho tiempo que se inició y aún no ha finalizado con un COMMIT ni con un ROLLBACK. Otra razón, mucho más dolorosa, es que la Base de Datos está corrupta.

En estas circunstancias (es decir, cuando los identificadores no están todos ok) debemos ejecutar el programa GFIX con las opciones -validate y -full

sweep06

Captura 5. Si haces clic en la imagen la verás más grande

Si queremos conocer cuales son las transacciones que hace mucho tiempo se iniciaron y aún no han finalizado ni con un COMMIT, ni con un ROLLBACK, podemos escribir el siguiente comando:

Listado 1.

SELECT
   MON$TRANSACTION_ID,
   MON$TIMESTAMP
FROM
   MON$TRANSACTIONS
ORDER BY
   MON$TRANSACTION_I

Y para eliminar a la transacción problemática, podemos escribir:

Listado 2.

DELETE FROM
   MON$TRANSACTIONS
WHERE
   MON$TRANSACTION_ID = NúmeroTransacciónProblemática

Si las transacciones problemáticas son varias, entonces podríamos necesitar repetir estos pasos varias veces.

Conclusión:

Tanto si se realiza un sweep manual como un sweep automático, debemos verificar que al finalizar los identificadores de las transacciones estén todos ok. Los identificadores de las transacciones están todos ok cuando la diferencia entre ellos es de 1 ó de 2, cuando no hay otros usuarios conectados a la Base de Datos. Si los identificadores no están ok, entonces debemos validar la Base de Datos, buscar las transacciones que se están demorando mucho en finalizar y eliminar a esas transacciones.

Artículos relacionados:

Entendiendo los identificadores de las transacciones

Entendiendo sweep y garbage collection

El índice del blog Firebird21

El foro del blog Firebird21

45 formas de aumentar la velocidad de las bases de datos de Firebird

Deja un comentario

En un artículo (en inglés) publicado por la empresa IBSURGEON se detallan 45 formas de aumentar la velocidad de las operaciones que realizan los usuarios en una Base de Datos de Firebird.

El artículo en cuestión puedes encontrarlo aquí:

45 ways to speed-up Firebird database

como verás, casi todas esas formas ya fueron motivo de algún artículo de este blog. De todas maneras, siempre es importante leerlo y si tienes algún comentario será bueno conocerlo.

Artículos relacionados:

El índice del blog Firebird21

El foro del blog Firebird21

 

 

Evitando confusiones con las fechas

Deja un comentario

¿En Firebird la fecha 06/05/2016 se refiere al 6 de mayo de 2016 o se refiere al 5 de junio de 2016? ¿Estás seguro? ¿Estás totalmente seguro? ¿Y la fecha 06-05-2016?

Ya habíamos visto en este artículo como se formatean las fechas en Firebird:

Guardando fechas

para evitar confusiones lo más recomendable es usar el nombre del mes en letras. O las tres primeras letras del nombre del mes.

Si escribimos:

Listado 1.

SELECT
   *
FROM
   VENTAS
WHERE
   VEN_FECHAX = '06/MAY/2016'

no hay confusión posible, es evidente que nos estamos refiriendo al 6 de mayo de 2016, jamás podríamos confundirnos con el 5 de junio de 2016.

Por eso, lo recomendable es usar los nombres de los meses con letras, no con números.

FECHA-1

Tabla 1. Si haces clic en la imagen la verás más grande

Podemos usar los nombres de los meses en inglés o sus abreviaturas, inclusive mezclando mayúsculas con minúsculas. O sea que cualquiera de las siguientes alternativas funcionarán bien:

MiFecha = ’28/September/2016′

MiFecha = ‘September/28/2016′

MiFecha = ’28/SEPTEMBER/2016′

MiFecha = ’28/SEPTember/2016’

MiFecha = ‘SEP/28/2016′

MiFecha = ’28/SEP/2016’

El autor de este blog prefiere la última alternativa porque se escribe poco y se entiende perfectamente, claro que tú puedes elegir cualquiera de las otras pero lo importante es usar letras para identificar a los meses, no números.

Artículos relacionados:

Guardando fechas

El índice del blog Firebird21

El foro del blog Firebird21

 

Entendiendo la memoria caché

1 comentario

Este asunto de la memoria caché que usa el Servidor del Firebird parece que no está muy claro, así que trataré de explicarlo en este artículo.

¿Qué es la memoria caché?

Es una porción de la memoria RAM de esa computadora donde se encuentra el Servidor del Firebird.

¿Para qué sirve la memoria caché?

Para devolver muy rápidamente los resultados de los SELECT.

¿Qué se coloca en la memoria caché?

Las páginas que fueron leídas para devolver los resultados que necesita un SELECT.

¿Cómo se obtienen los resultados que necesita un SELECT?

Extrayendo de la Base de Datos toda la página (o páginas) donde se encuentra la fila deseada.

Por ejemplo, si el tamaño de la página es de 4.096 bytes y cada fila de la tabla CLIENTES ocupa 100 bytes entonces en cada página de la tabla CLIENTES se podrán colocar 4.096 / 100 = 40 filas.

En una página jamás se mezclan datos de varias tablas. Si una página se reservó para ser usada por la tabla CLIENTES entonces solamente filas de la tabla CLIENTES podrá contener esa página.

En nuestro ejemplo, cada página podrá contener 40 filas (o registros) de la tabla CLIENTES.

Si escribimos un SELECT como el siguiente:

Listado 1.

SELECT
   *
FROM
   CLIENTES
WHERE
   CLI_IDENTI = 12345

Nuestro SELECT nos devolverá una sola fila, la fila cuyo identificador es 12345, pero extrajo de la Base de Datos toda la página donde se encuentra esa fila, o sea que extrajo 40 filas aunque solamente 1 de esas filas nos muestre.

Pero ahora las 40 filas, la que nos mostró y las 39 restantes que no nos mostró, están en la memoria caché.

 ¿Por qué se usa la memoria caché?

Porque para cualquier programa de computadora es muchísimo más rápido leer algo que se encuentra en la memoria RAM que leer algo que se encuentra en el disco duro. Extraer de la memoria RAM es miles de veces más rápido que extraer del disco duro.

Si ahora ejecutamos este SELECT:

Listado 2.

SELECT
   *
FROM
   CLIENTES
WHERE
   CLI_IDENTI = 12346

Y la fila cuyo identificador es 12346 se encuentra en la misma página que la fila cuyo identificador es 12345 entonces la fila que tiene el identificador 12346 ya está en la memoria caché. Y como ya está en la memoria caché entonces el Servidor del Firebird extraerá de allí los datos de esa fila, no los obtendrá del disco duro. En consecuencia, devolverá esos datos muy rápidamente.

¿Cómo se diferencian Classic y SuperServer en el uso de la memoria caché?

En Classic cada conexión usa su propia memoria caché. En nuestro ejemplo, si Juan ejecutó el SELECT del Listado 1. y luego María ejecutó el SELECT del Listado 2. entonces María no se benefició del SELECT ejecutado por Juan, ya que la memoria caché de María es totalmente independiente de la memoria caché de Juan. Pero si Juan ejecutó el SELECT del Listado 1. y después Juan también ejecutó el SELECT del Listado 2. entonces sí Juan se benefició de ya tener en su memoria caché los datos que necesitaba.

En SuperServer la memoria caché es compartida entre todas las conexiones. En nuestro ejemplo, si Juan ejecutó el SELECT del Listado 1. y después María ejecutó el SELECT del Listado 2. entonces María obtuvo un gran beneficio: los datos que necesitaba ya se encontraban en la memoria caché y por eso los recibió muy rápidamente.

 ¿Cómo se establece el tamaño de la memoria caché?

El tamaño de la memoria caché se calcula con esta fórmula:

Tamaño_memoria_caché = Cantidad_de_páginas * Tamaño_de_cada_página

La Cantidad_de_páginas debe estar entre 0 y 131.072. El Tamaño_de_cada_página debe ser 4.096 bytes, 8.192 bytes, ó 16.384 bytes.

El Tamaño_memoria_caché por supuesto no debe ser mayor que la memoria RAM libre disponible en la computadora donde está instalado el Servidor del Firebird.

¿Cómo se elige el tamaño de cada página?

Al crear la Base de Datos se puede elegir el tamaño de sus páginas, que como ya vimos debe ser uno de los siguientes: 4.096, 8.192, 16.384

Si no se eligió uno de ellos entonces el Servidor del Firebird usará 4.096

Se puede cambiar el tamaño de cada página haciendo un ciclo backup/restore. Al hacer el restore tenemos la opción de elegir el tamaño que tendrá cada página de la Base de Datos restaurada.

¿Cómo se elige la cantidad de páginas?

Primero, debemos recordar que esa cantidad debe estar entre 50 y 131.072

Segundo, no debemos sobrepasar la memoria RAM disponible.

Ejemplo: la computadora donde se instaló el Servidor del Firebird tiene 32 Gb de memoria RAM. Dejaremos 16 Gb para ser usados por el Sistema Operativo y los demás programas. Por lo tanto, para nuestra memoria caché reservaremos los restantes 16 Gb.

Nuestra Base de Datos tiene páginas de 4.096 bytes

Memoria RAM disponible = 16 Gb = 16 * 1.024 * 1.024 * 1.024 = 17.179.869.184 bytes

Tamaño de cada página = 4.096 bytes

Cantidad de páginas = 4.194.304

No podemos usar 4.194.304 páginas, la cantidad máxima que podemos usar es 131.072, el Firebird no nos permite usar más.

Y como cada página ocupa 4.096 bytes entonces nuestra memoria caché puede ocupar como máximo 131.072 * 4.096 = 536.870.912 bytes = 0,5 Gb

Si la arquitectura es SuperServer y el tamaño de cada página es de 4.096 bytes entonces el caché no puede ser mayor que 0,5 Gb. O sea, que tener disponibles 16 Gb de RAM es un gran desperdicio.

Podemos aumentar el tamaño del caché usado por SuperServer si aumentamos el tamaño de cada página. El tamaño de página máximo que podemos usar es 16.384 bytes, entonces: 131.072 * 16.384 = 2.147.483.648 bytes = 2 Gb

En otras palabras, si la arquitectura es SuperServer la cantidad máxima de memoria RAM del Servidor que podemos usar es de 2 Gb. Ni un byte más.

Entonces, si tenemos la posibilidad de usar más de 2 Gb de memoria RAM (como en este ejemplo) lo que debemos hacer es cambiar la arquitectura a Classic.

En Classic la memoria caché es por conexión. O sea que en nuestros cálculos debemos considerar la cantidad de conexiones concurrentes que podemos tener como máximo. No la cantidad normal de conexiones ni la cantidad mínima de conexiones, sino la cantidad máxima de conexiones que podríamos tener.

Por ejemplo, tenemos 70 usuarios pero algunos se conectan más de una vez al mismo tiempo, hemos determinado que nunca hay más de 90 conexiones concurrentes pero por un motivo de seguridad supondremos que son 100.

El tamaño de  cada página de nuestra Base de Datos es de 4.096 bytes.

Entonces:

16 Gb / 100 / 4.096 = 16 * 1.024 * 1.024 * 1.024 / 100 / 4.096 = 41.943 páginas

41.943 páginas * 4.096 bytes = 171.798.691 bytes

O sea, que podremos tener hasta 100 conexiones concurrentes, cada una de ellas usando 171.798.691 bytes de la memoria RAM del Servidor como su propia memoria caché.

Y como 171.798.691 bytes de memoria para cada conexión es muchísimo, entonces todos los SELECT de todos los usuarios serán rapidísimos.

¿Dónde se especifica la cantidad de páginas que debe usar el Servidor del Firebird?

En el archivo FIREBIRD.CONF, que encontrarás en la misma carpeta donde instalaste al Firebird. En ese archivo existe una entrada llamada DefaultDbCachePages. Debes borrar el símbolo de numeral que se encuentra a la izquierda (el símbolo de numeral indica que todo lo que se encuentra a su derecha es un comentario) y escribir la cantidad de páginas que quieres tener.

CACHE-1

Captura 1. Si haces clic en la imagen la verás más grande

CUIDADO: La cantidad de páginas es por Base de Datos. Eso significa que si puedes tener 2 bases de datos abiertas debes dividir la cantidad calculada por 2. Si puedes tener 3 bases de datos abiertas debes dividir la cantidad calculada por 3, y así sucesivamente.

En el archivo FIREBIRD.CONF se especifica la cantidad de páginas que usarán todas las bases de datos, o sea que es el valor por defecto, el que se usará si no se elige otra cantidad. Si queremos especificar para una Base de Datos en particular entonces usamos al programa GFIX.EXE para eso. Su opción -buffers nos permite determinar la cantidad de páginas que usará esa Base de Datos.

CACHE-2

Captura 2. Si haces clic en la imagen la verás más grande

En la Captura 2. le estamos diciendo que use 1.200 páginas para la Base de Datos BLOG_DB.FDB

CACHE-3

Captura 3. Si haces clic en la imagen la verás más grande

Y en la Captura 3. podemos ver la cantidad de páginas (Page buffers) usadas y el tamaño (Page size) de cada página. Entonces, 1.200 * 4.096 = 4.915.200 bytes

Si la arquitectura es Classic entonces esos 4.915.200 bytes serán para cada conexión y si la arquitectura es SuperServer entonces esos 4.915.200 bytes serán para todas las conexiones.

¿Cómo se coloca a una tabla en la memoria caché?

En la memoria caché se colocan las páginas que necesitó un SELECT. No se colocan datos allí cuando se ejecuta un INSERT, un UPDATE, o un DELETE. Solamente cuando se ejecuta un SELECT. Para colocar a toda una tabla en la memoria caché podemos hacerlo mediante SELECT * FROM MiTabla o mediante SELECT COUNT(*) FROM MiTabla. Esta última opción es preferible porque es mucho más rápida que la primera. ¿El motivo? Pone a cada página de MiTabla en la memoria caché pero no devuelve el contenido de esas páginas al Cliente.

Resumiendo:

  • La memoria caché es una porción de la memoria RAM de la computadora donde se instaló al Servidor del Firebird
  • Se usa la memoria caché para devolver muy rápidamente los resultados de los SELECT
  • Si los datos que debe devolver un SELECT se encuentran en la memoria caché obtenerlos de allí será mucho más rápido que obtenerlos del disco duro
  • En la memoria caché no se colocan filas ni tablas, se colocan páginas
  • Cuando se ejecuta un SELECT el Servidor del Firebird no extrae de la Base de Datos las filas que necesita ese SELECT sino las páginas donde se encuentran esas filas
  • En Classic la memoria caché de cada conexión es independiente de la memoria caché de las otras conexiones. En SuperServer la memoria caché es compartida entre todas las conexiones.
  • Si el tamaño de la Base de Datos es menor que 2 Gb entonces es recomendable que la arquitectura sea SuperServer. Si el tamaño de la Base de Datos es mayor que 2 Gb entonces es recomendable que la arquitectura sea Classic.
  • En el archivo FIREBIRD.CONF se puede especificar la cantidad de páginas que usará cada Base de Datos. Para una Base de Datos en particular se puede cambiar la cantidad de páginas usando el programa GFIX.EXE con la opción -buffers
  • El tamaño de cada página de la Base de Datos se determina cuando es creada, puede ser cambiado más adelante mediante un ciclo backup/restore
  • Los comandos INSERT, UPDATE, y DELETE no colocan datos en la memoria caché. Solamente el comando SELECT lo hace
  • Para poner a toda una tabla en la memoria caché se puede escribir: SELECT COUNT(*) FROM MiTabla. Ese comando colocará a cada página de MiTabla en la memoria caché.

Artículos relacionados:

Entendiendo las páginas de la Base de Datos

Eligiendo el tamaño adecuado de las páginas de la Base de Datos

SuperServer: optimizando el tamaño del caché

Classic: optimizando el tamaño del caché

Optimizando SuperServer: poniendo toda la Base de Datos en la memoria caché

El índice del blog Firebird21

El foro del blog Firebird21

 

Optimizando SuperServer: poniendo toda la Base de Datos en la memoria caché

5 comentarios

Hay un truco que podemos utilizar para optimizar a SuperServer, funciona también en Classic pero solamente con bases de datos mucho más pequeñas.

Como recordarás, si las filas que devolverá un SELECT ya se encuentran en la memoria caché entonces la velocidad de respuesta será muy alta porque la memoria RAM es muchísimo más rápida que el disco duro. Entonces, la idea es tener a toda la Base de Datos (o al menos a las tablas más utilizadas cuando tener a toda la Base de Datos no es posible) en la memoria caché.

Para poner a toda la Base de Datos en la memoria caché podríamos ejecutar el siguiente stored procedure:

Listado 1.

CREATE PROCEDURE LLENAR_CACHE
AS
   DECLARE VARIABLE lcNombreTabla   VARCHAR(1024);
   DECLARE VARIABLE lcComando       VARCHAR(1024);
   DECLARE VARIABLE lnCantidadFilas INTEGER;
BEGIN
   
   FOR SELECT
      RDB$RELATION_NAME
   FROM
      RDB$RELATIONS
   INTO
      :lcNombreTabla
   DO BEGIN
      lcComando = 'SELECT COUNT(*) FROM ' || lcNombreTabla;
      EXECUTE STATEMENT lcComando INTO :lnCantidadFilas;
   END

END;

Luego de ejecutar el comando EXECUTE PROCEDURE LLENAR_CACHE y el comando COMMIT, toda la Base de Datos se encontrará en la memoria caché (si hay suficiente espacio en la memoria caché, por supuesto). ¿Por qué? Porque cada vez que se lee una página de una tabla esa página es puesta en la memoria caché. La función COUNT(*) lee cada página de una tabla y por consiguiente coloca a cada página de esa tabla en la memoria caché. El stored procedure LLENAR_CACHE realiza esa operación para todas las tablas de la Base de Datos. Como resultado final, todas las páginas de todas las tablas se encontrarán en la memoria caché, si es que hay allí suficiente espacio libre.

¿Y si no hay suficiente espacio libre?

Entonces podríamos poner en la memoria caché a las tablas más utilizadas en los informes.

Listado 2.

CREATE PROCEDURE LLENAR_CACHE
AS
BEGIN
   SELECT COUNT(*) FROM BANCOS;
   SELECT COUNT(*) FROM PRODUCTOS;
   SELECT COUNT(*) FROM CLIENTES;
   SELECT COUNT(*) FROM PROVEEDORES;
   SELECT COUNT(*) FROM STOCK;
   SELECT COUNT(*) FROM MONEDAS;
END; 

Eso hará que todas las operaciones de lectura se realicen súper rápido. Pero durante el trabajo normal diario muchas operaciones de INSERT, UPDATE, y DELETE se irán ejecutando y en la memoria caché ya no se encontrarán las últimas filas insertadas, actualizadas, y borradas.

¿Y entonces?

Entonces lo que debemos hacer es volver a ejecutar el stored procedure LLENAR_CACHE para que nuevamente en la memoria caché se encuentre toda la Base de Datos (como en el caso del Listado 1.) o las tablas más utilizadas (como en el caso del Listado 2.).

Ese proceso podríamos hacerlo manualmente (por ejemplo, cada hora) pero es mucho más inteligente pedirle al Sistema Operativo que realice esa tarea.

Para ello crearemos un archivo de script y un archivo batch.

Listado 3.

CONNECT MiBaseDatos.FDB USER SYSDBA PASSWORD masterkey;

-- Primera vez
EXECUTE PROCEDURE LLENAR_CACHE;
COMMIT;
SHELL PING 192.0.2.2 -n 1 -w 36000 > NUL;

-- Segunda vez
EXECUTE PROCEDURE LLENAR_CACHE;
COMMIT;
SHELL PING 192.0.2.2 -n 1 -w 5000 > NUL;

-- Finalizar
EXIT;

Escribimos el contenido del Listado 3. en el bloc de notas y lo grabamos con el nombre LLENAR_CACHE.SQL

¿Qué hace este archivo de script?

Primero, se conecta a nuestra Base de Datos. Segundo, ejecuta el stored procedure llamado LLENAR_CACHE. Tercero, para finalizar la transacción hace un COMMIT. Cuarto, ejecuta el comando PING del Sistema Operativo donde la dirección IP mostrada es una IP que nunca existe y se usa normalmente para pruebas, la opción -n indica la cantidad de repeticiones, la opción -w indica la cantidad de milisegundos (36.000 milisegundos = 1 hora), y al redirigir a NUL no se ve salida en la pantalla.

En el Listado 3. el stored procedure LLENAR_CACHE se ejecutó 2 veces, con una hora de diferencia entre esas ejecuciones. Si la Base de Datos se usará durante 10 horas seguidas cada día, entonces deberíamos escribir 10 veces el EXECUTE PROCEDURE, el COMMIT, y el SHELL. Para que el Listado 3. no sea muy largo solamente escribí 2 veces esos comandos, en tu caso podrías necesitar escribirlos 8 veces, 10 veces, 14 veces, etc.

Listado 4.

C:
CD "\ARCHIVOS DE PROGRAMA\FIREBIRD\FIREBIRD_2_5\BIN"
ISQL -INPUT C:\USERS\WALTER\DESKTOP\LLENAR_CACHE.SQL

¿Qué hace este archivo batch?

Primero, se ubica en la unidad C:. Segundo, se ubica en la carpeta donde se encuentra el programa ISQL.EXE. Tercero, ejecuta el programa ISQL.EXE con la opción -INPUT y el nombre completo del archivo de script.

En síntesis, ejecuta al programa ISQL.EXE pidiéndole que ejecute los comandos que se encuentran en el archivo de script.

¿Cómo ejecutamos ese archivo batch?

Tenemos dos formas:

  • Manualmente
  • Automáticamente

Manualmente sería haciendo clic en él cada día antes de que el primer usuario empiece a trabajar, por ejemplo a las 7:30 de cada día. El problema es que podríamos olvidarnos de hacer ese clic o llegamos tarde al trabajo o estamos de vacaciones, etc.

Automáticamente sería mediante el Programador de Tareas del Windows. Podríamos programar una tarea que se ejecute cada vez que se enciende el Servidor o cada día a las 7:30. También podríamos hacerlo en nuestra aplicación: si detectamos que nadie está usando la Base de Datos entonces ejecutamos el archivo batch.

Conclusión:

Si la arquitectura que usamos es SuperServer entonces podemos conseguir que los resultados de las consultas sean rapidísimos poniendo a toda la Base de Datos en la memoria caché. Si no tenemos suficiente memoria para poner a toda la Base de Datos en la memoria caché, entonces pondremos allí a las tablas más utilizadas.

Dependiendo de nuestro caso crearíamos un stored procedure como el mostrado en el Listado 1. o como el mostrado en el Listado 2.

Luego creamos un archivo de script como el mostrado en el Listado 3., el cual se encargará de llenar la memoria caché. Como los usuarios estarán insertando, actualizando, y borrando filas de las tablas el contenido de la memoria caché se irá quedando desactualizado. Por eso cada hora (o el tiempo que te parezca conveniente) se vuelve a llenar la memoria caché con el contenido actual de cada tabla.

Finalmente, creamos un archivo batch que se encargará de ejecutar al programa ISQL.EXE teniendo como entrada al archivo de script. Ese archivo batch debería ser ejecutado cada día antes de que el primer usuario se conecte a la Base de Datos. Esto podríamos hacerlo manualmente o mucho mejor, automáticamente.

Artículos relacionados:

El índice del blog Firebird21

El foro del blog Firebird21

 

Classic: optimizando el tamaño del caché

Deja un comentario

Podemos conseguir que las operaciones ejecutadas por los usuarios se realicen mucho más rápido si el tamaño del caché está optimizado.

Recuerda: este artículo se refiere a la arquitectura Classic, en otro artículo se explica como optimizar para SuperServer.

El tamaño del caché se calcula de la siguiente manera:

Tamaño_caché = Cantidad_de_páginas * Tamaño_de_cada_página

En Classic la memoria caché es exclusiva para cada conexión. Eso significa que si hay 1 conexión se usará x memoria, si hay 2 conexiones se usará 2x memoria, si hay 3 conexiones se usará 3x memoria, etc.

El que cada usuario use su propia memoria caché tiene su ventaja y su desventaja.

La ventaja es que cada conexión es independiente de las demás. Si una conexión está usando mucha memoria RAM las demás conexiones ni se enterarán, no se verán afectadas por eso.

La desventaja es que si en la conexión 1 se ejecutó un SELECT y en la conexión 2 se ejecuta luego ese mismo SELECT, la conexión 2 no se beneficiará.

¿Y cómo determinamos el tamaño de la memoria caché?

Mediante dos variables: la cantidad de páginas y el tamaño de cada página de la Base de Datos

Los tamaños de las páginas actualmente pueden ser: 4096 bytes, 8192 bytes, 16384 bytes

Como sugerencia (no es obligatorio hacerlo así, aunque se obtienen buenos resultados) podríamos determinar el tamaño de cada página según estas reglas:

  1. Tamaño de la Base de Datos menor que 128 Mb, usar 4096 bytes
  2. Tamaño de la Base de Datos entre 128 Mb y 1 Gb, usar 8192 bytes
  3. Tamaño de la Base de Datos mayor que 1 Gb, usar 16384 bytes

Ejemplo:

Tenemos una Base de Datos cuyo tamaño es de 15 Gb, cada página ocupa 16.384 bytes, hay 90 usuarios concurrentes y una memoria RAM de 32 Gb.

Primero, debemos tomar en cuenta que no toda la memoria RAM estará disponible para el Servidor del Firebird, porque el Sistema Operativo y otros programas usarán una porción de esa memoria RAM. Para este ejemplo supongamos que queremos reservar 16 Gb para uso del Servidor del Firebird.

Segundo, por defecto para Classic se usan 75 páginas por cada conexión. Eso implica que cada conexión usaría 75 * 16.384 bytes, o sea 1.228.800 bytes.

Calculando la cantidad de páginas:

Cantidad_páginas = Memoria_caché / Cantidad_usuarios / Tamaño_cada_página

Cantidad_páginas = 16 Gb / 90 / 16.384 = 16 * 1.024 * 1.024 * 1.024 / 90 / 16.384 = 11.650

Aunque ahora los usuarios son 90, por un motivo de seguridad es mejor usar un número más alto, la cantidad de usuarios podría aumentar en el futuro próximo y es mejor prever eso, entonces supondremos que los usuarios serán 100.

Cantidad_páginas = 16 Gb / 100 / 16.384 = 16 * 1.024 * 1.024 * 1.024 / 100 / 16.384 = 10.485

El Firebird requiere que la cantidad de páginas se encuentre entre 50 y 131.072, y como 10.485 está en el rango admitido no tendremos problemas.

Cada conexión usará entonces 10.485 * 16.384 bytes, o sea 171.786.240 bytes. Lo cual es más que suficiente para la gran mayoría de las aplicaciones y de los usuarios. Muy raramente alguien podría necesitar más (y en ese caso el Servidor del Firebird deberá realizar parte del procesamiento en el disco duro, lo cual demorará la entrega del resultado).

Colocando a toda la Base de Datos en la memoria caché

Si toda nuestra Base de Datos está en la memoria caché entonces todos los SELECT y todos los procesos serán rapidísimos porque los datos necesarios se extraerán de la memoria RAM, no del disco duro. Y la memoria RAM es muchísimo más rápida que el disco duro.

Una página de la Base de Datos se coloca en la memoria caché cuando es leída. Eso implica que si leemos todas las páginas de una Base de Datos y tenemos suficiente memoria caché entonces toda la Base de Datos estará en la memoria caché. ¿Verdad? Es muy sencillo.

Y para leer cada página de cada tabla lo más simple es usar la función agregada COUNT(). Cada vez que escribimos un SELECT COUNT(*) FROM MiTabla todas las páginas de MiTabla son colocadas en la memoria caché (si hay suficiente espacio disponible, desde luego).

Listado 1.

CREATE PROCEDURE LLENAR_CACHE
AS
   DECLARE VARIABLE lcNombreTabla   VARCHAR(1024);
   DECLARE VARIABLE lcComando       VARCHAR(1024);
   DECLARE VARIABLE lnCantidadFilas INTEGER;
BEGIN
   
   FOR SELECT
      RDB$RELATION_NAME
   FROM
      RDB$RELATIONS
   INTO
      :lcNombreTabla
   DO BEGIN
      lcComando = 'SELECT COUNT(*) FROM ' || lcNombreTabla;
      EXECUTE STATEMENT lcComando INTO :lnCantidadFilas;
   END

END;

Explicación:

En la tabla del sistema llamada RDB$RELATIONS se encuentran los nombres de todas las tablas de la Base de Datos. Mediante un FOR SELECT se obtiene cada uno de esos nombres y se lo usa para hallar la cantidad de filas de la tabla.

Despues de escribir EXECUTE PROCEDURE LLENAR_CACHE y su correspondiente COMMIT, todas las páginas de todas las tablas estarán dentro de la memoria caché (desde luego, si hay suficiente espacio en la memoria caché).

Colocando parte de la Base de Datos en la memoria caché

Si la arquitectura es Classic muy raramente podremos poner a toda la Base de Datos en la memoria caché de cada conexión. Como vimos en este ejemplo, el tamaño máximo de una Base de Datos tendría que ser de 171.786.240 bytes lo cual para los estándares actuales es muy poco.

Si nuestra Base de Datos es muy grande no podremos ponerla completa dentro de la memoria caché, en esos casos lo mejor es colocar allí a las tablas más usadas, escribiendo algo como:

Listado 2.

SELECT COUNT(*) FROM PRODUCTOS;
SELECT COUNT(*) FROM CLIENTES;
SELECT COUNT(*) FROM PROVEEDORES;
SELECT COUNT(*) FROM BANCOS;
COMMIT;

Artículos relacionados:

El índice del blog Firebird21

El foro del blog Firebird21

SuperServer: optimizando el tamaño del caché

8 comentarios

Podemos conseguir que las operaciones ejecutadas por los usuarios se realicen mucho más rápido si el tamaño del caché está optimizado.

Recuerda: este artículo se refiere a la arquitectura SuperServer, en otro artículo se explica como optimizar para Classic.

El tamaño del caché se calcula de la siguiente manera:

Tamaño_caché = Cantidad_de_páginas * Tamaño_de_cada_página

En SuperServer la memoria caché es compartida por todas las conexiones. Eso significa que tanto si hay 1 usuario conectado, ó 12 usuarios conectados, ó 247 usuarios conectados, la memoria RAM que se usará en el Servidor será siempre la misma, no variará.

El que todos los usuarios usen la misma memoria caché tiene su ventaja y su desventaja.

La ventaja es que si el usuario 1 ejecuta un SELECT y el usuario 2 poco después ejecuta ese mismo SELECT los resultados muy probablemente ya se encontrarán en la memoria caché y el usuario 2 los recibirá muy rápido. Eso porque traer los datos desde la memoria caché es muchísimo más rápido que traerlos desde el disco duro.

La desventaja es que si el usuario 1 ejecuta un stored procedure o un SELECT que requieren de mucha memoria les producirá un cuello de botella a todos los demás usuarios (al usuario 2, al usuario 3, al usuario 4, etc.) porque mientras el Servidor del Firebird esté ejecutando lo solicitado por el usuario 1 la memoria RAM disponible para todos los demás usuarios será muy poca.

Por lo tanto, cuanto mayor sea la cantidad de memoria RAM disponible para el caché, mucho mejor para todos.

¿Y cómo determinamos el tamaño de la memoria caché?

Mediante dos variables: la cantidad de páginas y el tamaño de cada página de la Base de Datos

Los tamaños de las páginas actualmente pueden ser: 4096 bytes, 8192 bytes, 16384 bytes

Como sugerencia (no es obligatorio hacerlo así, aunque se obtienen buenos resultados) podríamos determinar el tamaño de cada página según estas reglas:

  1. Tamaño de la Base de Datos menor que 128 Mb, usar 4096 bytes
  2. Tamaño de la Base de Datos entre 128 Mb y 1 Gb, usar 8192 bytes
  3. Tamaño de la Base de Datos mayor que 1 Gb, usar 16384 bytes

Ejemplo:

Tenemos una Base de Datos cuyo tamaño es de 1,8 Gb, cada página ocupa 16.384 bytes, hay 12 usuarios concurrentes y una memoria RAM de 32 Gb.

Primero, debemos tomar en cuenta que no toda la memoria RAM estará disponible para el Servidor del Firebird, porque el Sistema Operativo y otros programas usarán una porción de esa memoria RAM. Pero en este caso el tamaño de la Base de Datos es mucho menor que la memoria RAM, así que podemos usar mucha memoria para ella.

Segundo, por defecto para SuperServer se usan 2.048 páginas por cada Base de Datos. Eso implica que cada Base de Datos usaría 2.048 * 16.384 bytes, o sea 33.554.432 bytes. Pero recuerda que es memoria compartida por todos los usuarios, en nuestro ejemplo si el usuario 1 ejecuta un proceso tan intensivo que requiere 30.000.000 de bytes, para los 11 usuarios restantes solamente quedarían 3.554.432 bytes, o en promedio 3.554.432 / 11, o sea 323.130 bytes para cada usuario. Parece muy injusto, pero así funciona SuperServer.

Calculando la cantidad de páginas:

Cantidad_páginas = 16 Gb / 16.384 = 16 * 1.024 * 1.024 * 1.024 / 16.384 = 1.048.576

El resultado es correcto, sin embargo el Firebird no aceptará 1.048.576 páginas, ¿por qué no? porque la cantidad de páginas debe estar entre 50 y 131.072.

Si establecemos que la cantidad de páginas será 131.072 entonces cada Base de Datos usará 131.072 * 16.384 = 2.147.483.648 bytes o lo que es lo mismo, 2 Gb. Tamaño máximo que se puede reservar en la memoria caché para SuperServer. Y una consecuencia es que no deberías usar SuperServer en bases de datos cuyo tamaño sea superior a 2 Gb, ¿el motivo? no puedes poner a toda tu Base de Datos en la memoria caché.

Como nuestra Base de Datos ocupa solamente 1,8 Gb entonces podemos tenerla completa dentro de la memoria caché.

Colocando a toda la Base de Datos en la memoria caché

Si toda nuestra Base de Datos está en la memoria caché entonces todos los SELECT y todos los procesos serán rapidísimos porque los datos necesarios se extraerán de la memoria RAM, no del disco duro. Y la memoria RAM es muchísimo más rápida que el disco duro.

Una página de la Base de Datos se coloca en la memoria caché cuando es leída. Eso implica que si leemos todas las páginas de una Base de Datos y tenemos suficiente memoria caché entonces toda la Base de Datos estará en la memoria caché. ¿Verdad? Es muy sencillo.

Y para leer cada página de cada tabla lo más simple es usar la función agregada COUNT(). Cada vez que escribimos un SELECT COUNT(*) FROM MiTabla todas las páginas de MiTabla son colocadas en la memoria caché (si hay suficiente espacio disponible, desde luego).

Listado 1.

CREATE PROCEDURE LLENAR_CACHE
AS
   DECLARE VARIABLE lcNombreTabla   VARCHAR(1024);
   DECLARE VARIABLE lcComando       VARCHAR(1024);
   DECLARE VARIABLE lnCantidadFilas INTEGER;
BEGIN
   
   FOR SELECT
      RDB$RELATION_NAME
   FROM
      RDB$RELATIONS
   INTO
      :lcNombreTabla
   DO BEGIN
      lcComando = 'SELECT COUNT(*) FROM ' || lcNombreTabla;
      EXECUTE STATEMENT lcComando INTO :lnCantidadFilas;
   END

END;

Explicación:

En la tabla del sistema llamada RDB$RELATIONS se encuentran los nombres de todas las tablas de la Base de Datos. Mediante un FOR SELECT se obtiene cada uno de esos nombres y se lo usa para hallar la cantidad de filas de la tabla.

Despues de escribir EXECUTE PROCEDURE LLENAR_CACHE y su correspondiente COMMIT, todas las páginas de todas las tablas estarán dentro de la memoria caché (desde luego, si hay suficiente espacio en la memoria caché).

Colocando parte de la Base de Datos en la memoria caché

Si nuestra Base de Datos es muy grande no podremos ponerla completa dentro de la memoria caché, en esos casos lo mejor es colocar allí a las tablas más usadas, escribiendo algo como:

Listado 2.

SELECT COUNT(*) FROM PRODUCTOS;
SELECT COUNT(*) FROM CLIENTES;
SELECT COUNT(*) FROM PROVEEDORES;
SELECT COUNT(*) FROM BANCOS;
COMMIT;

Artículos relacionados:

El índice del blog Firebird21

El foro del blog Firebird21

 

Older Entries