Haga que su aplicación sea escalable optimizando el rendimiento de ORM

La traducción del artículo se preparó antes del inicio del curso "Desarrollador backend en PHP" .










¡Hola! Soy Valerio, un desarrollador italiano y CTO de la plataforma Inspector.dev.



En este artículo, compartiré un conjunto de estrategias de optimización de ORM que utilizo al desarrollar servicios de backend.



Estoy seguro de que todos tuvimos que quejarnos de que el servidor o la aplicación funciona con lentitud (o incluso no funciona en absoluto) y pasamos el tiempo en la máquina de café esperando los resultados de una solicitud larga.



¿Como arreglarlo?

¡Vamos a averiguar!



La base de datos es un recurso compartido



¿Por qué la base de datos causa tantos problemas de rendimiento?

A menudo olvidamos que ninguna consulta es independiente de otras.

Pensamos que aunque alguna consulta sea lenta, difícilmente afecta a otras ... ¿Pero es realmente así?



Una base de datos es un recurso compartido que utilizan todos los procesos que se ejecutan en su aplicación. Incluso un método de acceso a la base de datos mal diseñado puede interrumpir el rendimiento de todo el sistema.



Por lo tanto, no te olvides de las posibles consecuencias, pensando: "¡Está bien que este fragmento de código no esté optimizado!" Un acceso lento a la base de datos puede provocar su sobrecarga y esto, a su vez, puede afectar negativamente la experiencia del usuario.



Problema de consulta de base de datos N + 1



¿Cuál es el problema N + 1?



Este es un problema común cuando se usa un ORM para interactuar con una base de datos. No se trata de escribir código SQL.



Cuando se utiliza un sistema ORM como Eloquent, no siempre es obvio qué consultas se ejecutarán y cuándo. En el contexto de este problema en particular, hablemos de relaciones y carga ansiosa.



Cualquier sistema ORM le permite declarar relaciones entre entidades y proporciona una gran API para navegar por la estructura de su base de datos.

A continuación se muestra un buen ejemplo de las entidades "Artículo" y "Autor".



/*
 * Each Article belongs to an Author
 */
$article = Article::find("1");
echo $article->author->name; 
/*
 * Each Author has many Articles
 */
foreach (Article::all() as $article)
{
    echo $article->title;
}


Sin embargo, cuando use relaciones dentro de un bucle, debe escribir su código con cuidado.



Eche un vistazo al siguiente ejemplo.



Queremos agregar el nombre del autor junto al título del artículo. Con el ORM, puede obtener el nombre del autor utilizando una relación uno a uno entre el artículo y el autor.



Todo parece sencillo:



// Initial query to grab all articles
$articles = Article::all();
foreach ($articles as $article)
{
    // Get the author to print the name.
    echo $article->title . ' by ' . $article->author->name;
}


¡Pero luego caímos en una trampa!



Este ciclo genera una solicitud inicial para obtener todos los artículos:



SELECT * FROM articles;


y N consultas más para obtener el autor de cada artículo y mostrar el valor del campo "nombre", incluso si el autor es siempre el mismo.



SELECT * FROM author WHERE id = [articles.author_id]


Recibimos exactamente N + 1 solicitudes.



Puede que esto no parezca gran cosa. Bueno, hagamos quince o veinte solicitudes adicionales, no es gran cosa. Sin embargo, volvamos a la primera parte de este artículo:



  • — , .
  • , , .
  • , .


:



De acuerdo con los documentos de Laravel, existe una buena posibilidad de que se encuentre con el problema de la consulta N + 1, porque cuando accede a las relaciones Eloquent como propiedades ( $article->author), los datos de la relación se cargan de forma diferida.



Esto significa que los datos de la relación no se cargan hasta que accede por primera vez a la propiedad.



Sin embargo, usando un método simple, podemos cargar todos los datos de la relación a la vez. Luego, al acceder a la relación Eloquent como propiedad, el sistema ORM no ejecutará una nueva consulta porque los datos ya han sido cargados.



Esta táctica se llama "carga ansiosa" y es compatible con todos los ORM.



