¿Cómo optimizar la aplicación web PHP Laravel para un alto rendimiento?

Laravel es muchas cosas. Pero rápido no es uno de ellos. Aprendamos algunos trucos del oficio para que sea más rápido.!


Ningún desarrollador de PHP no ha sido tocado por Laravel estos días. Son desarrolladores junior o de nivel medio que aman el rápido desarrollo que ofrece Laravel, o son desarrolladores senior que se ven obligados a aprender Laravel debido a las presiones del mercado..

De cualquier manera, no se puede negar que Laravel ha revitalizado el ecosistema PHP (yo, seguro, habría abandonado el mundo PHP hace mucho tiempo si Laravel no estuviera allí).

Un fragmento de elogio (algo justificado) de Laravel

Sin embargo, dado que Laravel se inclina hacia atrás para facilitarle las cosas, significa que debajo está haciendo toneladas y toneladas de trabajo para asegurarse de que tenga una vida cómoda como desarrollador. Todas las características “mágicas” de Laravel que parecen funcionar tienen capas sobre capas de código que deben ser creadas cada vez que se ejecuta una característica. Incluso una simple Excepción rastrea la profundidad del agujero del conejo (observe dónde comienza el error, hasta el núcleo principal):

Para lo que parece ser un error de compilación en una de las vistas, hay 18 llamadas de función para rastrear. Encontré personalmente 40, y fácilmente podría haber más si está utilizando otras bibliotecas y complementos.

El punto es, por defecto, estas capas sobre capas de código, hacen que Laravel sea lento.

¿Qué tan lento es Laravel??

Honestamente, es simplemente imposible responder esta pregunta por varias razones.

primero, no existe un estándar aceptado, objetivo y sensato para medir la velocidad de las aplicaciones web. ¿Más rápido o más lento en comparación con qué? Bajo que condiciones?

Segundo, una aplicación web depende de tantas cosas (base de datos, sistema de archivos, red, caché, etc.) que es tonto hablar de velocidad. Una aplicación web muy rápida con una base de datos muy lenta es una aplicación web muy lenta. ��

Pero esta incertidumbre es precisamente por qué los puntos de referencia son populares. Aunque no significan nada (ver esta y esta), proporcionan un marco de referencia y nos ayudan a volvernos locos. Por lo tanto, con varias pizcas de sal listas, tengamos una idea errónea y aproximada de la velocidad entre los marcos PHP.

Yendo por este GitHub bastante respetable fuente, así es como se alinean los frameworks PHP en comparación:

Es posible que ni siquiera note a Laravel aquí (incluso si entrecierra los ojos realmente) a menos que lance su caso hasta el final de la cola. Sí, queridos amigos, ¡Laravel es lo último! Ahora, por supuesto, la mayoría de estos “marcos” no son muy prácticos ni útiles, pero nos dice cuán lenta es Laravel en comparación con otros más populares..

Normalmente, esta “lentitud” no aparece en las aplicaciones porque nuestras aplicaciones web cotidianas rara vez alcanzan números altos. Pero una vez que lo hacen (digamos, más de 200-500 concurrencia), los servidores comienzan a ahogarse y morir. Es el momento en que incluso arrojar más hardware al problema no lo soluciona, y las facturas de infraestructura aumentan tan rápido que sus altos ideales de computación en la nube se derrumban.

Pero oye, anímate! Este artículo no trata sobre lo que no se puede hacer, sino sobre lo que se puede hacer. ��

La buena noticia es que puedes hacer mucho para que tu aplicación Laravel vaya más rápido. Varias veces rápido. Sí, no es broma. Puede hacer que el mismo código base se vuelva balístico y ahorre varios cientos de dólares en facturas de infraestructura / hosting cada mes. ¿Cómo? Hagámoslo.

Cuatro tipos de optimizaciones.

En mi opinión, la optimización se puede hacer en cuatro niveles distintos (cuando se trata de aplicaciones PHP, es decir):

  1. Nivel de idioma: Esto significa que usa una versión más rápida del idioma y evita características / estilos específicos de codificación en el idioma que hace que su código sea lento.
  2. Nivel de marco: Estas son las cosas que cubriremos en este artículo..
  3. Nivel de infraestructura: Ajuste su administrador de procesos PHP, servidor web, base de datos, etc..
  4. Nivel de hardware: Pasar a un proveedor de alojamiento de hardware mejor, más rápido y más potente.

