martes, 29 de abril de 2008

Mi solución al problema de inserción masiva en SQL Server

El título de este artículo puede parecer un poco egocéntrico pero hay que tener en cuenta que es la continuación de este otro y que además me refiero a una solución valida al problema que planteé en el entorno de producción que me afecta.

Recapitulando un poco para el que no quiera leerse el post enlazado, el problema en cuestión era conseguir unos 50 000 inserciones en un SGBD MS SQLServer. Todo ello sin ejecutar cada vez un

INSERT INTO MiTabla ...

El caso es que dicho entorno de producción tiene una particularidad tal vez no muy deseable: el SGBD y el servidor web corren en la misma máquina. Debido a esto se pasaba por mi cabeza que tal vez un Bulk Insert fuese una buena solución pero no me lo terminaba de creer porque suponía que esa instrucción se convertiría posteriormente en mis 50 000 "insert into", con lo cual incluso perdería mas tiempo.

El código fuente que utilicé aproximadamente en mis pruebas es el siguiente:


define("MAX",100000);
define("TAM_BLOQUE",10000);
mssql_connect("miservidor","miusuario","mipass");
mssql_select_db("MiDB");


mssql_query("TRUNCATE TABLE Cola");
$t1=time();
$query="BULK INSERT Cola
FROM 'C:\\www\\pruebas\\envio.txt'
WITH
(
KEEPNULLS,
FIELDTERMINATOR = '|',
ROWTERMINATOR = '\r\n'
)";
$i=0;
$fecha=date();
while ($i < MAX){
$str=null;
while ($j < TAM_BLOQUE && $i<=MAX){
$str.="0|0|".(620000000+$i)."|".$fecha."||2|12|8|1|1|un texto mas o menos largo|hola|prueba|10|http://www.google.com|1|1|0|0|0|0|0|0|0||\r\n";
$i++;$j++;
}
$fd=fopen("envio.txt","w");
fwrite($fd,$str);
fclose($fd);
mssql_query($query);
}
$t2=time();
echo "Tiempo: ".($t2-$t1);


Como se puede observar hay dos bucles anidados para evitar hacer todo el trabajo de golpe en caso de que se superen ciertos límites. Esto es así porque durante las pruebas observe que el uso de CPU se sostenía cerca del 100% durante bastante tiempo si se mandaba un sólo archivo de golpe. Escribir varios archivos le daba oxígeno al servidor y no me hacía perder un tiempo considerable en la ejecución total del script.

Sin duda el hecho de que el Apache pudiese escribir en el disco duro que comparte con el SQL Server aceleró bastante las cosas, pero esto puede hacer que a muchos de vosotros no os sirva la solución. Si no es vuestro caso tal vez podáis montar una unidad lógica para obtener unas condiciones parecidas pero sufriréis una latencia adicional por ello (aclaro que yo no he probado esto último).


Para los más curiosos detallaré un poco el entorno de pruebas que utilicé: se trata de un servidor con un procesador Intel Pentium 4 a 2,66 GHz con 1 GB de RAM y SQL Server 2000.

Os recuerdo algo que dejé caer en el anterior post y que es la moraleja de todo esto: no os fiéis de nada que encontréis en un blog, el blogger no tiene por que ser un gurú aunque él mismo lo crea. Por supuesto que este artículo está incluido en la moraleja así que si os interesa ponedlo en duda y haced vuestras propias pruebas antes de darlo por bueno.

martes, 22 de abril de 2008

Posts de interés

No os voy a contar mi vida. Sólo diré que estoy en horas bajas y no puedo terminar ninguno de los artículos que tengo pendientes, así que os dejo algunos posts interesantes que he leído estas últimas semanas.

Joomla y capa de presentación para móviles. De uno de los tipos que más sabe sobe presentación móvil. Interesante artículo para aquellos que quieran adaptar su aplicación Joomla a terminales móviles.

Presentación
sobre testeos con PHPUnit, DBUnit, etc. Una buena lectura para iniciarse con PHPUnit y demás-

