View
5.812
Download
3
Category
Preview:
DESCRIPTION
Ponencia sobre rendimiento web orientado a aplicaciones web Symfony2, para el evento #desymfony 2011
Citation preview
Asier Marqués @asiermarques
Socio fundador de Blackslot.com
Socio fundador de Artesanio.com
Organizador de #webdevbilbao
Arquitecto web, ex-administrador de sistemas Microsoft, apasionado de la creación de servicios en internet y adicto al café.
Escribo en asiermarques.com
Rendimiento Web
El rendimiento importaSegún kissmetrics.com:
El 73% de los usuarios de móvil ha encontrado algún sitio web demasiado lento en cargar.
El 47% de los usuarios esperan un máximo de 2 segundos a que cargue una página web en la que van a comprar algo.
Si un sitio de comercio electrónico está ganando $ 100.000 por día, un retraso de 1 segundo en la página podría costar $ 2.5 millones de perdidas al año.
http://blog.kissmetrics.com/loading-time
Rendimiento no es escalabilidadPero es clave que la aplicación esté bien diseñada y sea escalable.
Cómo diseñar bien
• No te repitas. DRY
• Divide. MVC
• Calidad. TDD
• Symfony2 te ayuda a conseguirlo.
“la optimización prematura es la raíz de todo mal”
Donald Knuth
Antes de optimizar
• No resuelvas problemas que no tienes
• No tomes decisiones tecnológicas en base a lo que hacen “los grandes”
• Monitoriza, estudia la información y después optimiza
• Evita operaciones síncronas que supongan demora
Usa InnoDB!
MyISAM
Bloqueos a nivel de tabla, no de filaLos selects pesados bloquean la tabla frente a escrituraLas escrituras bloquean la tablaLOW_PRIORITY, HIGH_PRIORITYhttp://dev.mysql.com/doc/refman/5.0/en/table-locking.html
No soporta transacciones
No es relacional
InnoDB
Relacional
Bloqueos de fila, no de tabla
Soporta transacciones• Doctrine2 hace uso interno de
transacciones
Configuración
Innodb_buffer_pool_size
Innodb_log_file_size
Innodb_flush_log_at_trx_commit=2
innodb_flush_method=O_DIRECT
innodb_file_per_table
Query Cachehttp://dev.mysql.com/doc/refman/5.1/en/query-cache-status-and-maintenance.html
query_cache_type = 1 # 0 desactivada, 1 activada, 2 bajo demandaquery_cache_size = <tamaño de cache>
SHOW STATUS LIKE 'Qcache%';
! -> Qcache_hits: Número de aciertos de la query caché.
! -> Qcache_lowmem_prunes: El número de consultas eliminadas de la cache por falta de memoria disponible.
! -> Qcache_inserts: El número de consultas insertadas a la cache.
El espacio importa
DivideParticionado
Vertical (por columna)Horizontal (por fila)http://mysql.isu.edu.tw/tech-resources/articles/testing-partitions-large-db.html
Mantener los datos de texto opcionales en otra tabla
Configura InnoDB con innodb_file_per_table Cada archivo maneja1) Los datos de la tabla2) Los datos de los índices3) Los MVCC (Multiversioning Concurrency Control) 4) La metainformación de la tabla
Optimize Table reduce la información de cada archivo y reorganiza los índices
Índices
Usa EXPLAIN para saber cuando utilizarlos
NOT NULL cuando sea posibleUna columna indexada que pueda ser NULL ocupa más espacio
Usa el tipo de datos correcto
EXPLAIN <QUERY>
Valores en los que fijarte
typeALL indica que debe recorrerse toda la tabla y debe evitarseINDEX se debe recorrer todo el índice
key y key_lenCuanto menor sea el valor de KeyLen mejorKey te indica el Índice elegido para la consulta
EXTRAEvitar Filesort y tablas temporalesLas tablas temporales aparecen al ordenar resultados o al hacer agrupaciones
Índices y espacio: Tipos de datos
Usa INT en claves primarias como unsigned en lugar de BIGINT.
Usa TINYINT en lugar de INT(1).TINYINT es 1 Byte, INT(1) son 4
Usa BIT para booleanos en lugar de INT(1) o TINYINT
Usa TIMESTAMP en lugar de DATETIMETIMESTAMP son 4 Bytes, DATETIME son 8
Índices y espacio: Tipos de datos
Usa CHAR(num) en lugar de VARCHAR(num) para valores fijos
No uses palabras en ENUM(), usa un solo carácter si es posible
Usa unsigned INT para guardar ips, no varchar
Usa utf8 cuando sea realmente necesarioVarchar(255) en utf8 son más de 700 bytes
Conoce tus consultas
Es peor tener 83 consultas simples que una consulta “compleja”
No hagas operaciones matemáticas en tus consultas
Aligera en la medida de lo posible tus consultas
No hagas JOINs en campos que no tengan índice
Cuidado con los count y los order
Conoce tus consultas
Count(*)Usa mejor SQL_CALC_FOUND_ROWS
OrderByfilesort en la mayoría de los casosrecupera los PK en una consulta simple antes de hacer una consulta compleja
Ejemplo típico en doctrine
Es típico hacer consultas innecesarias dentro de un bucle.
Tomemos por ejemplo la aplicación de #desymfony
Vista de PonentesSe ejecuta el siguiente dql en el controlador
SELECT p FROM Desymfony\DesymfonyBundle\Entity\Ponente pORDER BY p.nombre ASC
Y en la vista se hace
{% for ponente in ponentes %} … {% for ponencia in ponente.ponencias %}
Resultado
Cada iteración de Ponente hace una query de Ponencias
Tenemos 13 ponentes, se ejecutan 13 querys
Con 200 ponentes, se ejecutarían 200 querys
La solución es sencilla
En el DQL
SELECT p, po FROM Desymfony\DesymfonyBundle\Entity\Ponente p INNER JOIN p.ponencias po ORDER BY p.nombre ASC
Solamente con añadir el INNER JOIN reducimos el número de querys a tan sólo una en lugar de 13 o (n).
Symfony y Doctrine
Usar arrays en las vistas en lugar de objetos
Usar la cache de doctrineCache de DQL$config->setMetadataCacheImpl(new Doctrine\Common\Cache\ApcCache());
Cache de resultados$config->setQueryCacheImpl(new Doctrine\Common\Cache\ApcCache());
Búsqueda
InnoDB no soporta FULLTEXT
Sphinx, Lucene, SolrBuenas opciones incluso para reducir complejidad en consultas, pe. Ordenar, filtros complejos..
Analiza
Debes llevar un control de lo que sucede a diario en tu servidor y comparar los valores que obtengas cuando este tenga problemas con tus valores normales.
Herramientas: Munin, Nagios, Cacti..
Analiza mysqladmin extended
Threads_runningNúmero de consultas que se están ejecutando.
Handler_read_rnd_next y Handler_read_firstSi es muy superior a lo normal puede que sea debido a un problema con los índices.
Handler_rollback El número de rollbacks que se han hecho.
Select_full_joinJoins sin índices, debe ser cero.
Slow_queriesSi este valor crece indica problemas en el rendimiento de la aplicación.
AnalizaSHOW FULL PROCESS LIST\G
Para saber cuánto tardan las consultas pesadas en ejecutarse y las que se encuentran en estado “Locked”
SHOW TABLE STATUS FROM <base datos> LIKE ‘<nombre>’\GComprobar el espacio de la tabla (el Max_data_length nunca puede ser consumido por completo)
EXPLAIN [EXTENDED] <query>
SHOW INNODB STATUS\GEstadísticas de acceso a disco, transacciones, consultas con problemas de integridad..
SHOW PROFILE Comprobar los recursos de hardware consumidos por una queryhttp://dev.mysql.com/doc/refman/5.0/en/show-profiles.html
NO SQL
No lo uses por moda, conoce la tecnología y los problemas que solventaConoce sus posibilidades de escaladoConoce su rendimientoConoce sus posibilidades de backup/restore!
Funciona bien como frontend de una base relacional si su motor es rápido
Buena alternativa a desnormalizar
Cache
Http Cache – RFC 2616
Symfony2 implementa la cache definida en el RFC
Sigue los conceptos Expiración-Validación
Symfony2 tiene su propio proxy cache pero permite ser integrado con sistemas proxy cache como Varnish.
Cache-Control
PublicTodos los usuarios comparten la cache
PrivateLa cache es distinta para cada usuario
No-CacheNo se cachea en ningún casoEn Symfony2 por defecto es no-cache o private
Expires y Cache-Control: max-age
expires (fecha GMT)Frescura = expires – fecha actual
max-age (segundos)s-maxage se usa por servidores proxy, es público por lo que es común a todos los usuarios
max-age tiene prioridad sobre Expires ..y s-maxage sobre el max-age si es pública
expires y max-age en Symfony2
$response = $this ->render('DesymfonyBundle:Ponente:index.html.twig', array('ponentes' => $ponentes)); $response->setPublic(); $response->setMaxAge(10); <- Private $response->setSharedMaxAge(10); <- Public
$date = new DateTime(); $date->modify('+60 seconds');
$response->setExpires($date); <- Private (si no hay un setPublic())
return $response;
expires y max-age en Symfony2
Primera carga de la página, 234ms
Carga desde la caché, 3ms
Validación: Last Modified y ETAG
Last ModifiedFecha de última modificación del archivo, si el servidor retorna una fecha menor a la de la caché del cliente, descargará el archivo.
EtagEl navegador cachea el archivo identificándolo con el valor del Etag.
El navegador comprobará si debe servir el archivo comparando el Etag del servidor con el valor que él tiene almacenado en su cache, para ello usará el campo If-None-Match
Last Modified y ETAG en Symfony2
//debemos crear los métodos en la entidad$etag = $entity->getCurrentETag();$mod_date = $entity->getLastModification();
$response->setETag($etag);$response->setLastModified($mod_date);
if($response->isNotModified($this->get('request'))){ …
Varnish
Varnish
• Proxy cache
• Balanceador de carga
• Lenguaje de configuración VCL
• Soporte para ESI y HTTP Cache
Varnish
Podemos especificar
qué es lo que queremos cachear
Declaramos el servidor o
servidores web frontales
Retornando pass evitamos que se cachee y con lookup
forzamos a que se busque en el cache
VarnishPodemos
indicar un ttl para el objeto en la cache
También podemos evaluar si queremos
cachear desde aquí
Varnish: invalidar cacheIndicamos los Host que pueden lanzar
peticiones de invalidación
Si el request es de tipo PURGE se elimina la
url del host especificado
curl -X PURGE http://dominio.com/url-a-invalidar
Varnish: ESI
Varnish: ESISI una url contiene tags ESI basta con
indicar esi; para que Varnish lo procese
Podemos establecer que si la ruta es /esi/*
le ponga otro ttl o retorne pass, evitando
que se cachee
Varnish con Symfony2
En la plantilla{% render '...:vista' with {}, {'standalone': true} %}Devuelve..<esi:include src=“http://dominio/url_al_contenido”/>
Symfony2 añade automáticamente sólo cuando use ESI un header Surrogate-Control = "abc=ESI/1.0“.
En Varnish le debemos indicar que es capáz de procesar ESI con set req.http.Surrogate-Capability = "abc=ESI/1.0“.http://symfony.com/doc/2.0/cookbook/cache/varnish.html
Más cosas
APC
ByteCode cache para PHP
APC.STAT = 0En producción si no hay cambios frecuentes
Assetic
Agrupa los estáticos indicados de tu sitio web en un único archivo.
Se puede escribir en disco, en un cdn (pe.Amazon s3) o manejar como string.
Comprime y optimiza los css, js e imágenes.
Reduce las peticiones HTTP
Se integra con HTTP Cache de Symfony2 y con la sintaxis de Twig
Assetic
$assets = new AssetCollection( array( new FileAsset(“web/Bundle/style.css”), new GlobAsset(“web/*.css”), )
);$assets->load();
$writer = new AssetWriter(“s3://bucket”);$writer->writeManagerAssets($assets);
Assetic en Symfony2
{% javascripts '@Bundle/Resources/public/js/*' filter=‘nombre_js' %} <script src="{{ asset_url }}"></script> {% endjavascripts %}
$ php app/console assetic:dump s3://bucket
Usa un CDN público
Ahorro de ancho de banda
El navegador probablemente tenga ya cacheadas las librerías comunes (pe. jquery)
Operaciones en tiempo real
El tiempo real no es necesario en la mayoría de los casos.
Envío de emailsProcesamiento de imágenesInvalidaciones de cache
Herramientas: RabbitMQ, Gearman.
Otras soluciones:Queue de SwitfmailerImplementar nuestra solución en memcached, redis..
Usa Html5 y CSS3
Async
Cache Manifest
<html lang="en" manifest=“ejemplo.manifest">
CACHE MANIFEST
CACHEclock.js clock.css
NETWORKno_se_cachea.php
Cache Manifest
El manifiesto se descarga en base a nuestras indicaciones en el HTTP Cache
..o forzándolo con JavaScript mediante el método window.applicationCache
La caché se invalida mediante una comparación byte-for-byte entre el archivo más reciente y el anterior.
No uses imágenes, usa CSS3Border-radius
Gradient
Text-shadow y Box-shadow
Canvas
Font-face
¿Preguntas?
Gracias
Asier MarquésBlackslot
@asiermarqueslinkedin.com/in/asierasiermarques@blackslot.com
Imágeneshttp://www.flickr.com/photos/caharley72/11332057
http://www.flickr.com/photos/poetatum/3457696479
http://www.flickr.com/photos/32299138@N08/5772093221
http://www.flickr.com/photos/aereimilitariorg/3956024476
http://www.flickr.com/photos/zombieite/5181067906
http://www.flickr.com/photos/bcash67/4496036286
Recommended