Todos estos tipos de optimizaciones tienen su lugar (por ejemplo, la optimización php-fpm es bastante crítica y poderosa). Pero el enfoque de este artículo serán optimizaciones puramente de tipo 2: las relacionadas con el marco.

Por cierto, no hay justificación detrás de la numeración, y no es un estándar aceptado. Acabo de inventar esto. Por favor, nunca me cite y diga: “Necesitamos optimización de tipo 3 en nuestro servidor”, o el líder de su equipo lo matará, me encontrará y luego me matará a mí también. ��

Y ahora, finalmente, llegamos a la tierra prometida..

Tenga en cuenta las consultas de base de datos n + 1

El problema de consulta n + 1 es común cuando se usan ORM. Laravel tiene su poderoso ORM llamado Eloquent, que es tan hermoso, tan conveniente que a menudo nos olvidamos de mirar lo que está sucediendo..

Considere un escenario muy común: mostrar la lista de todos los pedidos realizados por una lista dada de clientes. Esto es bastante común en los sistemas de comercio electrónico y en cualquier interfaz de informes en general, donde necesitamos mostrar todas las entidades relacionadas con algunas entidades..

En Laravel, podríamos imaginar una función de controlador que haga el trabajo de esta manera:

clase OrdersController extiende Controlador
{
// …

función pública getAllByCustomers (Request $ request, array $ ids) {
$ clientes = Cliente :: findMany ($ ids);
$ pedidos = recoger (); // nueva colección

foreach ($ clientes como $ cliente) {
$ pedidos = $ pedidos->fusionar ($ cliente->pedidos);
}

vista de retorno (‘admin.reports.orders’, [‘pedidos’ => $ pedidos]);
}
}

¡Dulce! Y lo más importante, elegante, hermoso. ����

Desafortunadamente, es una forma desastrosa de escribir código en Laravel.

Este es el por qué.

Cuando le pedimos al ORM que busque los clientes dados, se genera una consulta SQL como esta:

SELECCIONAR * DE los clientes DONDE ENTRAR ID (22, 45, 34,…);

Que es exactamente como se esperaba. Como resultado, todas las filas devueltas se almacenan en la colección $ clients dentro de la función del controlador.

Ahora recorremos cada cliente uno por uno y obtenemos sus pedidos. Esto ejecuta la siguiente consulta . . .

SELECT * FROM pedidos DONDE customer_id = 22;

. . . tantas veces como haya clientes.

En otras palabras, si necesitamos obtener los datos de pedido para 1000 clientes, el número total de consultas de base de datos ejecutadas será 1 (para obtener todos los datos de los clientes) + 1000 (para obtener datos de pedidos para cada cliente) = 1001. Esto es de donde viene el nombre n + 1.

¿Podemos hacerlo mejor? ¡Ciertamente! ¡Al usar lo que se conoce como carga ansiosa, podemos forzar al ORM a realizar una UNIÓN y devolver todos los datos necesarios en una sola consulta! Me gusta esto:

$ orders = Customer :: findMany ($ ids)->con (‘pedidos’)->obtener();

La estructura de datos resultante es anidada, claro, pero los datos del pedido se pueden extraer fácilmente. La consulta única resultante, en este caso, es algo como esto:

SELECCIONAR * DE los clientes INTERIORES ÚNETE a los pedidos de clientes.id = orders.customer_id DONDE customers.id IN (22, 45,…);

Una sola consulta es, por supuesto, mejor que mil consultas adicionales. ¡Imagínese lo que sucedería si hubiera 10.000 clientes para procesar! ¡O Dios no lo quiera si también quisiéramos mostrar los artículos contenidos en cada orden! Recuerde, el nombre de la técnica es una carga ansiosa, y casi siempre es una buena idea.

Caché la configuración!

Una de las razones de la flexibilidad de Laravel es la gran cantidad de archivos de configuración que forman parte del marco. Quiere cambiar cómo / dónde se almacenan las imágenes?