Guerra de frameworks. Respuesta de un desarrollador Symfony a otr de Rubi on Rails ¿Que puedo decir? si alguien se enzarza en una guerra dialéctica más vale saber de que se habla.


Peticiones simultaneas en PHP usando CURL. En realidad no son tan simultaneas y recomiendo probar su viabilidad antes de implantarlo. En nuestro caso finalmente lo desechamos.

Situación del UML. Esta es la causa de mi anterior post sobre el uso de UML.

Modelo de contrato ágil
. Solo es un patrón pero me parece interesante.

miércoles, 9 de abril de 2008

Sobre el uso de UML

Hace un par de días el autor de uno de mis blogs preferidos se preguntaba si el UML estaba pasado de moda. Lo del uso o desuso del UML es un tema que me toca la fibra. En principio yo pienso que no ha terminado de extenderse todo lo que se presumía, pero aún así lo creo algo imprescindible y a mi modo de ver se irá propagando poco a poco.

Uno de los primeros problemas que veo es el modo en el que empezó a enseñarse. Los que como yo lo hayáis estudiado hace unos siete años, seguramente, habréis aprendido toda una metodología cerrada en la que empezabais por los casos de uso, seguías con los diagramas de clases y así sucesivamente en un orden fijo preestablecido, bastante aburrido por cierto, en el que lo último que dibujaríais sería el diagrama de despliegue. Ni que decir tiene que en mi caso me limité a aprobar la asignatura y después lo dejé un poco de lado (ummm ¿no hice eso con casi todas? ¡Ah! ¡No! Hice esa carrera por que me gustaba :p ).

Afortunadamente parece que este modo de ver las cosas ha ido cambiando. En realidad todo es mucho más flexible pero tal vez no hemos entendido aún que hay miles de formas de usar UML y su uso tiene que ser diferente en base al perfil de quien lo haga:
  • Un programador debe saber "leer" UML y no tiene por qué escribirlo. Parece una tontería pero leer bien un diagrama de clases que modela el diseño de una parte de la aplicación significa que sabremos como escribir el código de esas clases: una herencia se convertirá en un "extends" (fácil), pero ¿y una agregación? ¿y una composición? Esto implica también que quien haga el diseño debe saber lo que está pintando ya que repercutirá en el código.
  • Probablemente las capas altas de tu organización y los clientes finales no habrán ni oído hablar de este lenguaje, así que mostrarles un diagrama de interacción o estados puede ser bastante inútil. Sin embargo no se debe desechar la posibilidad de enseñarles algún diagrama de casos de uso, despliegue, o incluso de clases orientado al análisis (preferiblemente representando las clases como cajas simples, sin los compartimentos de atributos y métodos).
  • Por supuesto que los que se encuentren entre los dos extremos (analistas de cualquier tipo, coordinadores, etc.) deberían saber leer y escribir UML.
Por otro lado es muy importante escoger la herramienta CASE correcta para nuestras necesidades. No creo que mucha gente que se desenvuelva con Java o .Net escogiera ArgoUML como herramienta pero, como dije hace un par de posts, para un escenario basado en PHP puede ser una opción muy interesante.

Una vez escogida la herramienta más adecuada la forma de aplicar UML vendrá determinada en gran medida por el entorno en el que nos encontremos. Puede influir en ello la carga de trabajo, los conocimientos de UML de todo el equipo de trabajo, la naturaleza de los proyectos, etc.

En definitiva cada equipo debe encontrar su modo idóneo de utilizar UML, los ingenieros debemos acostumbrar a programadores y altos mandos a recibir pequeñas raciones de diagramas cuando lo creamos necesario. No olvidemos tampoco que en este país muchos ponen en duda que nuestro trabajo sea una ingeniería y en parte se debe a que todas nuestras metodologías de trabajo están verdes, por eso creo, aplicando un símil con el mundo de la construcción (como no) que nuestra herramienta CASE debe ser lo mismo para nosotros que el AutoCAD para un arquitecto.

sábado, 5 de abril de 2008

Optimizando inserciones múltiples en SQL server y el mito del blogger gurú

