Consejos para acelerar la consulta de la base de datos en Laravel
El rendimiento de la aplicación no depende de la aplicación en sí. La aplicación y la base de datos deben sintonizarse juntas. Por ejemplo, imagina que 100 clientes entran en un restaurante muy grande y elegante (aplicación) y solo hay 2 camareros disponibles (base de datos). ¿Cómo pueden 2 camareros atender a todo el cliente sin tener al cliente en la cola durante un período prolongado? Al final, el cliente abandonó el restaurante y nunca volvió a visitarlo.
¿Alguna vez se ha preguntado por qué su aplicación es lenta aunque ya haya arreglado el código de su aplicación?
Aquí están estos consejos para acelerar la consulta de la base de datos según mi experiencia.
Indexación de tablas
Los índices son la parte más importante de la base de datos. La indexación permite la recuperación de datos de manera más rápida y eficiente. Los índices se utilizan para recuperar datos rápidamente sin tener que buscar en cada fila de una tabla cada vez que se accede a una tabla de base de datos. La base de datos pequeña no se notará, pero una vez que crece, la consulta consume mucho tiempo.
¿Cómo utilizar? Todo lo que necesita es identificar de qué columna desea aprovechar. Por ejemplo, tiene 1,3 millones de filas de usuarios y desea recibir un correo electrónico.
select * from users where email = "[email protected]"
Sin índice, el proceso será de arriba a abajo de los datos del usuario hasta que se encuentre el email
. Aquí hay una comparación entre con índice y sin él.
Sin índice – 750ms
Con índice – 2 ms (400 veces más rápido)
En Laravel, la forma más fácil es crear un archivo de migración para alterar su tabla de deseos
public function up() { Schema::table('users', function(Blueprint $table) { $table->index('email'); }); }
O si actualmente está en un proyecto nuevo, simplemente agregue el índice a la columna que desea
public function up() { Schema::create('users', function(Blueprint $table) { ... $table->string('email')->index(); ... }); }
Tenga en cuenta que,
- El índice no es solo para una sola columna. En algún momento, es necesario buscar en varias columnas a la vez, algo como este
$table->index([‘email’, ‘status’]);
. - La velocidad de la consulta depende de la CPU / memoria de su base de datos.
Carga ávida de ORM elocuente
Eloquent Object Relation Mapper (ORM) facilita la gestión y el trabajo con estas relaciones en lugar de unir manualmente cada tabla. Para usar Eloquent, hay algunos que debe considerar para usarlo con cuidado, que es Eager loading. Una implementación incorrecta causa una consulta N + 1. Aquí es donde los desarrolladores comunes hacen lo que causa N + 1.
Por ejemplo:
class User extends Model { ... ... public function devices() { return $this->hasMany(Device::class); } }
Permite buscar a los usuarios alrededor de 100 filas de la tabla y lo que sucede.
Error 1
$users = User::all();foreach($users as $user) { $total_devices_by_user = $user->devices->count(); }
La consulta se ejecuta una vez para obtener todos los usuarios y para cada bucle consulta otra consulta de dispositivos 100 veces. 100 + 1 = N + 1 problema. Imagínese si desea obtener miles de datos. Si una consulta cuesta 100 ms * 100 = 10,000 ms, se tarda en completar el proceso.
Error 2
Manipule la tabla de relaciones usando el atributo adjuntar.
class User extends Model { ... ... protected $appends = ['device_count']; public function devices() { return $this->hasMany(Device::class); } public function getDeviceCountAttribute() { return $this->device->count(); } }
Y busque a los usuarios
$users = User::all();
La consulta se ejecuta una vez para obtener a todos los usuarios, pero en el momento en que Laravel quiere transformarla en una colección, debido al atributo adjunto, la colección consulta otras 100 consultas. Es lo mismo que el Error 1 porque produce un problema N + 1.
Solución
Se puede resolver utilizando el método with
en la carga Eager de una sola vez. Por ejemplo:
User::with('devices')->get(); User::with(['devices', 'posts'])->get(); User::with(['devices', 'posts.comments'])->get();
Con esto, en lugar de presionar la base de datos N + 1, with
realizará una consulta única uniendo esas tablas y transformándolas en una colección. Para que pueda acceder libremente al objeto de la colección sin acceder a la base de datos. Para obtener más información sobre la carga ansiosa, puede consultar los documentos de Laravel
Consultor de construcción
Usar elocuente es genial porque tiene muchas características interesantes, pero cuando se trata de velocidad, es un poco más lento que el generador de consultas debido a ORM. Aquí está la comparación de ejemplo entre elocuente y el generador de consultas frente a 1,3 millones de filas
Elocuente
\App\Models\User::query()->get(); // 12.6 secondsUser::where('email', '[email protected]')->first(); // 1.3 seconds (Without index)User::where('email', '[email protected]')->first(); // 17 miliseconds (With index)
Consulta
\DB::table('users')->get(); // 7.4 seconds\DB::table('users')->where('email', '[email protected]')->first(); // 1.2 seconds (Without index)\DB::table('users')->where('email', '[email protected]')->first(); // 0.9 milisecond (With index)
Lo más probable es que las consultas sean 2 o 3 veces más rápidas que elocuentes. No significa que no pueda usar Eloquent. Se lo recomendé a Eloquent, pero en ciertos casos, para un enfoque simple como la recuperación de datos sin ninguna complejidad, es posible que deba usar el generador de consultas.
Consulta de paginación
Pagine tantos como pueda para la lista de datos (limite el resultado por consulta) porque es más rápido en lugar de recuperar todos los datos a la vez. Si tiene 100 filas, estaría bien. Pero, ¿qué pasa si quieres millones por filas? La mejor práctica para la lista de datos es utilizar la paginación para obtener la mejor interfaz de usuario / experiencia de usuario.
En Laravel, hay 2 paginación: simplePaginate
y paginate
. Probémoslo con 1,3 millones de filas.
User::query()->paginate(); // takes 950ms User::query()->simplePaginate(); // takes 14ms
¿Por qué paginar mucho más lento (70% más lento)? porque contiene una consulta adicional para contar todas las filas para el total de filas y otros campos adicionales. Como dije, los datos pequeños no tendrán efecto. Si tiene un gran conjunto de datos, podría considerar simplePaginate
Consulta siempre lo que realmente necesitas
Aquí también es importante consultar solo lo que necesita. Por ejemplo, probemos en una consulta seleccionada
User::query()->get(); // 13 secondsUser::query()->select('email')->get(); // 5.5 seconds\DB::table('users')->get(); // 7 seconds\DB::table('users')->select('email')->get(); // 1.2 seconds
Seleccione claramente qué columna necesita es mucho más rápido. Hay varias razones para ello. Puedes consultar aquí
Lo mismo ocurre con la carga ansiosa, no se una a la mesa si no la necesita. Es muy probable que algunos desarrolladores usen $with
variable en Model. Mi consejo, simplemente no lo haga. Debe elegir manualmente qué tabla desea cargar ansiosamente en lugar de vincularla automáticamente.
Si tiene una condición lógica para verificar la existencia de datos en otra tabla, no se una a ella, use whereHas
en su lugar
Almacenamiento en caché de consultas
El almacenamiento en caché de los resultados de las consultas a las que se accede con frecuencia aumentaría el rendimiento de una aplicación. La caché básicamente almacena las consultas realizadas por primera vez y úsala para la siguiente solicitud durante un período de tiempo o almacenada para siempre.
Dejemos probar esta consulta
User::query()->where('email', '[email protected]')->get();
Sin caché: 985 ms (con índice de 17 ms)
Con caché: 0,4 ms
La caché es más rápida que cualquier otra cosa. Evite utilizar archivos o bases de datos como caché. El archivo producirá una gran cantidad de E / S y la base de datos generará una alta conexión, lenta si hay mucho tráfico. Se recomienda utilizar Redis o Memcached porque se almacena en la memoria.
Aquí hay un paquete que puede usar para facilitar su implementación
Optimización de la base de datos
El propósito del ajuste es donde necesitamos encontrar los mejores recursos para la base de datos. Es necesario ajustar la mejor base de datos. No siempre viene por defecto. Debe verificar estos 4 recursos: CPU, RAM, Almacenamiento y Red. La falta de esto resultará en un rendimiento deficiente en su aplicación. No significa que si tiene una alta especificación de CPU y RAM, pueda admitir un tráfico elevado con una red lenta.
Mi consejo es siempre intentar y error. Realice las pruebas de carga en su base de datos para que conozca las capacidades de su base de datos. Ajuste todas las configuraciones posibles que se pueden configurar y vea qué sucede.
Utilice siempre la última versión
Es importante utilizar siempre la última versión del marco o la base de datos. La mayoría de las actualizaciones también le proporcionarán una mejora del rendimiento.
¿Por qué importante?
- Darle actualizaciones de seguridad
- Mantenga su aplicación funcionando sin problemas
- El sistema heredado en algún momento le dará problemas en el futuro
- Aumentar el rendimiento
Por ejemplo en rendimiento,
El benchmark entre MySQL 5 y 8 que MySQL 8 da una gran diferencia en el tiempo que lleva procesar las transacciones y mejora en la alta carga de trabajo.
Para Laravel, también hay un rendimiento diferente entre L7 y L8, que según mi prueba es 2 veces más rápido que L7 y 4 veces más rápido que L6.
Por lo tanto, la base de datos y el marco de programación deben actualizarse (a menos que no esté utilizando el marco). Realice evaluaciones comparativas en varias versiones y vea cómo va.
Base de datos separada del servidor de aplicaciones
Tradicionalmente y más comúnmente, el entorno completo se coloca en una sola máquina, donde la aplicación, el servidor web, la base de datos, etc., todos juntos utilizan los mismos recursos. La pila de uso común es LEMP (Linux, Nginx, MySQL, PHP).
¿Pros?
- Rápido y fácil para una configuración simple con poca escalabilidad.
¿Contras?
- Recursos compartidos: conducen a un rendimiento deficiente
- Dificultades para determinar la fuente de un mal desempeño
- Si la máquina se apaga, todo desaparece
¿Qué relacionado con este tema? El recurso compartido causa que la base de datos no pueda asignar el proceso correctamente. Si la aplicación usa el 70% de la CPU, la base de datos solo tiene un 30% o menos solo para procesar consultas que conducen a problemas de consulta largos y tiempos de espera.
Por lo tanto, sería mejor separar la base de datos del servidor de aplicaciones. ¿Por qué? Al dividir la base de datos del servidor de aplicaciones, podemos asignar correctamente el servicio solo para la base de datos y también aumentar la seguridad al colocarlo en una red privada.
Beneficios
- Número de conexiones totalmente optimizado sin que otros procesos lo sobrecarguen.
- Propósito de seguridad al aislarlo de la red pública.
- El diagnóstico de problemas y la supervisión del rendimiento son más fáciles.
- El mantenimiento y la actualización son mucho más fáciles cuando solo se enfoca en la base de datos sin preocuparse por otros componentes
Recomendaría utilizar el proveedor de la nube (AWS, Alibaba, DigitalOcean) cuando se trata de dividir el entorno porque el proveedor de la nube tiene una responsabilidad compartida en la que solo necesita preocuparse por su aplicación y sus datos. Mantener el hardware y el software es responsabilidad del proveedor de la nube.
Utilice una base de datos de solo lectura para recuperar datos
Si tiene suficiente costo, dividir la base de datos en una base de datos de solo lectura sería bueno. Proporciona flexibilidad, tolerancia a errores, velocidad y no se produjo ningún bloqueo. Además, la función de solo lectura proporciona una seguridad mejorada para las bases de datos al protegerlas de actualizaciones accidentales o maliciosas.
Entonces tendrás 2 cadenas de conexión. 1 – Base de datos de lectura y escritura y 2 – Base de datos de solo lectura. Recuperación de datos de acceso frecuente, es posible que deba utilizar una base de datos de solo lectura. Para una menor recuperación de datos y una actualización frecuente, utilice la base de datos de lectura y escritura.
Eso es todo por ahora. ¡Gracias por tu tiempo!