Bueno, simplemente cambie el archivo config / filesystems.php (al menos al momento de la escritura). ¿Quieres trabajar con múltiples controladores de cola? Siéntase libre de describirlos en config / queue.php. Solo conté y descubrí que hay 13 archivos de configuración para diferentes aspectos del marco, lo que garantiza que no te decepcionará sin importar lo que quieras cambiar.

Dada la naturaleza de PHP, cada vez que ingresa una nueva solicitud web, Laravel se despierta, arranca todo y analiza todos estos archivos de configuración para descubrir cómo hacer las cosas de manera diferente esta vez. ¡Excepto que es estúpido si nada ha cambiado en los últimos días! Reconstruir la configuración en cada solicitud es un desperdicio que se puede evitar (en realidad, se debe evitar), y la salida es un comando simple que ofrece Laravel:

php artisan config: caché

Lo que esto hace es combinar todos los archivos de configuración disponibles en uno solo y el caché está en algún lugar para una recuperación rápida. La próxima vez que haya una solicitud web, Laravel simplemente leerá este archivo y se pondrá en marcha..

Dicho esto, el almacenamiento en caché de la configuración es una operación extremadamente delicada que puede explotar en su cara. El mayor inconveniente es que una vez que ha emitido este comando, la función env () llama desde todas partes, excepto los archivos de configuración, devolverán nulo!

Tiene sentido cuando lo piensas. Si usa el almacenamiento en caché de la configuración, le está diciendo al marco: “Sabes qué, creo que he configurado las cosas muy bien y estoy 100% seguro de que no quiero que cambien”. En otras palabras, espera que el entorno permanezca estático, que es para lo que sirven los archivos .env.

Dicho esto, aquí hay algunas reglas de almacenamiento en caché de configuración irrompibles, sagradas e irrompibles:

  1. Hazlo solo en un sistema de producción.
  2. Hágalo solo si está realmente seguro de que desea congelar la configuración.
  3. En caso de que algo salga mal, deshaga la configuración con php artisan cache: clear
  4. Ore para que el daño hecho al negocio no sea significativo!

Reduce los servicios autocargados

Para ser útil, Laravel carga una tonelada de servicios cuando se despierta. Estos están disponibles en el archivo config / app.php como parte de la clave de matriz ‘proveedores’. Echemos un vistazo a lo que tengo en mi caso:

/ *
El |————————————————————————–
El | Proveedores de servicios autocargados
El |————————————————————————–
El |
El | Los proveedores de servicios enumerados aquí se cargarán automáticamente en el
El | solicitud a su solicitud. Siéntase libre de agregar sus propios servicios a
El | esta matriz para otorgar una funcionalidad ampliada a sus aplicaciones.
El |
* /

‘proveedores’ => [

/ *
* Proveedores de servicios Laravel Framework…
* /
Iluminar \ Auth \ AuthServiceProvider :: clase,
Illuminate \ Broadcasting \ BroadcastServiceProvider :: clase,
Illuminate \ Bus \ BusServiceProvider :: clase,
Illuminate \ Cache \ CacheServiceProvider :: clase,
Illuminate \ Foundation \ Providers \ ConsoleSupportServiceProvider :: clase,
Illuminate \ Cookie \ CookieServiceProvider :: clase,
Illuminate \ Database \ DatabaseServiceProvider :: clase,
Illuminate \ Encryption \ EncryptionServiceProvider :: clase,
Illuminate \ Filesystem \ FilesystemServiceProvider :: clase,
Illuminate \ Foundation \ Providers \ FoundationServiceProvider :: clase,
Illuminate \ Hashing \ HashServiceProvider :: clase,
Illuminate \ Mail \ MailServiceProvider :: clase,
Iluminar \ Notificaciones \ NotificationServiceProvider :: clase,
Iluminar \ Pagination \ PaginationServiceProvider :: clase,
Iluminar \ Pipeline \ PipelineServiceProvider :: clase,
Illuminate \ Queue \ QueueServiceProvider :: class,
Illuminate \ Redis \ RedisServiceProvider :: clase,
Illuminate \ Auth \ Passwords \ PasswordResetServiceProvider :: class,
Illuminate \ Session \ SessionServiceProvider :: clase,
Illuminate \ Translation \ TranslationServiceProvider :: clase,
Illuminate \ Validation \ ValidationServiceProvider :: clase,
Illuminate \ View \ ViewServiceProvider :: clase,

/ *
* Proveedores de servicios de paquetes…
* /

/ *
* Proveedores de servicios de aplicaciones…
* /
App \ Providers \ AppServiceProvider :: class,
App \ Providers \ AuthServiceProvider :: class,
// App \ Providers \ BroadcastServiceProvider :: class,
App \ Providers \ EventServiceProvider :: class,
App \ Providers \ RouteServiceProvider :: class,

],