Empezará por el final, por la moraleja: no hay que fiarse de todo aquél que escribe un blog y por supuesto tampoco hay que considerarlo un gurú. Esta afirmación no deja de ser paradójica ya que me incluye a mi mismo aunque ni mucho menos afirmo ser un gurú... pero en fin dejémoslo.

Imaginad que tenéis una tabla en una base de datos que se debe poblar de forma masiva con una información ya conocida que tras ser procesada mediante un script PHP o similar se materializará en miles de registros. En mySQL existe una bonita sintaxis que si no recuerdo mal es algo así como


INSERT INTO tabla (campo1,...,campoN)
(valor1_1,..., valor1_N),
...
(valorN_1,..., valorN_N)


Lamentablemente esto no existe en SQL Server que es el gestor de base de datos con el que me enfrento a diario, así que me puse a buscar en google alguna solución que me evitara lo que venimos haciendo hasta ahora:



INSERT INTO tabla (campo1,...,campoN) VALUES (valor1_1,..., valor1_N)
...

INSERT INTO tabla (campo1,...,campoN) VALUES (valorN_1,..., valorN_N)


Así es como fui a dar con un blog donde se proponía la siguiente opción como algo bastante más eficiente:


INSERT INTO tabla (campo1,...,campoN)
SELECT valor1_1,..., valor1_N
UNION ALL
SELECT valor2_1,..., valor2_N
...
UNION ALL

SELECT valorN_1,..., valorN_N


A partir de ahí un montón de comentarios al post original en plan, "¡fantástico!", "¡lo he probado con 200 registros y gano x ms!", que van degenerando poco a poco en los típicos "Lo he probado y me devuelve un error..." y la consiguiente respuesta "Has escrito mal esto o lo otro". Debido a esto y a que había más de 100 comentarios cometí mi primer error leyendo sólo una docena de ellos. El segundo fue no sospechar del operador UNION y el tercero simplemente darlo por bueno.

¿Y por qué no darlo por bueno? Era una solución escrita en un blog. Seguro que el tipo que aparece fotografiado es un gurú del SQL. Sólo había que ver el nombre del blog: SQLAuthority, como para no fiarse (leer esto imaginando la voz de Robert de Niro en Taxi Driver):
¡Joder tío! ¡Ese tipo es la puta autoridad del SQL, tío!
Tan convencido estaba de mi "descubrimiento" que escribí un mail a unos cuantos compañeros para que se aprovecharan de ello.

Afortunadamente un día después empecé a hacer algunas pruebas. A mi me interesaba que fuese rápido insertando unos 50.000 registros en una tabla con unos 20 campos, tres o cuatro de texto y el resto numéricos. Tras montar la consulta y ejecutarla en el servidor... sorpresa: el motor de la base de datos devuelve un error porque la consulta es demasiado grande. Empiezo a reducir la consulta para encontrar el límite: 25000 inserciones,10000, 5000... y así hasta que finalmente traga con 1000, bastante desesperanzador para mi propósito.

Aún así me empeñé en compararlo con el método que venía utilizando y... ¡era más lento! Tras varias pruebas siempre tardaba un par de segundos más. El método de siempre tardaba unos 8 segundos y el propuesto unos 10. Menuda decepción, además con el tradicional me ahorraba el problema del error con el tamaño de la consulta.

No me lo podía creer y la gente del blog maravillada con el invento. Volví al post y seguí leyendo. Había alguno que llegaba a las mismas conclusiones que yo pero la mayoría de la gente estaba encantada. El autor en un momento dado reconocía que existía el límite del tamaño de la consulta pero que el conseguía insertar así varios miles de registros.

En fin, a mi no me sirvió y acepto sugerencias a mi problema: quiero hacer unas 50.000 inserciones en una tabla sin tener que ejecutar 50.000 INSERTs y además que me resulte más rápido ¿alguna solución? Yo he dado con una que me sirve debido a las particularidades del entorno de producción que tenemos, o sea, no es una solución universal. La explicaré en otro post. Por cierto, envié un segundo mail a mis compañeros recomendando olvidar el primero.