sábado, 17 de octubre de 2015

EIG - Abonando a un ladrón de varias cabezas



Hace algunos meses realicé la adquisición de un servicio de alojamiento con un proveedor llamado Arvixe, no puedo quejarme de esos primeros meses, el servicio muy bueno, la atención por parte del personal de soporte muy buena, y el rendimiento del servidor bastante bueno.

Después de un buen tiempo, hubo un cambio radical en sus servicios, y puedo decir que en toda su gama por la cantidad de tickets y quejas que llueven en twitter, a lo que nuestros amigos siempre se limitan a responder algo así:
  1. "Gracias por proporcionar el id del ticket, te responderé en él".
  2. "¿Podrías proporcionar el id del ticket?"
Obviamente tienen un programa muy sencillo donde reciben sus tweets y apretando un botón, se realiza la respuesta del mismo.    ¿El esfuerzo?, elegir entre la respuesta 1 o la respuesta 2, de ahí en fuera, nada.

Después de esperar por más de 3 horas y media con el chat abierto, finalmente una persona atiende la conversación, después de comentar la situación y mencionar que el 99.99% que GARANTIZAN en su página de inicio no se estaba cumpliendo, esta personita comentaba que como era un problema grave ellos no podían dar ese 99.99%.

En este momento pensé, vamos 2-0 y la atención es inexistente.    Procedí a levantar un ticket, después de esperar 6 horas, levanté otro... y así siguió el ciclo, el cual no interrumpí para verificar en qué momento o qué tipo de ticket es el que responden.

¡Finalmente respondieron!, después de 48 horas con el servidor totalmente caído y sin respuesta por ningún lado, pude ver en la lista que uno de tantos estaba respondido, ¿qué podría ser?, entre emoción y coraje abrí el ticket, ¡wow!, la respuesta fue increíble: "olvidó colocar los últimos 4 caracteres de la contraseña de su usuario de facturación, para poder atender su ticket, necesitamos esa información".

Este ir y venir con esta gente lo voy a hacer a un lado, y no por falta de más cosas qué decir, sino que me llamaba la atención que la calidad en el servicio decayera de una forma tan estrepitosa y en tan poco tiempo.

Después de investigar un poco, supe que un corporativo con las siglas EIG (Endurance International Group) había adquidido a Arvixe, y ahí comencé a darle al clavo.

Sucede que estos amigos de EIG son dueños de muchos (pero muchos) proveedores de alojamiento, ¿qué tienen en común aparte del dueño?, que sus primeros meses de servicio son "aceptables", posteriormente comienzan los problemas y niegan cualquier posibilidad de devolución de dinero para sus clientes, ¿qué sucede?, el cliente termina yéndose a otra compañía.

Encontrarán muchas entradas de blog donde te recomiendan pasarte del proveedor X al proveedor Y, que con el proveedor Y están súper contentos y que el servicio es espectacular.    Si le buscamos un poco, ¡el proveedor Y pertenece también a EIG!, ¿resultado?, basta con buscar un poco en los foros de soporte y cuentas de redes sociales del proveedor Y, para darte cuenta que es igual de asqueroso que el proveedor X.

Al final, en todos estos blogs encuentras que te recomiendan ir saltando entre proveedores que pertenecen a esta empresa EIG, al final le caerá dinero a los mismos ladrones y tu servicio seguirá siendo de pésima calidad.

Quiero finalizar esta entrada de blog con una recomendación sumamente simple, cuando vayas a contratar algún servicio de alojamiento, ¡verifica que no sea una empresa que forme parte del grupo EIG!, te ahorrarás muchos dolores de cabeza y pérdidas económicas.

martes, 5 de mayo de 2015

Incorrect syntax near 'go'



El día de hoy me encontré con que un compañero estaba sufriendo bastante para crear un programa en .NET (aplicación de escritorio) que instalara una base de datos, desde su aprovisionamiento hasta la inicialización.

Al revisar el código que estaba utilizando, me encontré que los comandos en el archivo de recursos incluían la palabra GO.

Antes de abordar una solución simple, me gustaría explicar para qué sirve la palabra reservada GO en una sentencia SQL.

Vamos a abrir la página de opciones del SQL Server Management Studio:


Notemos que la opción "Batch separator" tiene el valor GO.    De ello podemos deducir que es una opción de configuración para SQL, de manera tal que en un sólo script podamos meter varias sentencias sin que una interfiera con la otra.    La palabra reservada GO no hace más que decir que ya terminó la sentencia y que la sentencia posterior es totalmente independiente a la anterior.