Una vez más, conté, ¡y hay 27 servicios listados! Ahora, puede que los necesite a todos, pero es poco probable.

Por ejemplo, estoy construyendo una API REST en este momento, lo que significa que no necesito el proveedor de servicios de sesión, el proveedor de servicios de visualización, etc. Y dado que estoy haciendo algunas cosas a mi manera y no sigo los valores predeterminados del marco , También puedo desactivar el proveedor de servicios de autenticación, el proveedor de servicios de paginación, el proveedor de servicios de traducción, etc. Con todo, casi la mitad de estos son innecesarios para mi caso de uso.

Examine detenidamente su solicitud. ¿Necesita todos estos proveedores de servicios? Pero, por el amor de Dios, ¡no comente a ciegas estos servicios y avance a la producción! Ejecute todas las pruebas, verifique las cosas manualmente en máquinas de desarrollo y preparación, y sea muy paranoico antes de apretar el gatillo. ��

Sé sabio con las pilas de middleware

Cuando necesita un procesamiento personalizado de la solicitud web entrante, la respuesta es crear un nuevo middleware. Ahora, es tentador abrir app / Http / Kernel.php y pegar el middleware en la web o api stack; de esa manera, estará disponible en toda la aplicación y si no está haciendo algo intrusivo (como iniciar sesión o notificar, por ejemplo).

Sin embargo, a medida que la aplicación crece, esta colección de middleware global puede convertirse en una carga silenciosa para la aplicación si todos (o la mayoría) de estos están presentes en cada solicitud, incluso si no hay una razón comercial para eso.

En otras palabras, tenga cuidado de dónde agrega / aplica un nuevo middleware. Puede ser más conveniente agregar algo globalmente, pero la penalización de rendimiento es muy alta a largo plazo. Sé el dolor que tendrías que sufrir si aplicaras selectivamente el middleware cada vez que hay un nuevo cambio, pero es un dolor que aceptaría y recomendaría de buena gana.!

Evite el ORM (a veces)

Si bien Eloquent hace que muchos aspectos de la interacción de DB sean agradables, tiene un costo de velocidad. Al ser un mapeador, el ORM no solo tiene que buscar registros de la base de datos, sino también instanciar los objetos del modelo e hidratarlos (completarlos) con datos de columna.

Entonces, si hace un simple $ users = User :: all () y hay, por ejemplo, 10,000 usuarios, el marco obtendrá 10,000 filas de la base de datos e internamente 10,000 nuevos User () y completará sus propiedades con los datos relevantes . Esto es una gran cantidad de trabajo que se está realizando detrás de escena, y si la base de datos es donde su aplicación se está convirtiendo en un cuello de botella, a veces pasar por alto el ORM es una buena idea.

Esto es especialmente cierto para consultas SQL complejas, en las que tendría que saltar muchos aros y escribir cierres sobre cierres y aún así terminar con una consulta eficiente. En tales casos, se prefiere hacer un DB :: raw () y escribir la consulta a mano..

Pasando por esta estudio de rendimiento, incluso para inserciones simples Eloquent es mucho más lento a medida que aumenta el número de registros:

Use el almacenamiento en caché tanto como sea posible

Uno de los secretos mejor guardados de la optimización de aplicaciones web es el almacenamiento en caché.

Para los no iniciados, el almacenamiento en caché significa precalcular y almacenar resultados costosos (costosos en términos de uso de CPU y memoria), y simplemente devolverlos cuando se repite la misma consulta.

