¿Entrando en la "optimización extrema"?

Siempre fui renuente a todos los artículos sobre "optimización extrema", ya que en PHP es muy poco probable que sirva hilar tan fino como para decir que:

++$a es más rápido que $a++ (aunque encontraremos opiniones opuestas)

Que foreach es más lento que un for, o que para imprimir varios valores en un echo es "más rápido" hacerlo con "," que con ".", etcétera.

No será la primera vez que diré que me parece una locura este tipo de optimizaciones, no tienen sentido en un ambiente web, en un entorno LAMP, y particularmente para PHP... a menos que estemos usando este lenguaje para hacer grandes "cálculos científicos" en memoria, pero estimo que si necesitas esto, erraste el entorno y el lenguaje para hacerlo.

"Optimizaciones Reales"


Para el que no lo sepa, en la actualidad trabajo durante la semana en una empresa argentina relacionada con productos / servicios vinculados a los celulares, y nuestra principal tarea es -muy resumidamente- enviar y recibir SMS (aunque ahora estamos empezando a trabajar con MMS).

También desarrollamos sistemas de administración y estadísticas para nosotros y para nuestros clientes (que son sistemas que ahora se hacen mayormente con Zend Framework), pero para estos sistemas nuestro tráfico es "reducido y controlado", lo opuesto a un servicio público como podría ser una red social donde los usuarios van creciendo día a día.

En este caso, de los sistemas web comunes, la "optimización extrema" no tienen ningún tipo de efecto, más que intentar hacer las cosas bien dentro de los "parámetros conocidos" y muchas veces estamos hablando de hacer las consultas sql de forma correcta (algo que no es tampoco común para la media de los desarrolladores PHP).

Pero en el primer caso, nosotros somos una empresa que podemos "estar orgullosos" que hacemos el envío de SMS/MMS a través de un ambiente web que incluye Apache, MySQL y PHP (aunque muchos podría discutir que no es la mejor tecnología, tal vez sería mejor usar arquitecturas como Java y puedo llegar a estar de acuerdo con ellos).

Desde hace unas semanas estamos haciendo funcionar una aplicación masiva en la que existen miles de usuarios enviando miles de SMS para juntar puntos en modalidad de "trivia" (se envían preguntas y se deben responder con opciones como "A" o "B") para un concurso que durará unos meses. Cada MO (mensaje originado por el usuario) enviado equivale a una instancia de apache evaluando la información recibida, registrando todos los datos y respondiendo a continuación con un MT (mensaje originado por el sistema). El juego cambia de comportamiento de acuerdo al tipo de jugador, tiene incentivadores que motivan al jugador seguir en carrera, le informa los puntos, le envía nuevos desafíos, lo recaptura si deja de jugar por distintas unidades de tiempo, etc.

Y aquí es donde entra la optimización que yo le llamaría "extrema". Les comparto algunas prácticas que fui aprendiendo sobre la marcha para que las discutamos juntos a ver que les parece y si no son tan esotéricas como las "++$a versus $a++":

Evitar el acceso a la base de datos

Generalmente el "cuello de botella" de los servidores está en el acceso a disco, el consumo de procesador es mínimo, pero sí es crítico el acceso a la base de datos y el principal recurso es acceder al disco. Una decisión de diseño fue evitar en algunos casos acceder a una tabla para traer datos y los dejamos "estáticos" en un array dentro de una clase (evitamos conectar, consultar, traer datos, desconectar).

Desventajas: perdemos "flexibilidad" ya que en vez de tener un admin que acceda a una tabla hay que modificar el código en el fuente (en nuestro caso son datos que no cambiarán frecuentemente y más que flexibilidad buscamos rendimiento y que el sistema no quede sin recursos).

Evitar repetir el acceso a base de datos

Nuevamente, nuestro cuello de botella. Algo muy común cuando desarrollamos orientado a objetos es que cada objeto es independiente del otro, y si lo hacemos "3 capas", en algún momento cada objeto terminará accediendo a la base de datos por su cuenta.

