Simulación de la actividad
Introducción
Durante la entrada en funcionamiento de cualquier sistema informático, el aumento de carga sigue siendo un paso fundamental. Lo bueno en la informática es que se puede simular la realidad. Y se vuelve realmente interesante cuando es posible simular docenas o incluso cientos de usuarios. Si bien estos se comportan de manera ideal, sin cometer errores (al fin y al cabo, son autómatas), al menos permite validar herramientas en un contexto óptimo. Recuerde, todo es cuestión de volumen y frecuencia.
En este capítulo, vamos a ver cómo simular una actividad informática de gestión con una pseudo empresa que practica el trading. Es un caso fácil y clásico, pero permite generar actividad en un servidor y volumen de datos.
Originalmente, este pequeño proyecto me permitiría probar las copias de seguridad RMAN de la base de datos de Oracle, sin embargo, hacer una copia de seguridad de una base de datos sin volumen no tiene es casi ningún interés.
Hacer una copia de seguridad siempre es la parte más fácil. Lo que es complicado y difícil es la restauración. Además, la recuperación de datos suele realizarse en un entorno tenso y estresante con usuarios ansiosos, de ahí el valor de entrenarse en la recuperación de bases de datos.
Además, necesitábamos simular una pequeña actividad y generar...
Descripción
La empresa "ACME Corporation" (el nombre no importa), vende productos a sus clientes y, dependiendo de las cantidades solicitadas, compra a sus proveedores el volumen necesario para cumplir con los pedidos.
Esto se conoce comúnmente como comercio.
Aquí hay tres entidades principales: Cliente/Producto/Proveedor, que se convertirán en objetos y tablas en nuestra base de datos.
El cliente realiza pedidos de cliente que, una vez entregados en su totalidad, se pueden facturar. Esto nos da un objeto "pedido cliente" que, a nivel de la base de datos, se convertirá en una tabla de encabezados y líneas de pedidos.
La empresa realiza los pedidos de los proveedores que se deben recibir. Esto da otro objeto: líneas de pedido de proveedores.
Y para saber dónde estamos para cada producto, gestionaremos stock, de ahí el último objeto: el stock.
La estructura de los datos
A continuación puede ver una descripción de la estructura de los datos.
En primer lugar, en forma de esquema:
Estructura de datos
Ahora más detallado:
Las tablas Cliente/Producto/Proveedor:
Tabla |
Campo |
Tipo |
Observación |
CLIENTE |
Nombre |
Char(20) |
Clave única |
PRODUCTO |
Desc |
Char(30) |
Clave única/Descripción del producto |
PRODUCTO |
Precio |
Flotante |
El precio del producto |
PROVDR |
Nombre |
Char(20) |
Clave única |
PROVDR |
Plazo |
Entero |
Plazo de reaprovisionamiento |
La Tabla Stock:
Tabla |
Campo |
Tipo |
Observación |
STOCK |
Almacen |
Char(10) |
Código del almacén de mercancías |
STOCK |
PRO_ID |
|
Identificador producto/clave extranjera |
STOCK |
CSTOCK |
Entero |
Cantidad en stock |
Los pedidos de los clientes: las tablas PEDCLI y LINCLI.
Tabla PEDCLI (Pedidos de cliente):
Tabla |
Campo |
Tipo |
Observación |
PEDCLI |
NUMPED |
Entero |
Clave única |
PEDCLI |
FECPED |
Fecha |
Por defecto fecha de creación |
PEDCLI |
CLI_ID |
|
Identificador Cliente/Clave extranjera |
PEDCLI |
FACTURA |
Entero |
N° de factura |
Tabla LINCLI (Líneas de pedido cliente):
Tabla |
Campo |
Tipo |
Observación |
LINCLI |
NUMPED |
Entero |
N° de pedido cliente |
LINCLI |
NUMLIN |
Entero |
N° de línea |
LINCLI |
PRO_ID |
|
Identificador producto/clave extranjera |
LINCLI |
NPED |
Entero |
Cantidad pedida |
LINCLI |
PRECIO |
Flotante |
Precio del producto |
LINCLI |
CENTREG |
|
Cantidad entregada |
LINCLI |
CFAC |
Entero |
Cantidad facturada |
La tabla LINPROV de los pedidos proveedores:
Tabla |
Campo |
Tipo |
Observación |
LINPROV |
NUMPED |
Entero |
N° de pedido cliente |
LINPROV |
NUMLIN |
Entero |
N° de línea |
LINPROV |
PROV_ID |
|
Identificador proveedor/clave extranjera |
LINPROV |
PRO_ID |
|
Identificador producto/clave extranjera |
LINPROV |
NPED |
Entero |
Cantidad pedida |
LINPROV |
PRECIO |
Flotante |
Precio del producto |
LINPROV |
FPREVI |
Fecha |
Fecha de entrega prevista |
LINPROV |
CREC |
Entero |
Cantidad recibida |
La gestión de los contadores requiere la gestión de una tabla adicional. Se utiliza un contador para gestionar los números de pedido de cliente, de pedidos a proveedores, de factura, etc.
Es necesario codificar una gestión de contadores, para que sea independiente de la base de datos.
Tabla |
Campo |
Tipo |
Observación |
CONTADOR |
NOM |
Char(10) |
Nombre del contador |
CONTADOR |
VAL |
Entero |
Valor del contador |
Esta estructura de datos es muy clásica para la informática...
La inicialización de la base de datos
SQLAlchemy ya se ha tratado más atrás en este libro y por esta razón, solo describiremos lo esencial.
1. definitions.py
Lo primero que se debe hacer, es describir la estructura de los datos como un objeto Python.
Entonces el primer script se llamará definitions.py y tendrá que definir la base de datos que queremos crear.
En primer lugar, es necesario realizar las importaciones de los módulos correctos. SQLAlchemy es una gran librería y una importación global (import SQLAlchemy.*) puede ser costosa en términos de memoria.
Es necesario importar:
La definición de columnas y claves extranjeras: Column ForeignKey.
Los tipos de datos: Integer, String, DateTime, Date, Float y BigInteger.
Métodos y funciones como relationship, declarative_base y create_engine.
ForeignKey: una clave extranjera, en una base de datos relacional, es una restricción que garantiza la integridad referencial entre dos tablas. La clave extranjera identifica una columna o conjunto de columnas de una tabla que se reflere a una columna o conjunto de columnas en otra tabla.
Fuente Wikipedia: https://es.wikipedia.org/wiki/Clave_for%C3%A1nea
En el lado más "práctico" aquí: la declaración de una clave extranjera de una tabla a otra garantiza que, al introducir un pedido para un cliente, este cliente existe en la tabla de clientes, de lo contrario el pedido es rechazado.
El módulo de definiciones se debe diseñar para ser importado por los otros scripts; las clases descritas necesitan ser representadas específicamente, de ahí el uso del método __repr__.
Y dado que estas son clases de Python, es muy posible definir una clase base que contenga los campos relacionados con cada clase.
A continuación el archivo definiciones.py:
#archivo: db-activity-gestion/definitions.py
## -------------------------------------
## DEFINICIONES DE LA BASE DE DATOS
## -------------------------------------
import os
import sys
from sqlalchemy import Column, ForeignKey
from sqlalchemy import Integer, String, DateTime, Date, Float,
BigInteger
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import relationship
from sqlalchemy import create_engine ...
La conexión a la base de datos
El script anterior solo necesita ejecutarse una vez para inicializar la base de datos.
Para el resto de la simulación, cada script será autónomo, pero funcionará de la misma forma:
-
Conexión a la base de datos
-
Ejecución de la función principal
Cada script necesitará una función connect() que devolverá un objeto session.
Este es el objetivo del script base.py que se potencia con una función simple para recuperar el catálogo de productos para probar:
#archivo: db-activity-gestion/base.py
from sqlalchemy import create_engine, select
from sqlalchemy.orm import sessionmaker
from definitions import Base, BASE_NAME
from definitions import Cliente, Producto, Proveed, Stock,
PedCli, LinCli, LinProv
def connect():
engine = create_engine(BASE_NAME, echo=False)
Base.metadata.bind = engine
DBSession = sessionmaker(bind=engine)
session = DBSession()
return session
def get_catalog():
session = connect()
s = select([Producto.id])
prods = session.execute(s)
p = [ x[0] for x in prods ]
session.close() ...
Los contadores
Inicialmente, esta simulación fue diseñada para la base de datos Oracle, que tiene de base una gestión de secuencia. Estas secuencias permiten obtener una serie de números únicos que se suceden, pero sin garantizar la continuidad.
Desafortunadamente, este objeto de base de datos no existe en algunas bases de datos, incluida SQLite. Sin embargo, es bastante fácil de codificar, especialmente con Python.
Para un contador, es solo un nombre y un valor; solo necesitamos una función para crear un contador, otra para obtener el siguiente valor y, dado el caso, una función para obtener el valor actual.
Cada función será autónoma y creará su propia sesión.
A continuación se muestra el script:
# archivo: db-activity-gestion/secuencia.py
## -------------------------
## Librería de las secuencias
## -------------------------
from sqlalchemy import create_engine, select
from sqlalchemy.orm import sessionmaker
from base import connect
from definitions import Contador
def cr_Contador(nombre):
session = connect()
c = Contador()
c.nombre = nombre
c.val = 1
session.add(c)
session.commit() ...
Los pedidos de clientes
Una vez que se inicializa la base de datos, es posible centrarse en el aspecto básico del negocio, es decir, el pedido del cliente.
Para esta simulación, este script deberá generar un pedido de cliente a partir del catálogo completo de los productos y para un cliente elegido al azar.
El script comienza recuperando el catálogo de los productos, y especialmente la información interesante, a saber, el identificador del producto y su precio.
A continuación, debe buscar un cliente y para eso, extraer los identificadores de los clientes y usar la función random.choice().
Queda por seleccionar los productos pedidos. Para hacer esto, la función random.sample() le permite extraer una muestra significativa.
En el estado actual de las cosas, el máximo de productos pedidos se calcula en función del número de productos; esto es adecuado cuando hay pocos productos. Pero si queremos un archivo de productos más imponente, será necesario limitar el número de productos pedidos, para no tener pedidos surrealistas de varios miles de líneas (aunque esto realmente exista).
Cuando tengamos el cliente y la lista de productos solicitados, podemos generar un pedido. Nos encargaremos de supervisar la escritura en la base de datos mediante una gestión de excepciones y de hacer commit solo al final. También puede hacer todo lo posible para tratar de mantener...
La entrega de los pedidos de cliente
Si disponemos de pedidos y de stock, podemos proceder a la entrega de los pedidos del cliente.
No es mucho más complicado que el resto. Hay que buscar las líneas de pedido cuya cantidad pedida menos la cantidad entregada sea mayor que cero.
La consulta SQL es mucho más sucinta que en español, porque no es ni más ni menos que:
select numped, numlin, producto_id, cped, centreg
from LINCLI
where cped-centreg > 0
order by numped, numlin
SQLAlchemy permite usar consultas directamente con el método execute(), de la sesión actual.
A partir del resultado de esta consulta, podemos trabajar en cada pedido y en cada línea de cada pedido.
Empezamos recuperando el stock de cada producto y, si el stock es mayor a la cantidad necesaria (cped - centreg), entonces podemos asignar nuestra línea y disminuir el stock.
Tenga en cuenta la solución encontrada por los desarrolladores de SQLAlchemy para expresar múltiples pruebas, en la función get_stock() acumulando los ".where".
No hay demasiado control a este nivel para el stock negativo, ni demasiado riesgo siempre que este script se ejecute de manera unitaria y con regularidad.
A continuación se muestra el script:
# archivo: db-activity-gestion/entrega_cliente.py
from sqlalchemy import select
from base import connect ...
La facturación de los pedidos entregados
Los pedidos están entregados, por lo que es posible facturarlos. De hecho, se trata más de un proceso de reflexión sobre la cuestión.
El principio de este script es buscar pedidos de clientes no facturados (campo FACTURA == 0) y comprobar si se han introducido todas las líneas.
Esto es lo que hace la función get_pcl_a_facturar(); devuelve una lista de pedidos a facturar.
Esta lista permite lanzar, por cada pedido a facturar, la función factura_pcl(). Su función es crear un número de factura, lanzar un comando SQL update para actualizar el pedido y actualizar las líneas del pedido, colocando la cantidad facturada en la cantidad entregada.
A continuación se muestra el script:
#archivo: db-activity-gestion/fact_cliente.py
from sqlalchemy import select
from base import connect
from definitions import Cliente, Producto, Proveed, Stock, PedCli,
LinCli, LinProv
import sequence as seq
import datetime
import random
# -------------------------------------
# Búsqueda de los pedidos facturables
# -------------------------------------
def get_pcl_a_facturar():
pcl_a_facturar = []
session = connect()
pcl...
El reaprovisionamiento del stock
Después de un tiempo, no habrá más stock y tendremos que ir a alguna parte. Aquí es donde entra el reaprovisionamiento.
En esta simulación, cada proveedor puede entregar todos los productos. En la vida real, es mucho más complejo.
Al principio, para realizar las pruebas, el tiempo de reabastecimiento se establece en 0 días para evitar esperas. En un contexto de simulación más largo, es deseable modificarlo para reproducir los flujos de un entorno en explotación.
¿Cómo se repone un stock?
Calculando la necesidad de cada producto. Esto se hace en dos pasadas:
La primera busca en los pedidos del cliente el volumen de stock necesario para cada producto, con una consulta como:
select producto_id, sum(cped) 'cnt'
from LINCLI
where cped-centreg > 0
group by producto_id
order by producto_id
Esto significa que queremos la lista de identificadores de producto con la suma de las cantidades pedidas para las líneas de pedidos no entregadas (cped-centreg> 0) y, para hacer la vida más fácil, ordenamos y agrupamos por identificador de producto.
El resultado es la lista ordenada de necesidades (la suma de las cantidades a entregar), para cada producto.
Una vez establecida esta lista de necesidades, aún es necesario comprobar si no hay ningún pedido de proveedor activo, antes de generar...
La recepción de pedidos de proveedores
Último script para la gestión de esta pequeña comercializadora: la recepción de los pedidos de proveedores.
Solo hay que buscar las líneas de pedidos a proveedores, cuya cantidad pedida menos la cantidad recibida, sea mayor que cero (cped - crec> 0) y cuya fecha esperada sea menor o igual a la fecha actual.
De ser así, se recupera el registro de stock correspondiente y el stock se incrementa con la cantidad recibida.
En la misma transacción, actualizamos la línea de pedido de proveedor, aumentando también la cantidad recibida.
A continuación se muestra el script:
# archivo: db-activity-gestion/recept_prov.py
from sqlalchemy import select
from base import connect
from definitions import Cliente, Producto, Proveed, Stock, PedCli,
LinCli, LinProv
import sequence as seq
import datetime
import random
def get_cprov_ar(fecha_actual):
session = connect()
r = select([LinProv]).\
where(LinProv.cped-LinProv.crec > 0).\
where(LinProv.fprevi <= fecha_actual)
cprovar = session.execute(r).fetchall()
c = [ x for x in cprovar]
...
Utilización
A continuación, un ejemplo de uso de estos scripts.
Lo primero que se debe hacer es elegir la base de datos que se desea probar.
Estos scripts se adaptan fácilmente a otra base de datos, gracias al uso de SQLAlchemy.
El segundo punto es adaptar el script populate.py al volumen deseado; por defecto, este script crea 10 clientes, productos y proveedores, lo que hace posible probar los scripts, pero incluso SQLite no responde demasiado mal en estos volúmenes.
El último punto es elegir un período: hora, día, 30 minutos. Todo depende del tiempo y del volumen que quiera generar.
A continuación, la tabla resumen de los scripts descritos anteriormente:
Scripts |
Frecuencia |
Observación |
Populate.py |
1 sola vez |
Inicializa la base de datos |
Cliente.py |
Varias veces/periodo |
La frecuencia se tiene que adaptar en función del volumen de flujo deseado |
entrega_cliente.py |
2 veces por periodo |
|
fact_cliente.py |
1 vez por periodo |
Fuera del periodo de explotación |
reappro_prov.py |
1 vez por periodo |
Fuera del periodo de explotación |
recept_prov.py |
1 a 2 veces por periodo |
|
El período puede corresponder a un día, una hora o un minuto. Todo depende del tipo de simulación que se desee.
Una continuación se muestra una tabla de activación de los scripts, que podría corresponder a una explotación tradicional y que además, permite secuenciar bien las operaciones para no molestar demasiado al servidor.
Todas las operaciones se debían completar a las 22 h, hora a la que habíamos programado una copia de seguridad global de la base de datos.
Horario |
|
|
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
|
|
||
scripts |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
||
cliente.py |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
||
entrega_cliente.py |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
reappro_prov.py |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
||
recepe_prov.py |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
... |
Resumen
Originalmente, este proyecto debería permitir probar durante un largo período de tiempo, los scripts de copia de seguridad y purgado vinculados a los mecanismos de archivado de los archivos de log de Oracle, más conocidos con los nombres de RMAN y ARCHIVELOG.
De hecho, es difícil probar este tipo de cosas sobre una base de datos estática y queríamos un entorno de laboratorio para comprender un poco mejor cómo evolucionaba el consumo de recursos y especialmente, el recurso disco.
Después de unos días, esto superó nuestras expectativas, porque también pudimos estudiar el volumen de cómo se comportaba la base de datos en función de parámetros deliberadamente reducidos, como la memoria caché asignada (SGA, System GlobalArea).
Pero esto se puede trasponer a otra base de datos como MySQL o PostgreSQL, y de esta manera permitir ver cómo reacciona con un volumen que hemos decidido y que controlamos.
Estos scripts son intencionalmente simples, incluso simplistas. Cuando haya escrito código equivalente más complejo en diferentes lenguajes, no le llevará mucho tiempo crear estos seis scripts.
Sin embargo, solo se trata de una simulación, ciertamente imperfecta, y no hay que presionar demasiado esta simulación para que se desencadenen errores o inconsistencias (como stock negativo, por ejemplo).
Por otro lado, gracias...