Por ejemplo, en una tienda de comercio electrónico, puede encontrar el de los 2 millones de productos, la mayoría de las veces las personas están interesadas en los que están recién almacenados, dentro de un cierto rango de precios y para un grupo de edad en particular. Consultar la base de datos para obtener esta información es un desperdicio, ya que la consulta no cambia con frecuencia, es mejor almacenar estos resultados en algún lugar al que podamos acceder rápidamente.

Laravel tiene soporte incorporado para varios tipos de almacenamiento en caché. Además de utilizar un controlador de almacenamiento en caché y construir el sistema de almacenamiento en caché desde cero, es posible que desee utilizar algunos paquetes de Laravel que facilitan almacenamiento en caché de modelos, caché de consultas, etc..

Pero tenga en cuenta que más allá de cierto caso de uso simplificado, los paquetes de almacenamiento en caché preconstruidos pueden causar más problemas de los que resuelven.

Prefiere el almacenamiento en caché en memoria

Cuando almacena en caché algo en Laravel, tiene varias opciones de dónde almacenar el cálculo resultante que debe almacenarse en caché. Estas opciones también se conocen como controladores de caché. Entonces, si bien es posible y perfectamente razonable usar el sistema de archivos para almacenar los resultados de la memoria caché, no es realmente lo que el almacenamiento en caché debe ser.

Idealmente, desea utilizar un caché en memoria (que vive en la RAM por completo) como Redis, Memcached, MongoDB, etc., de modo que, bajo cargas más altas, el almacenamiento en caché sirve para un uso vital en lugar de convertirse en un cuello de botella en sí mismo.

Ahora, puede pensar que tener un disco SSD es casi lo mismo que usar una memoria RAM, pero ni siquiera está cerca. Incluso informal puntos de referencia Demuestre que la RAM supera al SSD entre 10 y 20 veces en lo que respecta a la velocidad.

Mi sistema favorito cuando se trata de almacenamiento en caché es Redis. Sus ridículamente rápido (100.000 operaciones de lectura por segundo son comunes), y para sistemas de caché muy grandes, se pueden convertir en un racimo fácilmente.

Caché de las rutas

Al igual que la configuración de la aplicación, las rutas no cambian mucho con el tiempo y son un candidato ideal para el almacenamiento en caché. Esto es especialmente cierto si no puede soportar archivos grandes como yo y termina dividiendo su web.php y api.php en varios archivos. Un solo comando de Laravel empaqueta todas las rutas disponibles y las mantiene a mano para acceso futuro:

ruta artesanal php: caché

Y cuando termines agregando o cambiando rutas, simplemente haz lo siguiente:

ruta artesanal php: claro

Optimización de imagen y CDN

Las imágenes son el corazón y el alma de la mayoría de las aplicaciones web. Casualmente, también son los mayores consumidores de ancho de banda y una de las principales razones para aplicaciones / sitios web lentos. Si simplemente almacena las imágenes cargadas ingenuamente en el servidor y las devuelve en respuestas HTTP, está dejando pasar una oportunidad de optimización masiva.

Mi primera recomendación es no almacenar imágenes localmente: hay que tratar el problema de la pérdida de datos y, según la región geográfica en la que se encuentre su cliente, la transferencia de datos puede ser muy lenta.

En cambio, busque una solución como Cloudinary que cambia automáticamente el tamaño y optimiza las imágenes sobre la marcha.

Si eso no es posible, use algo como Cloudflare para almacenar en caché y servir imágenes mientras están almacenadas en su servidor.

Y si incluso eso no es posible, ajustar un poco el software de su servidor web para comprimir los activos y dirigir el navegador del visitante a la memoria caché, hace una gran diferencia. Así es como se vería un fragmento de la configuración de Nginx:

servidor {

# archivo truncado

# configuración de compresión gzip
gzip on;
gzip_comp_level 5;
gzip_min_length 256;
gzip_proxied any;
gzip_vary on;

# control de caché del navegador
ubicación ~ * \. (ico | css | js | gif | jpeg | jpg | png | woff | ttf | otf | svg | woff2 | eot) $ {
expira 1d;
acceso_log desactivado;
add_header Pragma public;
add_header Cache-Control "público, max-age = 86400";
}
}