Me ha pasado de implementar un log que activo con todas las consultas del sistema y ver que se repiten algunas consultas dentro del mismo acceso al sistema, una y otra vez, cuando en sí están trayendo la misma información.

Por ejemplo (el ejemplo no es real, pero lo que importa es el concepto), imaginen que tengo el nombre del usuario en la base de datos y cada vez que hago algo como $jugador->getNombre() hace un SELECT nombre FROM jugadores WHERE id = 100;. Como la instancia se usa en varias partes del sistema y requiere verificar información del nombre en distintas oportunidades, esta consulta se repite "n" veces. Para evitar esto se cambia la operativa por una "carga tardía" (lazy load), nunca cargar nada hasta que lo necesite (no cargues toda la información en el constructor del objeto), y cuando ya tenga el valor, y si es un valor que no tiende a cambiar/actualizarse, retornar su contenido sin ir nuevamente a la base (parece tonto, pero hasta que no lo detectas que lo estás haciendo no tomas conciencia de que debes evitarlo).

Antes lo podrías tener así:



Luego lo podrías cambiar por:



Mejorar el acceso a base de datos

Algo que es poco común que un desarrollador domine, hacer correctas consultas a las bases de datos, agregar índices según lo que necesitemos hacer, evitar los índices en los lugares que no conviene, verificar el tipo de cada campo y si realmente es el que necesitamos.

Algunas pautas que recuerdo ahora:
  • Revisa por qué campos haces normalmente las condiciones de tus WHERE y crea un índice (recuerda que las claves primarias generan implícitamente un índice).
  • Recuerda que por cada índice que agregues tu tabla se recarga si esta tienen que actualizar los datos constantemente, ya que hará un "insert" en la tabla real y luego deberá actualizar el índice (como si fueran dos tablas). Si tienes 100 índices, y es una tabla grande y con muchas actualizaciones, será mejor que te tomes el tiempo y veas cual puedes eliminar.
  • Diferencia las tablas de "lectura/escritura" sobre las de "solo lectura". Si tu tabla necesita hacer ambas y sabes que crecerá, limita la cantidad de índices (ver punto anterior), pero si es de solo lectura, puedes tomarte la libertad de agregar más índices que en el primer caso.
  • Usa los mismos tipos y largos en los campos de tus tablas: si creas jugador_id y es un int(11), que así sea en todas las demás tablas, ya que unir campos que son de distintos tamaños tendrá un costo de conversión al compararlos.
  • Diferencia lo que es un varchar de un char: si tus valores de texto serán fijos, es decir, tienes un campo de 100 caracteres que será probable que siempre esté lleno, usa char(100), si puede que esté lleno de caracteres o existan casos donde no tenga nada, o pocos (10 caracteres), define que es de tipo varchar (var = variable).
  • etc
En estos casos (bueno, siempre) el manual del motor de base de datos es tu amigo.

No tengas miedo de desnormalizar una base de datos

Lo aprendí con el tiempo, no ser extremista y purista, luego de aprender las reglas, descubre cuando se justifica romperlas para sacar más provecho ;-)

Nos pasó que ante un tráfico masivo de datos estábamos teniendo un crecimiento desmedido en tablas que registran la entrega y salida de SMS, y que cuando queríamos hacer un cruzamiento de datos entre otras tablas, las consultas nos generaban pérdidas de segundos muy valiosos.

Por ejemplo, si nosotros tenemos un sistema que maneja múltiples carriers, por lógica deberíamos tener en la tabla de participantes el número de celular y el número de carrier al cual corresponde. En sistemas donde las tablas de participantes pueden crecer exponencialmente no es conveniente que por cada MO tengamos que acceder a una tabla extra para saber esta información, por lo que en tablas importantes "desnormalizamos" su acceso y duplicamos estos campos para evitar tener que hacer un join contra una tabla extra (y así con otros datos).