Vamos a probar esto con un ejemplo muy sencillo:

DECLARE @CurrentTime DATETIME = GETDATE()
PRINT @CurrentTime
GO
PRINT @CurrentTime


El resultado será el siguiente:


La primera sentencia PRINT sí se está ejecutando de forma exitosa, pero la segunda está marcando un error, y esto se debe a que la palabra reservada GO hace un "borrón y cuenta nueva", de manera tal que la variable @CurrentTime ya no existe en la segunda sentencia PRINT.

Debido a esto, el query que está ocupando mi compañero, está marcando el mensaje de error "Incorrect syntax near 'GO'"

Lo que menos se quería era tener que modificar el script para poder instalar la base de datos, por lo que encontramos una solución muy sencilla, crear una función que recibiera el script completo (con todo y GO) y lo dividiera en varios scripts tal como hipotéticamente se hace en SQL Server cuando se ejecuta, y el resultado fue el siguiente:

static void ExecuteCommand(string sqlCommandText, SqlConnection conn)
{
  SqlCommand comm = new SqlCommand("", conn);
  foreach (string sqlCommand in sqlCommandText.Split(new string[] { "GO\r\n" }, StringSplitOptions.RemoveEmptyEntries))
  {
    comm.CommandText = sqlCommand;
    comm.ExecuteNonQuery();
  }
}


Nuestra función aparte recibe una conexión abierta y disponible para ser utilizada.

Podemos notar que lo único que se está haciendo es dividir el script completo en pedazos ejecutables desde .NET.

La solución es sumamente simple, no hubo que ajustar nada en el código del script y la instalación de la base de datos ya tiene un resultado exitoso.

Espero te haya resultado de utilidad, nos vemos la siguiente entrada.

jueves, 30 de abril de 2015

Covering indexes



Los índices de cobertura, son aquellos que nos ayudan a mejorar el rendimiento de ciertas consultas que utilizan un índice y que cuyo resultado incluye un mismo conjunto de columnas.

Tal como en las anteriores entradas, vamos a aterrizar esto en un ejercicio para que se vea la utilidad de estos elementos.

Tomaremos la base de datos AdventureWorks como ejemplo y partiremos del siguiente query:

SELECT ProductID, Name, ProductNumber, MakeFlag
FROM Production.Product
WHERE Color = 'White'


Si revisamos el plan de ejecución, notaremos que se está llevando a cabo un barrido del CLUSTERED INDEX de la tabla Production.Product:


Con un costo estimado de 0.0127253, el cual ciertamente es bastante bajo, pero esto se debe a la poca cantidad de elementos que tiene nuestro conjunto.

¿Qué es lo primero que haríamos para mejorar el rendimiento de esta consulta?, claro, irnos a la parte del WHERE y analizar el predicado.    Después de revisarlo podemos proponer un índice sobre la columna Color de la tabla Production.Product.

CREATE INDEX ixColor
ON Production.Product (Color)


Vamos a revisar nuevamente nuestro plan de ejecución:


Notaremos que hay un cambio importante, ahora se está realizando una búsqueda en el índice ixColor, justamente el que acabamos de crear.    Notemos también que se está llevando a cabo una tarea Key Lookup que sirve para encontrar las columnas que nos faltan para poder dar el resultado, las cuales son ProductID, Name, ProductNumber y MakeFlag.

El costo de este query cambió de 0.0127253 a 0.0126079, lo cual significa una variación menor al 1%, en verdad que no es nada impresionante si lo comparamos contra el costo que tendrá para la base de datos el mantener al día este nuevo índice respecto a los cambios que sufra la tabla a la que apunta.

Pero bueno, tomemos en cuenta que tenemos sólo 504 filas en la tabla de productos, también es por ello que la variación es tan pequeña.

Ahora, vamos a ahondar un poco más en el Key Lookup:


Nuestro índice ixColor, sólo guarda los valores del Color y una liga hacia la tabla Production.Product donde se almacena la fila a la que pertenece, de ahí que se tenga que hacer ese Key Lookup para encontrar los datos que le hacen falta al query para devolver el resultado completo.     Notemos que en la imagen aparecen en Output List Name, ProductNumber y MakeFlag.