Soy consciente de que la optimización de imágenes no tiene nada que ver con Laravel, pero es un truco tan simple y poderoso (y a menudo se descuida) que no podría ayudarme a mí mismo.

Optimización de autocargador

La carga automática es una característica ordenada y no tan antigua en PHP que podría salvar el lenguaje de la fatalidad. Dicho esto, el proceso de encontrar y cargar la clase relevante descifrando una cadena de espacio de nombres determinada lleva tiempo y puede evitarse en implementaciones de producción donde es deseable un alto rendimiento. Una vez más, Laravel tiene una solución de comando único para esto:

instalación del compositor –optimize-autoloader –no-dev

Haz amigos con las colas

Colas es cómo se procesan las cosas cuando hay muchas, y cada una de ellas tarda unos milisegundos en completarse. Un buen ejemplo es el envío de correos electrónicos: un caso de uso generalizado en aplicaciones web es enviar algunos correos electrónicos de notificación cuando un usuario realiza algunas acciones.

Por ejemplo, en un producto recién lanzado, es posible que desee que se notifique al liderazgo de la empresa (unas 6-7 direcciones de correo electrónico) cada vez que alguien realiza un pedido por encima de un cierto valor. Suponiendo que su puerta de enlace de correo electrónico puede responder a su solicitud SMTP en 500 ms, estamos hablando de una buena espera de 3-4 segundos para el usuario antes de que la confirmación del pedido entre en vigencia. Una muy mala pieza de UX, estoy seguro de que de acuerdo.

El remedio es almacenar los trabajos a medida que entran, decirle al usuario que todo salió bien y procesarlos (unos segundos) más tarde. Si hay un error, los trabajos en cola se pueden volver a intentar varias veces antes de que se declare que fallaron.

Créditos: Microsoft.com

Si bien un sistema de colas complica un poco la configuración (y agrega algunos gastos generales de monitoreo), es indispensable en una aplicación web moderna.

Optimización de activos (Laravel Mix)

Para cualquier activo de front-end en su aplicación Laravel, asegúrese de que haya una canalización que compile y minimice todos los archivos de activos. Aquellos que se sienten cómodos con un sistema de paquetes como Webpack, Gulp, Parcel, etc., no necesitan molestarse, pero si aún no lo están haciendo., Mezcla Laravel es una recomendación sólida.

Mix es un envoltorio liviano (¡y delicioso, con toda honestidad!) Alrededor de Webpack que se encarga de todos sus archivos CSS, SASS, JS, etc., para la producción. Un archivo .mix.js típico puede ser tan pequeño como este y aún así hacer maravillas:

const mix = require (‘laravel-mix’);

mix.js (‘recursos / js / app.js’, ‘public / js’)
.sass (‘recursos / sass / app.scss’, ‘public / css’);

Esto se encarga automáticamente de las importaciones, la minificación, la optimización y todo el shebang cuando esté listo para la producción y ejecute npm run production. Mix se ocupa no solo de los archivos JS y CSS tradicionales, sino también de los componentes Vue y React que pueda tener en el flujo de trabajo de su aplicación.

Más información aquí!

Conclusión

La optimización del rendimiento es más arte que ciencia: saber cómo y cuánto hacer es importante más que qué hacer. Dicho esto, no hay fin a cuánto y qué puede optimizar todo en una aplicación Laravel.

Pero haga lo que haga, me gustaría dejarle algunos consejos de despedida: la optimización debe hacerse cuando hay una razón sólida, y no porque suene bien o porque esté paranoico sobre el rendimiento de la aplicación para más de 100,000 usuarios mientras que en realidad solo hay 10.

Si no está seguro de si necesita optimizar su aplicación o no, no necesita patear el avispero proverbial. Una aplicación funcional que se siente aburrida pero que hace exactamente lo que tiene que hacer es diez veces más deseable que una aplicación que ha sido optimizada para convertirse en una supermáquina híbrida mutante pero que se cae de vez en cuando.

Y, para que newbiew se convierta en un maestro de Laravel, mira esto curso por Internet.

¡Que tus aplicaciones se ejecuten mucho, mucho más rápido! ��

Jeffrey Wilson Administrator
Sorry! The Author has not filled his profile.
follow me
    Like this post? Please share to your friends:
    Adblock
    detector
    map