viernes, 8 de marzo de 2013

SLA del SAT



Hoy acudí al SAT a realizar un trámite y me encontré con la sorpresa de que no tenían sistema de forma local, es decir que no era un problema del SAT a nivel nacional, sólo de la oficina a la que acudí a mi cita.

Después de platicar por unos momentos con la persona encargada de darme lo que fui a solicitar me enteré que el sistema estaba caído desde que comenzó el día por lo que todos los que habían ido, los que estábamos y los que pasaran después de mí no podrían obtener lo que necesitábamos.

Me pregunté si el personal de sistemas del SAT tiene un SLA por cumplir, si es que tienen planes para la recuperación de desastres y así poder atender como se debe, al final ellos son nuestros proveedores de información y documentos y tenemos derecho a obtener un servicio de calidad.

El personal que labora ahí es sumamente atento, de eso no hay la menor duda, en todo momento están al pendiente si es que nesitas algo, revisan la papelería, te aconsejan, etc.; en esta entrada de blog quisiera analizar el actuar del personal de sistemas.

Una vez que me dijeron que no había sistema, me hicieron firmar un papel donde me avisaban que a más tardar en 72 horas hábiles (3 días) me entregarían lo que fui a solicitar, esto implica que durante esos 3 días podrían seguir teniendo tirado el sistema sin problema alguno.    Si a estos tres días le sumamos el día de hoy, estaríamos hablando de 4 días hábiles sin sistema, de aquí me desprende otra pregunta, ¿en qué empresa le permitirían al jefe de sistemas y a su personal el tener la operación detenida por 4 días seguidos?, estoy seguro que en ninguna que tenga más de 2 centavos de responsabilidad.

Saqué la cuenta de los días hábiles (lunes - viernes) que hay entre el 1 de enero del 2013 y el 31 de diciembre del 2013, son 261 días, si a esta cantidad le quitamos los días que están establecidos en la ley federal del trabajo en su artículo 74, entonces quedamos con 254 días.

No buscaré quitarle más días investigando en qué otras fechas no trabajan estas personas, no quiero que me duela el estómago del enojo.

Calculemos su SLA esperado al día de hoy, si 254 días de servicio son el 100% y le quitamos los 4 días que arbitrariamente se permiten perder quedamos con 250 días, lo cual nos da un SLA del 98.41% de disponibilidad.

¡Sí!, 98.41% es el porcentaje que, a lo máximo, podrían cumplir este año... veamos bien que es un SLA ¡de un nueve!, no es malo, es pésimo.

Lamentablemente el trabajo de la gente de sistemas de esta oficina es verdaderamente desastroso y patético, es un ejemplo perfecto de lo que es un servicio deficiente e incapaz de cubrir las necesidades que plantea su puesto.

De aquí se desprende una invitación a toda persona que se dedique a la administración de servidores y bases de datos a que se actualice, se prepare, estudie, intente e investigue diferentes técnicas para ofrecer un servicio de calidad y que, al menos, genere un SLA de dos nueves, sé que es una cantidad aún extremadamente baja, pero ir trabajando poco a poco irá subiendo ese número hasta llegar a niveles de calidad superior.

jueves, 17 de enero de 2013

Programación en SQL Server - parte 2



En la entrada anterior hablábamos de los aspectos iniciales de la sentecia SELECT, en ésta platicaremos acerca de las implicaciones que tiene la palabra reservada JOIN.

La palabra reservada JOIN nos sirve para establecer un criterio a través del cual elegiremos filas o datos que queremos recuperar de dos tablas que comparten valores en una o más columnas (podría ser un valor calculado al vuelo pero no se recomienda dado que significaría en una sobrecarga de trabajo)

Llamaremos tabla "izquierda" a la que aparece primero en la sentencia y tabla "derecha" la que aparece después, por ejemplo:

SELECT I.Col1, I.Col2, D.Col1, D.Col2
FROM Izquierda I
INNER JOIN Derecha D
ON I.Col1 = D.Col1

INNER JOIN nos devolverá únicamente aquellas filas que cumplan con el criterio o criterios definidos en la parte del "ON", en este caso sólo devolvería las filas que tengan el mismo valor en Col1 de la tabla Izquierda y Col1 de la tabla Derecha.

Es común que en las bases de datos entidad - relación se haga JOIN entre dos tablas relacionadas a través de una llave foránea, lo cual no es obligatorio; si esta tarea se ejecuta forma habitual entonces deberemos valorar el crear un índice que cubra las columnas que conforman a la llave foránea.    Por ejemplo, supongamos que tenemos una tabla de Categorías y otra tabla de Productos:

CREATE TABLE Categories(
    Id SMALLINT IDENTITY(1, 1),
    Name VARCHAR(10) NOT NULL,
    CONSTRAINT pkCategories PRIMARY KEY (Id)
)
GO
CREATE TABLE Products(
    Id SMALLINT IDENTITY(1, 1),
    Id_Category SMALLINT NOT NULL,
    Name VARCHAR(10) NOT NULL DEFAULT SUBSTRING(REPLACE(CAST(NEWID() AS VARCHAR(36)), '-', ''), 1, 10),
    CONSTRAINT pkProducts PRIMARY KEY (Id),
    CONSTRAINT fkProducts_Categories FOREIGN KEY (Id_Category) REFERENCES Categories (Id)
)

Y que han sido llenadas con el siguiente código:

INSERT INTO Categories (Name) VALUES ('Category 1')
INSERT INTO Categories (Name) VALUES ('Category 2')
INSERT INTO Categories (Name) VALUES ('Category 3')
INSERT INTO Categories (Name) VALUES ('Category 4')
INSERT INTO Categories (Name) VALUES ('Category 5')
GO
DECLARE @i SMALLINT = 1
WHILE @i <= 5000
BEGIN
    INSERT INTO Products (Id_Category)
    VALUES ((ABS(CHECKSUM(NEWID())) % 5) + 1)

    SET @i = @i + 1
END

Si el sistema pide que los productos sean filtrados por categoría para ser mostrados al cliente, tendríamos un query como este:

SELECT P.Name, C.Name AS Category
FROM Products P
INNER JOIN Categories C
ON P.Id_Category = C.Id
WHERE C.Id = 3

Si observamos el plan de ejecución del query, veremos que se está llevando a cabo un barrido del CLUSTERED INDEX pkProducts, lo cual implica en un trabajo prolongado y que consume recursos.


El costo aproximado de este query es de: 0.260062

Tal como lo había mencionado antes, si es habitual la ejecución de este query, es súper recomendable crear un índice que cubra las columnas que son utilizadas en la parte del "ON".    En este caso también incluiremos la columna "Name" en el covering index para que el resultado se tenga a la mano en la estructura misma del índice:


CREATE INDEX ixProducts_Category
ON Products (Id_Category)
INCLUDE (Name)

Calculemos de nuevo el plan de ejecución y veremos que ha cambiado el barrido físico por una búsqueda en el índice que acabamos de crear, si analizamos el costo reportado por SQL Server veremos que es de: 0.0799945, lo cual significa una mejora del 325.09%, que se verá reflejada en una rápida lectura y también en un menor consumo de recursos.



En la siguiente entrada veremos el uso del LEFT JOIN, espero te haya sido de utilidad.

miércoles, 19 de diciembre de 2012

Programación en SQL Server



Últimamente me he dado cuenta (tristemente) que la gente no le da la importancia debida a las diferentes técnicas y herramientas de programación con las que se cuentan en SQL Server, es preocupante que los "DBA" de las empresas tengan técnicas paupérrimas para obtener la información que requieren y peor aún que piensen que es la única y mejor manera sin haber tenido contacto con literatura más avanzada del tema.

Con este conjunto de entradas no quiero ser agresivo y mucho menos engreído, el objetivo es dar una serie de consejos que puedan guiar al programador a crear las estructuras necesarias, utilizar las técnicas óptimas y proponer las tareas de análisis que hagan en conjunto una técnica de programación aceptable, no podría decir que la mejor porque al final estoy 100% seguro que su servidor no tendrá siempre la mejor respuesta, sería egocentrismo.

¿Por dónde empezar?, analicemos la sentencia SELECT y veamos muchos puntos interesantes que podemos desprender de este tema.

Es más que conocido que la sentencia SELECT es utilizada para obtener información de la base de datos y dado que tiene una sintaxis tan sencilla luego se olvidan las implicaciones que se pueden tener al escribir una sentencia de pobre análisis y peor ejecución.

Recordemos que estamos trabajando en bases de datos OLTP cuyo objetivo es establecer una infraestructura que permita la escritura rápida de datos y dar herramientas para poder realizar lecturas rápidas, pero dejaremos en claro que su objetivo es la escritura rápida.

La sentencia más básica tendría esta estructura:

SELECT * FROM Productos

"Productos" es el nombre de la tabla de la que queremos obtener la información y "*" significa que queremos obtener todas las columnas de la tabla.

¿Implicaciones?, muchas, el primer punto importante es que debemos dejar en claro que jamás deberemos obtener columnas que no vamos a utilizar, en este caso si sólo necesitamos el id, nombre, color y precio; pues sólo debemos mencionar esas columnas:

SELECT Id, Nombre, Color, Precio
FROM Productos

Una segunda implicación de utilizar el "*" en lugar de sólo las columnas que vamos a utilizar, es que de forma inequívoca se realizará un barrido al índice CLUSTERED de la tabla, es decir que se hará un barrido fila a fila de la tabla, lo cual dependiendo de la cantidad de filas puede tener consecuencias negativas.

Si no vamos a utilizar todos los productos, es imprescindible el filtrar los datos desde el manejador de base de datos, es decir que no deberemos enviar toda la información al cliente y posteriormente realizar el filtrado, todo esto gastaría recursos de forma inútil (a menos que haya razones de mucho peso para hacerlo)    La sentencia para filtrar los productos cuyo precio sea mayor a $100.00 sería la siguiente:

SELECT Id, Nombre, Color, Precio
FROM Productos
WHERE Precio > 100

Es importante notar que no siempre que exista un índice sobre las columnas que aparecen en la parte del WHERE éste va a ser utilizado:
  1. Si la cantidad de productos que cumplen con la condición es grande respecto al total de filas en la tabla, entonces el manejador ejecutará un barrido fila a fila.
  2. Si la cantidad de productos que cumplen con la condición es pequeña respecto al total de filas en la tabla, entonces el índice será utilizado.
Uno podría pensar que si son muchos, pues los regresamos todos, total así fue requerida la sentencia ¿no?, bueno, supongamos que entramos a una tienda de discos y buscamos el género "banda", ¿imaginan cuántos discos van a salir?, ¿es posible que alguien los vea todos?, generalmente no pasa así, por lo tanto es imperativo que ofrezcamos un resultado paginado para evitar que se envíen resultados que no van a ser utilizados.

En entradas posteriores estaremos trabajando más sobre la sentencia SELECT.