¿Qué pasa si siempre regresamos las mismas columnas?, ¿qué pasa si este query regresa un subconjunto de columnas que habitualmente se ocupan?    Si este es el caso, estamos ante la perfecta opción de utilizar un índice de cobertura (covering index)

¿Qué es un covering index?, es aquel que almacena también los valores que forman parte del resultado, es decir, que ya no debe de ir a la tabla original por ellos dado que tiene una copia actualizada en él.

Vamos a crearlo:

CREATE INDEX ixColorCovering
ON Production.Product (Color)
INCLUDE (Name, ProductNumber, MakeFlag)


Y analicemos nuevamente el plan de ejecución para ver los cambios:


Podemos notar que ahora se está utilizando únicamente nuestro índice ixColorCovering.    ¿Qué costo tiene actualmente el query?, el costo es de 0.0032864, lo cual significa una mejora del 74.17%.

¿Qué tal?, ahora sí impresiona ¿no?, y eso que estamos hablando de una tabla con 504 filas, ahora imagina que estuviéramos trabajando en una tabla con miles o millones de filas, verás dos impactos mayúsculos:
  1. Tiempo de respuesta muchísimo menor.
  2. Menor uso de memoria de SQL Server para poder despachar el resultado.
Antes de dar por finalizada esta entrada, quisiera remarcar lo siguiente: cuidado con los índices, recuerda que cada vez que generas un nuevo índice, SQL debe actualizar su valor si es que el valor original en la tabla fue alterado, esto significa que una simple escritura se puede traducir en muchas más.

Espero te haya resultado de utilidad esta entrada, nos vemos la siguiente.

miércoles, 29 de abril de 2015

Twitter automatizado



¿Tienes cuenta en Twitter?, lo más probable es que tu respuesta sea "sí".     ¿Qué haces en Twitter?, siendo una red del tipo microblog, lo más probable es que sea para chismear un rato, ya sea para lo bueno o para lo malo, es decir, estar al día o encontrar personas a las cuales molestar mediante comentarios incómodos, insultantes e hirientes.

Seguro habrás notado que muchos de los mensajes publicados en esta red social, vienen de calendarios programados de publicación, es decir, creo mi lista de mensajes, les pongo fecha y hora ¡y listo!

Realmente no hay algo de malo en ello si es que se le da un seguimiento a las respuestas o a los comentarios recibidos, de manera tal que la red se enriquezca y también se exploten las características de ésta.   Pero, qué pasa cuando...
  1. Se programan respuestas automáticas.
  2. Se monitorean cuentas de otras personas o instituciones y se hace retweet sobre cada una de sus publicaciones.
  3. Un sistema recopila los tweets con mayor impacto de las personas que se siguen para después hacer un "resumen personal y ejecutivo" de lo que otros publicaron.
Vayamos un poco más allá, ¿qué pasa si se deja que estos sistemitas se pongan a jugar y recopilar información para que entre ellos mismos se sigan y se den un retweet, follow, unfollow, etc.?, ¿no sería lo mismo que no existiera la red?

Me disculparás por ser tan tradicional, creo más en la relación personal, en ese ir y venir de ideas, comentarios, aseveraciones, discusiones, etc.    ¿Para qué colocar sistemas de actividad automatizada en una red social?, es como un "me invitaron a una fiesta, voy a ir, pero dejo a mi robot para que haga como que estoy porque me importa un cacahuate la interacción".

miércoles, 18 de marzo de 2015

Caracteres inválidos en XML



El otro día un exalumno me preguntaba si había alguna forma amigable de sustituir los caracteres no válidos en un texto para que éstos puedan ser integrados como parte de un documento XML.

Después de revisar todo el trabajo que había realizado, del cual la mayoría había sido extraído de internet, noté que le estaba dando bastantes vueltas al tema siendo algo tan simple como una sola línea de código.

Supongamos que tenemos la siguiente descripción:

El auto <de Juan & Rosy> es llamado "el volador"

Y queremos asignarla al atributo description del siguiente elemento:

<object description="" />

La solución es sencilla, utilicemos la función Escape de la clase SecurityElement que encontramos en el espacio de nombres System.Security, el resultado será el siguiente:

El auto &lt;de Juan &amp; Rosy&gt; es llamado &quot;el volador&quot;

Este último texto puede ser asignado sin problemas al atributo.

Como puedes ver es muy sencillo, no hay que darle tantas vueltas al tema y así te podrás quitar de muchos dolores de cabeza y operaciones sobre un objeto String.