Duplica campos en distintas tablas (número de teléfono, carrier, id de usuario, etc) para evitar tener que hacer joins contra una tabla que crece y se actualiza constantemente.

Evita acceder a los datos de producción para hacer consultas

Aunque puede ser obvio, evita tener un sistema de estadísticas para tu clientes que hagan consultas directas contra una base de datos en producción. Cualquier consulta compleja puede tirar el rendimiento de tu sistema. Puedes intentar duplicar la base en otro servidor una vez al día, o simplemente crear "tablas resúmenes" con un proceso que corre cada "x" horas y en vez de sumar todos los datos cada vez que se consulta, ya lo haces en la carga y el sistema de estadísticas muestra solo los totales.

Si quieres darte un lujo, solo accede a producción para dar datos "reales" con un "count", y si quieres acceder a tablas, que estas no sean las más usadas.

En resumen

Fuera que descarto que debemos diseñar bien nuestro sistema pensado cual será el contexto en el cual funcionará, pensar en las clases y sus relaciones, y ver si estamos programando correctamente, a menos que estemos hablando de muchas clases por cada vez que hay una invocación en nuestro sistema (estoy hablando de 50 clases o más), lo más probable es que la optimización no se deba hacer en el código PHP, probablemente esté más orientado hacia nuestra comunicación con la base de datos (y descarto que ya estamos usando algún sistema de caché del lado del servidor).

Sé que no es de todos los días tener este tipo de situaciones de cantidades masivas de tráfico, pero cuando ocurren, estos detalles afectan el rendimiento o directamente la estabilidad del servidor.

¿Tienes algún tips para compartir? ¿cual fue tu experiencia en estos casos? ;-)

Artículos relacionados:

Retomando los artículos del blog

Estimados lectores, como sabrán (dadas mis actividades de público conocimiento y las otras, trabajo, familia, etc), he estado un poco alejado de este blog, aunque a veces escribo en mi blog personal, tengo bastantes reclamos de ustedes para que retome el ritmo aquí :-)

También voy haciendo micro-post a través de mis cuentas de twitter (la personal, la de este blog y la relacionada a SURFORCE y los cursos a distancia), pero obviamente no es lo mismo.

Intentaré seguir prácticas de otros blogs, hacer un resumen de temas aunque no me de el tiempo de desarrollar un artículo completo al respecto, pero por lo menos podré comentar temas del momento relacionadas con PHP y algunas experiencias más, y en caso de requerirlo a través de los comentarios, me extenderé en lo que pueda.

Así que, retomando ritmo, como quién dice: este lunes empiezo el gimnasio, sin falta ;-)

PD: siempre recibo sus sugerencias a través de email o en los comentarios de este blog, así que aprovechen este mismo post para tirar ideas de temas que les gustaría tratar en el corto plazo.

Abrazos! ;-)

Comentar código de forma "invisible"

Este tema tiene un origen histórico, trataré de empezar desde el principio ;-)

Cuando hace años empezamos en el mundo estático del "html" (cuando el 95% de los sitios solo eran puro html sin dinamismo, sin un lenguaje de scripting), lo que aprendimos fue a "programar html" haciendo muchas tablas (no usábamos los div's como ahora) y para seguir la "cascada" de código le agregábamos comentarios usando el tradicional <!-- -->

Por ejemplo:


Muy "cómodo" para hacer debug "manual" usando de apoyo los comentarios porque evidentemente era un "humano" el que debía controlar todo esto y se podía perder entre tanto código en cascada ;-)

Para los "técnicos" esto siempre fue desprolijo, ya que para nuestros futuros clientes o hasta curiosos, estábamos revelando detalles internos de nuestra aplicación / diseño. Está de más decir que no es para nada recomendable hacer comentarios técnicos importantes en ellos ya que es "código público" que cualquiera puede leer, y tampoco comentar código que no se va a usar, ya que es extremadamente desprolijo (si no lo quieres perder, respalda, o versiona).