// Eager load authors using "with".
$articles = Article::with('author')->get();
foreach ($articles as $article)
{
    // Author will not run a query on each iteration.
    echo $article->author->name;
}


Eloquent proporciona un método with()para cargar con entusiasmo las relaciones.



En este caso, solo se ejecutarán dos consultas.

El primero es necesario para descargar todos los artículos:



SELECT * FROM articles;


El segundo será ejecutado por el método with()y buscará todos los autores:



SELECT * FROM authors WHERE id IN (1, 2, 3, 4, ...);


El motor interno de Eloquent mapeará los datos y se puede acceder a ellos de la forma habitual:



$article->author->name;


Optimice sus operadores select



Durante mucho tiempo, pensé que declarar explícitamente la cantidad de campos en una consulta de recuperación no conducía a una mejora significativa del rendimiento, por lo que, para simplificar, obtuve todos los campos en mis consultas.



Además, especificar rígidamente la lista de campos en una declaración de selección específica complica aún más el soporte para dicho código.



El mayor escollo de este argumento es que, desde la perspectiva de una base de datos, esto puede ser cierto.



Sin embargo, trabajamos con un ORM, por lo que los datos seleccionados de la base de datos se cargarán en la memoria en el lado de PHP para que el sistema ORM los administre más. Cuantos más campos capturemos, más memoria ocupará el proceso.



Laravel Eloquent proporciona un método de selección para restringir la consulta solo a las columnas que necesitamos:



$articles = Article::query()
    ->select('id', 'title', 'content') // The fields you need
    ->latest()
    ->get();


Al excluir campos, el intérprete de PHP no tiene que procesar datos innecesarios, por lo que puede reducir significativamente el consumo de memoria.



Evitar la búsqueda completa también puede mejorar el rendimiento para ordenar, agrupar y fusionar, ya que la base de datos en sí puede ahorrar memoria como resultado.



Usar vistas en MySQL



Las vistas son consultas SELECT basadas en otras tablas y almacenadas en la base de datos.



Cuando SELECCIONAMOS una o más tablas, la base de datos primero compila nuestra declaración SQL, se asegura de que esté libre de errores y luego recupera los datos.



Una vista es una declaración SELECT precompilada que, cuando se procesa, MySQL ejecuta inmediatamente la consulta interna subyacente de la vista.



Además, MySQL suele ser más inteligente que PHP cuando se trata de filtrar datos. Hay ganancias de rendimiento significativas cuando se utilizan vistas sobre el uso de funciones PHP para procesar colecciones o matrices.



Si desea obtener más información sobre las capacidades de MySQL para desarrollar aplicaciones con uso intensivo de bases de datos, visite este excelente sitio: www.mysqltutorial.org



Vincular un modelo elocuente a una vista



Las vistas también se denominan "tablas virtuales". Desde el punto de vista del ORM, parecen tablas normales.



Por lo tanto, puede crear un modelo Eloquent para consultar los datos que están en la vista.



class ArticleStats extends Model
{
    /**
     * The name of the view is the table name.
     */
    protected $table = "article_stats_view";
    /**
     * If the resultset of the View include the "author_id"
     * we can use it to retrieve the author as normal relation.
     */
    public function author()
    {
        return $this->belongsTo(Author::class);
    }
}


Las relaciones funcionan como de costumbre, al igual que las coacciones, la paginación, etc. Y no hay penalización de rendimiento.



Conclusión



Espero que estos consejos le ayuden a desarrollar un software más fiable y escalable.



Todos los ejemplos de código están escritos usando Eloquent como ORM, pero tenga en cuenta que estas estrategias funcionan igual para todos los ORM principales.



Como digo a menudo, necesitamos herramientas para implementar estrategias efectivas. Y si no hay una estrategia, entonces no hay nada de qué hablar.



Muchas gracias por leer el artículo hasta el final. Si desea obtener más información sobre Inspector, lo invito a nuestro sitio web www.inspector.dev . ¡No dudes en escribir al chat si tienes alguna pregunta!



Publicado anteriormente aquí: www.inspector.dev/make-your-application-scalable-optimizing-the-orm-performance





Lee mas:






All Articles