viernes, 27 de febrero de 2015

Validador de contabilidad digital



El día de hoy por la tarde (27 de febrero de 2015) me encontré un tweet en la cuenta del SAT donde se anunciaba que existe un validador para los archivos XML de contabilidad electrónica, ¿reacción inmediata?, ¡vamos a probar los generadores en los que hemos trabajado!

¡Oh sorpresa!, entras a la página y al elegir un archivo XML muestra un mensaje de error diciendo:

"Validación de Nomenclatura: El nombre del Documento es inválido. Tipo identificado: Ninguno."

No se necesita tener un profundo conocimiento informático para notar que el problema era el nombre del archivo, por lo que inmediatamente busqué alguna liga en la página donde viniera alguna documentación de cómo debe llamarse el archivo para que el validador pueda tomarlo:


Realmente no hay mucho dónde buscar, lo cual me llevó a deducir que lo publicaron a las prisas (como casi nunca pasa con los sistemas del SAT y las empresas que le desarrollan software)

¿Qué opción tenemos?, ¡claro!, el chat uno a uno del SAT, ahí he realizado muchas consultas relacionadas con situaciones fiscales y siempre ha funcionado todo muy bien.

Pero... me surgió la duda... no voy a preguntar de cuestiones fiscales, ¡sino técnicas!, y ahí comenzó la parte triste de la historia, pero bueno, habrá que darles el beneficio y me animé a iniciar un chat con su heroico grupo de técnicos.

La excelente noticia es que la plática sólo duró 8 minutos, fue bastante rápido... la mala noticia, es que la persona que atendió no tiene ni la mínima idea de cómo se utiliza un sistema que ellos mismos publicaron.

Tristemente, el soporte técnico sigue siendo el talón de Aquiles, las razones no las conozco, pero ya es algo común y creo que les importa 2 kilos de cebollas que sea así.

¡Pero!, se encontró la solución, después de ponerme a analizar el código de la página, pude deducir los nombres de los archivos y a continuación los presento.

Catálogo de cuentas:
  • RFC completo
  • Los 4 dígitos del año
  • Número de mes del catálogo de cuentas expresado en dos dígitos.
  • CT, sí, textualmente las letras CT.
  • Ejemplo, si queremos validar el XML del catálogo de cuentas del RFC XAXX010101XXX del mes de enero de 2015, el archivo debe llamarse XAXX010101XXX201501CT.xml
Balanza de comprobación normal:
  • RFC completo
  • Los 4 dígitos del año
  • Número de mes de la balanza de comprobación expresado en dos dígitos
  • BN
  • Ejemplo, si queremos validar el XML de la balanza de comprobación normal del RFC XAXX010101XXX del mes de febrero de 2015, el archivo debe llamarse XAXX010101XXX201502BN.xml
Balanza de comprobación complementaria:
  • RFC completo
  • Los 4 dígitos del año
  • Número de mes de la balanza de comprobación expresado en dos dígitos
  • BC
  • Ejemplo, si queremos validar el XML de la balanza de comprobación complementaria del RFC XAXX010101XXX del mes de enero de 2015, el archivo debe llamarse XAXX010101XXX201501BC.xml
Pólizas:
  • RFC completo
  • Los 4 dígitos del año
  • Número de mes de las pólizas
  • PL
  • Ejemplo, si queremos validar la presentación de pólizas del RFC XAXX010101XXX del mes de febrero de 2015, el archivo debe llamarse XAXX010101XXX201502PL.xml
Auxiliar de folios:
  • RFC completo
  • Los 4 dígitos del año
  • Número de mes del auxiliar de folios
  • XF
  • Ejemplo, si queremos validar el auxiliar de folios del RFC XAXX010101XXX del mes de enero de 2015, el archivo debe llamarse XAXX010101XXX201501XF.xml
Auxiliar de cuentas:
  • RFC completo
  • Los 4 dígitos del año
  • Número de mes del auxiliar de cuentas
  • XC
  • Ejemplo, si queremos validar el auxiliar de cuentas del RFC XAXX010101XXX del mes de febrero de 2015, el archivo debe llamarse XAXX010101XXX201502XC.xml

Es importante señalar que la extensión también puede ser .zip, creo que lo manejarán así cuando se presenten las pólizas porque seguramente ese archivo va a ser muy grande.

Espero te resulte de utilidad y te ahorre mucha (pero mucha) pérdida de tiempo con el soporte técnico del SAT.