Nota: no será la primera y última vez que veo en los comentarios de un código html insultos del propio desarrollador, que la verdad queda muy feo ;-)

Primer sugerencia, el código de producción debe estar "limpio", y si conoces algún sistema que pueda contener los comentarios en tu código de desarrollo e impida que quede en producción, úsalo.

Algunos ejemplos.

Usando el propio lenguaje de scripting: PHP

Cuando la tecnología evolucionó y empezamos a contar con lenguajes de scripting como PHP, la primera práctica que empezamos a adoptar fue el de embeber el código "dinámico" dentro de nuestro código html, pero dejamos todas las demás prácticas intactas, por lo que los comentarios seguían estando públicos en el html.

Con el tiempo, dejamos de "embeber" y ya no era tan evidente la manipulación del código html, empezamos a hacer invocaciones a funciones de tipo:

generarCabezalHtml();
generarContenido('Hola Mundo');
generarPie();


Por lo que ya no es el "humano" que debe seguir tan literalmente el código, ver si cierra o no, porque podemos validarlo a través de herramientas, y luego tratar de seguirlo a través del código PHP (aquí también ayudan mucho los IDEs en la pre-validación del código y evitar errores).

Una opción posible es mover todos los comentarios html al código php y estos quedan "privados" dentro del sistema.

Si esto no fuera suficiente, aún se podría generar algo como una configuración que diga "habilitarComentariosHtml" y que genere html con comentarios embebidos, y una vez en producción, eliminar esta opción (pero estaríamos generando mucho código poco productivo para el sistema).

Sigamos un siguiente nivel en la escala evolutiva ;-)

Usando sistemas de plantillas: Smarty

Con los años esto no nos quedó muy cómodo y preferimos optar por un sistema de "plantillas" (templates), y limpiar nuestro código PHP. Smarty fue una de las mejores opciones por años, lo cual te ofrecía además un código simil PHP del lado del template, pero podíamos volver a caer en la misma práctica, hacer comentarios html públicos.

Repitiendo lo que hablamos en el punto anterior, podíamos adoptar el mecanismo de comentarios de Smarty, lo cual genera el mismo efecto que hacerlo desde PHP. Se hacía con {* comentarios *}

{* display dropdown lists *}
<
select name="company">
{
html_options options=$vals selected=$selected_id}
select>


La diferencia con el primer caso de "solo html" es que lo estamos haciendo en la plantilla y no es público, y tampoco lo estamos haciendo del lado de PHP ya que toda la parte de generado de html era responsabilidad de Smarty (como una separación de capas).

Siguiendo con la evolución...

Nos pasamos a Zend Framework y usamos "Vistas"

Las vistas son como al principio de nuestra era, código html donde se embebe código PHP de la siguiente forma (un mix entre PHP embebido y sistema de plantillas).




Y volvemos al principio, he visto muchos proyectos que para poder hacer seguimiento de lo que generan de html tienen comentarios públicos html por todos lados, por lo que mi sugerencia final es... hagamos los comentarios dentro de la la vista, dentro del código PHP embebido y no dejemos expuestos nuestros comentarios internos.

Existen también herramientas que limpian el código, lo compactan y hasta te lo ofuscan, bien podrían ser una alternativa.

Pero la idea es esa, trata de no dejar comentarios que los pueda leer todo el mundo, sé ordenado y limpio. En lo personal cuando voy a ver páginas de diseño web que se ufanan de seguir "estándares", descubrir luego tablas o comentarios del tipo "abre tabla", "cierra tabla", etc, no me dejan muy tranquilo.

¿Qué opinas? ¿cuales son tus prácticas? ¿o no te importa que puedan descubrir leyendo el código de tu sitio? ;-)

Entradas populares