{"id":75530,"date":"2020-03-26T19:42:23","date_gmt":"2020-03-26T17:42:23","guid":{"rendered":"https:\/\/prohoster.info\/blog\/administrirovanie\/vyvod-rezultatov-poiska-i-problemy-s-proizvoditelnostyu"},"modified":"2020-03-26T19:42:23","modified_gmt":"2020-03-26T17:42:23","slug":"vyvod-rezultatov-poiska-i-problemy-s-proizvoditelnostyu","status":"publish","type":"post","link":"https:\/\/prohoster.info\/es\/blog\/administrirovanie\/vyvod-rezultatov-poiska-i-problemy-s-proizvoditelnostyu","title":{"rendered":"Resultados de b\u00fasqueda y problemas de rendimiento","gt_translate_keys":[{"key":"rendered","format":"text"}]},"content":{"rendered":"<p>Uno de los casos de uso m\u00e1s comunes en todas nuestras aplicaciones habituales es la b\u00fasqueda de datos seg\u00fan ciertos criterios y su presentaci\u00f3n en un formato legible. Aqu\u00ed tambi\u00e9n pueden haber opciones adicionales para ordenamiento, agrupamiento y paginaci\u00f3n. La tarea, en teor\u00eda, es trivial, pero en su resoluci\u00f3n, muchos desarrolladores cometen una serie de errores que luego afectan el rendimiento. Intentaremos examinar varias alternativas para resolver esta tarea y formular recomendaciones sobre la elecci\u00f3n de la implementaci\u00f3n m\u00e1s eficiente.<\/p>\n<p><img decoding=\"async\" alt=\"Resultados de b\u00fasqueda y problemas de rendimiento\" src=\"\/wp-content\/uploads\/2020\/03\/364ab29c4a6117ff441b933a38ec8401.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<noindex><a rel=\"nofollow\" name=\"habracut\"><\/a><\/noindex><\/p>\n<h2>Opci\u00f3n de paginaci\u00f3n #1<\/h2>\n<p>\nLa opci\u00f3n m\u00e1s sencilla que se nos ocurre es la paginaci\u00f3n de los resultados de b\u00fasqueda en su forma m\u00e1s cl\u00e1sica.<\/p>\n<p><img decoding=\"async\" alt=\"Resultados de b\u00fasqueda y problemas de rendimiento\" src=\"\/wp-content\/uploads\/2020\/03\/4957d9ad21a8a0c70390b224fe76cb33.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nSupongamos que en la aplicaci\u00f3n se utiliza una base de datos relacional. En este caso, para mostrar la informaci\u00f3n de esta manera, ser\u00e1 necesario realizar dos consultas SQL:<\/p>\n<ul>\n<li>Obtener las filas de la p\u00e1gina actual.<\/li>\n<li>Contar el n\u00famero total de filas que cumplen con los criterios de b\u00fasqueda; esto es necesario para mostrar las p\u00e1ginas.<\/li>\n<\/ul>\n<p>\nConsideremos la primera consulta utilizando una base de datos de prueba de MS SQL <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/Microsoft\/sql-server-samples\/releases\/download\/adventureworks\/AdventureWorks2016_EXT.bak\">AdventureWorks <\/a><\/noindex>para SQL Server 2016. Para este prop\u00f3sito, utilizaremos la tabla Sales.SalesOrderHeader:<\/p>\n<pre><code class=\"sql\">SELECT * FROM Sales.SalesOrderHeader\nORDER BY OrderDate DESC\nOFFSET 0 ROWS\nFETCH NEXT 50 ROWS ONLY\n<\/code><\/pre>\n<p>\nLa consulta anterior mostrar\u00e1 los primeros 50 pedidos de la lista, ordenados por fecha de adici\u00f3n en orden descendente, en otras palabras, los 50 \u00faltimos pedidos.<\/p>\n<p>Se ejecuta r\u00e1pidamente en la base de datos de prueba, pero veamos el plan de ejecuci\u00f3n y las estad\u00edsticas de entrada\/salida:<\/p>\n<p><img decoding=\"async\" alt=\"Resultados de b\u00fasqueda y problemas de rendimiento\" src=\"\/wp-content\/uploads\/2020\/03\/248e4b64593c7000216b46777183d639.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<pre><code class=\"plaintext\">Tabla 'SalesOrderHeader'. Conteo de escaneos 1, lecturas l\u00f3gicas 698, lecturas f\u00edsicas 0, lecturas anticipadas 0, lecturas lob l\u00f3gicas 0, lecturas lob f\u00edsicas 0, lecturas lob anticipadas 0.<\/code><\/pre>\n<p>\n<i>Se puede obtener estad\u00edsticas de entrada\/salida para cada consulta ejecutando en el entorno de ejecuci\u00f3n el comando SET STATISTICS IO ON.<\/i><\/p>\n<p>Como se puede ver en el plan de ejecuci\u00f3n, la operaci\u00f3n m\u00e1s intensiva en recursos es la clasificaci\u00f3n de todas las filas de la tabla original por fecha de adici\u00f3n. Y el problema es que cuanto m\u00e1s filas haya en la tabla, m\u00e1s 'pesada' ser\u00e1 la clasificaci\u00f3n. En la pr\u00e1ctica, hay que evitar tales situaciones, por lo que agregaremos un \u00edndice en la fecha de adici\u00f3n y veremos si ha cambiado el consumo de recursos:<\/p>\n<p><img decoding=\"async\" alt=\"Resultados de b\u00fasqueda y problemas de rendimiento\" src=\"\/wp-content\/uploads\/2020\/03\/a33e1eadf6f8f8b516b9bb834ad7ad86.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<pre><code class=\"plaintext\">Tabla 'SalesOrderHeader'. Conteo de escaneos 1, lecturas l\u00f3gicas 165, lecturas f\u00edsicas 0, lecturas anticipadas 5, lecturas lob l\u00f3gicas 0, lecturas lob f\u00edsicas 0, lecturas lob anticipadas 0.\n<\/code><\/pre>\n<p>\nEs evidente que ha mejorado mucho. Pero, \u00bfse han resuelto todos los problemas? Cambiemos la consulta para buscar pedidos donde el costo total de los productos supere los 100 d\u00f3lares:<\/p>\n<pre><code class=\"sql\">SELECT * FROM Sales.SalesOrderHeader\nWHERE SubTotal &gt; 100\nORDER BY OrderDate DESC\nOFFSET 0 ROWS\nFETCH NEXT 50 ROWS ONLY\n<\/code><\/pre>\n<p>\n<img decoding=\"async\" alt=\"Resultados de b\u00fasqueda y problemas de rendimiento\" src=\"\/wp-content\/uploads\/2020\/03\/e057bfc89e11a31c6d2855b93ec3c747.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<pre><code class=\"plaintext\">Tabla 'SalesOrderHeader'. Conteo de escaneos 1, lecturas l\u00f3gicas 1081, lecturas f\u00edsicas 0, lecturas anticipadas 0, lecturas l\u00f3gicas lob 0, lecturas f\u00edsicas lob 0, lecturas anticipadas lob 0.<\/code><\/pre>\n<p>\nNos encontramos en una situaci\u00f3n curiosa: el plan de consulta no es mucho peor que el anterior, pero el n\u00famero real de lecturas l\u00f3gicas es casi el doble que al realizar un escaneo completo de la tabla. Hay una salida: si convertimos el \u00edndice existente en uno compuesto y a\u00f1adimos como segundo campo el precio total de los productos, obtendremos nuevamente 165 lecturas l\u00f3gicas:<\/p>\n<pre><code class=\"sql\">CREATE INDEX IX_SalesOrderHeader_OrderDate_SubTotal on Sales.SalesOrderHeader(OrderDate, SubTotal);\n<\/code><\/pre>\n<p>\nEsta serie de ejemplos se puede continuar por mucho tiempo, pero hay dos ideas principales que quiero expresar aqu\u00ed:<\/p>\n<ul>\n<li>Agregar cualquier nuevo criterio o orden de clasificaci\u00f3n a la consulta puede afectar significativamente la velocidad de ejecuci\u00f3n.<\/li>\n<li>Pero si solo necesitamos restar una parte de los datos, y no todos los resultados que cumplen con las condiciones de b\u00fasqueda, hay muchas formas de optimizar esa consulta.<\/li>\n<\/ul>\n<p>\nAhora pasemos a la segunda consulta mencionada al principio, aquella que cuenta la cantidad de registros que cumplen con el criterio de b\u00fasqueda. Tomemos el mismo ejemplo: buscar pedidos que costen m\u00e1s de 100 d\u00f3lares:<\/p>\n<pre><code class=\"sql\">SELECT COUNT(1) FROM Sales.SalesOrderHeader\nWHERE SubTotal &gt; 100\n<\/code><\/pre>\n<p>\nCon el \u00edndice compuesto mencionado anteriormente, obtenemos:<\/p>\n<p><img decoding=\"async\" alt=\"Resultados de b\u00fasqueda y problemas de rendimiento\" src=\"\/wp-content\/uploads\/2020\/03\/7beab0c83a50e2d68a83a965df555ec9.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<pre><code class=\"plaintext\">Tabla 'SalesOrderHeader'. Conteo de escaneos 1, lecturas l\u00f3gicas 698, lecturas f\u00edsicas 0, lecturas anticipadas 0, lecturas lob l\u00f3gicas 0, lecturas lob f\u00edsicas 0, lecturas lob anticipadas 0.<\/code><\/pre>\n<p>\nQue la consulta recorra todo el \u00edndice entero no es sorprendente, ya que el campo SubTotal no est\u00e1 en la primera posici\u00f3n, por lo que la consulta no puede beneficiarse de ello. El problema se resuelve a\u00f1adiendo otro \u00edndice al campo SubTotal, lo que al final da solo 48 lecturas l\u00f3gicas.<\/p>\n<p>Se pueden dar m\u00e1s ejemplos de consultas para contar cantidades, pero la esencia seguir\u00e1 siendo la misma: <b>obtener un lote de datos y contar el total son dos consultas esencialmente diferentes<\/b>, y cada uno requiere sus propias medidas para la optimizaci\u00f3n. En general, no se puede encontrar una combinaci\u00f3n de \u00edndices que funcione igual de bien para ambas consultas.<\/p>\n<p>Por lo tanto, uno de los requisitos importantes que debe clarificarse al desarrollar una soluci\u00f3n de b\u00fasqueda de este tipo es si para el negocio es realmente relevante conocer el n\u00famero total de objetos encontrados. A menudo no lo es. Y la navegaci\u00f3n a trav\u00e9s de n\u00fameros de p\u00e1gina espec\u00edficos, en mi opini\u00f3n, es una soluci\u00f3n con un \u00e1mbito de aplicaci\u00f3n muy limitado, ya que la mayor\u00eda de los escenarios de paginaci\u00f3n se asemejan a \"ir a la siguiente p\u00e1gina\".<\/p>\n<h2>Opci\u00f3n de paginaci\u00f3n #2<\/h2>\n<p>\nSupongamos que a los usuarios no les importa conocer el n\u00famero total de objetos encontrados. Intentemos simplificar la p\u00e1gina de b\u00fasqueda:<\/p>\n<p><img decoding=\"async\" alt=\"Resultados de b\u00fasqueda y problemas de rendimiento\" src=\"\/wp-content\/uploads\/2020\/03\/66f5ee6dc1a52f14e55ae31e05c502b2.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nDe hecho, solo ha cambiado que no hay posibilidad de navegar a n\u00fameros de p\u00e1gina espec\u00edficos, y ahora esta tabla para mostrar no necesita conocer cu\u00e1ntas en total podr\u00eda haber. Pero surge la pregunta: \u00bfc\u00f3mo sabr\u00e1 la tabla si hay datos para la siguiente p\u00e1gina (para mostrar correctamente el enlace \"Siguiente\")?<\/p>\n<p>La respuesta es muy simple: se puede leer de la base de datos una entrada m\u00e1s de la que es necesaria para mostrar, y la existencia de esta \"entrada adicional\" indicar\u00e1 si hay un lote siguiente. As\u00ed, para obtener una p\u00e1gina de datos, solo se tendr\u00e1 que realizar una consulta, lo que mejora considerablemente el rendimiento y facilita el mantenimiento de esta funcionalidad. En mi experiencia, hubo un caso en el que renunciar al conteo total de registros aceler\u00f3 la entrega de resultados entre 4 y 5 veces.<\/p>\n<p>Para este enfoque, existen varias opciones de interfaz de usuario: comandos de \"anterior\" y \"siguiente\", como en el ejemplo anterior, un bot\u00f3n de \"cargar m\u00e1s\", que simplemente a\u00f1ade un nuevo lote a los resultados mostrados, y \"desplazamiento infinito\", que funciona de manera similar a \"cargar m\u00e1s\", pero la se\u00f1al para obtener el siguiente lote es que el usuario desplaza todos los resultados mostrados hasta el final. Cualquiera que sea la soluci\u00f3n visual, el principio de muestreo de datos permanece igual.<\/p>\n<h2>Matices de la implementaci\u00f3n de la paginaci\u00f3n<\/h2>\n<p>\nEn todos los ejemplos de solicitudes mencionados anteriormente, se utiliza el enfoque de \"desplazamiento + cantidad\", donde en la propia solicitud se indica desde qu\u00e9 fila del resultado y cu\u00e1ntas filas se deben devolver. Primero, veamos c\u00f3mo organizar mejor la transmisi\u00f3n de par\u00e1metros en este caso. En la pr\u00e1ctica, he encontrado varios m\u00e9todos:<\/p>\n<ul>\n<li>N\u00famero de orden de la p\u00e1gina solicitada (pageIndex), tama\u00f1o de la p\u00e1gina (pageSize).<\/li>\n<li>N\u00famero de orden del primer registro que se debe devolver (startIndex), n\u00famero m\u00e1ximo de registros en el resultado (count).<\/li>\n<li>N\u00famero de orden del primer registro que se debe devolver (startIndex), n\u00famero de orden del \u00faltimo registro que se debe devolver (endIndex).<\/li>\n<\/ul>\n<p>\nA primera vista, puede parecer tan elemental que no hay diferencia. Pero no es as\u00ed: la opci\u00f3n m\u00e1s conveniente y universal es la segunda (startIndex, count). Hay varias razones para esto:<\/p>\n<ul>\n<li>Para el enfoque de leer +1 registro, el primer formato con pageIndex y pageSize es extremadamente inc\u00f3modo. Por ejemplo, queremos mostrar 50 registros en una p\u00e1gina. Seg\u00fan el algoritmo anterior, se debe leer una entrada m\u00e1s de lo necesario. Si este \u2018+1\u2019 no est\u00e1 incorporado en el servidor, resulta que para la primera p\u00e1gina debemos solicitar registros del 1 al 51, para la segunda del 51 al 101, etc. Si se especifica un tama\u00f1o de p\u00e1gina de 51 y se incrementa pageIndex, entonces la segunda p\u00e1gina devolver\u00e1 del 52 al 102, y as\u00ed sucesivamente. Por lo tanto, en el primer formato, la \u00fanica forma de implementar correctamente el bot\u00f3n de pasar a la siguiente p\u00e1gina es incluir la lectura de una \u2018l\u00ednea extra\u2019 en el servidor, lo cual ser\u00e1 un matiz muy impl\u00edcito.<\/li>\n<li>El tercer formato no tiene sentido en absoluto, ya que para realizar las consultas en la mayor\u00eda de las bases de datos, a\u00fan se debe pasar la cantidad, no el \u00edndice del \u00faltimo registro. Aunque restar startIndex de endIndex es una operaci\u00f3n aritm\u00e9tica b\u00e1sica, aqu\u00ed resulta innecesaria.<\/li>\n<\/ul>\n<p>\nAhora es necesario describir las desventajas de implementar la paginaci\u00f3n a trav\u00e9s de \u2018desplazamiento + cantidad\u2019:<\/p>\n<ul>\n<li>Obtener cada p\u00e1gina siguiente ser\u00e1 m\u00e1s costoso y lento que la anterior, porque la base de datos a\u00fan necesitar\u00e1 recorrer todos los registros \u2018desde el principio\u2019 de acuerdo con los criterios de b\u00fasqueda y ordenaci\u00f3n, y luego detenerse en el fragmento necesario.<\/li>\n<li>No todas las bases de datos pueden soportar este enfoque.<\/li>\n<\/ul>\n<p>\nExisten alternativas, pero tambi\u00e9n tienen sus defectos. El primer enfoque de este tipo se llama \u2018paginaci\u00f3n por conjunto de clave\u2019 o \u2018m\u00e9todo de b\u00fasqueda\u2019 y consiste en lo siguiente: despu\u00e9s de obtener un lote, se pueden recordar los valores de los campos en el \u00faltimo registro de la p\u00e1gina, y luego usarlos para obtener el siguiente lote. Por ejemplo, realizamos una consulta as\u00ed:<\/p>\n<pre><code class=\"sql\">SELECT * FROM Sales.SalesOrderHeader\nORDER BY OrderDate DESC\nOFFSET 0 ROWS\nFETCH NEXT 50 ROWS ONLY\n<\/code><\/pre>\n<p>\nY en la \u00faltima entrada obtuvimos el valor de la fecha del pedido &#8216;2014-06-29&#8217;. Entonces, para obtener la siguiente p\u00e1gina, se puede intentar ejecutar lo siguiente:<\/p>\n<pre><code class=\"sql\">SELECT * FROM Sales.SalesOrderHeader\nWHERE OrderDate &lt; &#039;2014-06-29&#039;\nORDER BY OrderDate DESC\nOFFSET 0 ROWS\nFETCH NEXT 50 ROWS ONLY\n<\/code><\/pre>\n<p>\nEl problema es que OrderDate no es un campo \u00fanico y la condici\u00f3n mencionada anteriormente es muy probable que omita muchas filas necesarias. Para agregar claridad a esta consulta, es necesario a\u00f1adir un campo \u00fanico a la condici\u00f3n (supongamos que 75074 es el \u00faltimo valor de la clave primaria del primer lote):<\/p>\n<pre><code class=\"sql\">SELECT * FROM Sales.SalesOrderHeader\nWHERE (OrderDate = '2014-06-29' AND SalesOrderID &lt; 75074)\n   OR (OrderDate &lt; &#039;2014-06-29&#039;)\nORDER BY OrderDate DESC, SalesOrderID DESC\nOFFSET 0 ROWS\nFETCH NEXT 50 ROWS ONLY\n<\/code><\/pre>\n<p>\nEsta opci\u00f3n funcionar\u00e1 correctamente, pero en general ser\u00e1 dif\u00edcil de optimizar, ya que la condici\u00f3n contiene el operador OR. Si al aumentar OrderDate tambi\u00e9n crece el valor de la clave primaria, la condici\u00f3n se puede simplificar dejando solo el filtro por SalesOrderID. Pero si no hay una correlaci\u00f3n estricta entre los valores de la clave primaria y el campo por el que se ha ordenado el resultado, en la mayor\u00eda de las bases de datos, no se podr\u00e1 evitar este OR. Una excepci\u00f3n que conozco es PostgreSQL, donde se admite plenamente la comparaci\u00f3n de tuplas, y la condici\u00f3n mencionada anteriormente se puede escribir como \u00abWHERE (OrderDate, SalesOrderID) &lt; (&#8216;2014-06-29&#8217;, 75074)\u00bb. Si hay una clave compuesta con estos dos campos, dicha consulta deber\u00eda ser lo suficientemente ligera.<\/p>\n<p>Un segundo enfoque alternativo puede encontrarse, por ejemplo, en <noindex><a rel=\"nofollow\" href=\"https:\/\/www.elastic.co\/guide\/en\/elasticsearch\/reference\/6.8\/search-request-scroll.html\">ElasticSearch scroll API<\/a><\/noindex> o <noindex><a rel=\"nofollow\" href=\"https:\/\/medium.com\/@gary.strange\/understanding-cosmosdb-continuation-tokens-hasmoreresults-and-connectionpolicy-requesttimeouts-3ed1fadfa81d\">Cosmos DB<\/a><\/noindex> \u2014 cuando la consulta, adem\u00e1s de los datos, devuelve un identificador especial, con el cual se puede obtener la siguiente porci\u00f3n de datos. Si este identificador tiene una duraci\u00f3n ilimitada (como en Cosmos DB), entonces es una excelente manera de implementar paginaci\u00f3n con transici\u00f3n secuencial entre p\u00e1ginas (la opci\u00f3n #2 mencionada anteriormente). Sus posibles desventajas: no se admite en todas las bases de datos; el identificador obtenido para la siguiente porci\u00f3n puede tener una duraci\u00f3n limitada, lo que en general no es adecuado para la interacci\u00f3n con el usuario (como en el caso de ElasticSearch scroll API).<\/p>\n<h2>Filtraci\u00f3n compleja<\/h2>\n<p>\nComplicamos a\u00fan m\u00e1s la tarea. Supongamos que surge la necesidad de implementar lo que se conoce como b\u00fasqueda facetada, bien conocida por todos en las tiendas en l\u00ednea. Los ejemplos anteriores basados en la tabla de pedidos no son muy representativos en este caso, as\u00ed que cambiemos a la tabla Product de la base de datos AdventureWorks:<\/p>\n<p><img decoding=\"async\" alt=\"Resultados de b\u00fasqueda y problemas de rendimiento\" src=\"\/wp-content\/uploads\/2020\/03\/070890a3cb79de3edc69b60b5300efe7.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n\u00bfCu\u00e1l es la idea de la b\u00fasqueda facetada? Que para cada elemento del filtro se muestre la cantidad de registros que corresponden a ese criterio. <i>teniendo en cuenta los filtros seleccionados en todas las dem\u00e1s categor\u00edas.<\/i>.<\/p>\n<p>Por ejemplo, si elegimos en este caso la categor\u00eda Bikes y el color Black, la tabla mostrar\u00e1 solo bicicletas de color negro, pero adem\u00e1s:<\/p>\n<ul>\n<li>Para cada criterio del grupo 'Categories', se mostrar\u00e1 el n\u00famero de productos de esa categor\u00eda de color negro.<\/li>\n<li>Para cada criterio del grupo 'Colors', se mostrar\u00e1 el n\u00famero de bicicletas de ese color.<\/li>\n<\/ul>\n<p>\nAqu\u00ed hay un ejemplo de salida de resultados para tales condiciones:<\/p>\n<p><img decoding=\"async\" alt=\"Resultados de b\u00fasqueda y problemas de rendimiento\" src=\"\/wp-content\/uploads\/2020\/03\/7c8ec4d8b427f1f59f0129afebe74b08.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nSi adem\u00e1s marcamos la categor\u00eda 'Clothing', la tabla mostrar\u00e1 tambi\u00e9n ropa de color negro que est\u00e1 disponible. La cantidad de productos de color negro en la secci\u00f3n 'Color' tambi\u00e9n se recalcular\u00e1 seg\u00fan las nuevas condiciones, pero nada cambiar\u00e1 en la secci\u00f3n 'Categories'... Espero que estos ejemplos sean suficientes para entender el algoritmo habitual del funcionamiento de la b\u00fasqueda facetada.<\/p>\n<p>Ahora imaginemos c\u00f3mo se podr\u00eda implementar esto en una base de datos relacional. Cada grupo de criterios, como Category y Color, requerir\u00e1 una consulta separada:<\/p>\n<pre><code class=\"sql\">SELECT pc.ProductCategoryID, pc.Name, COUNT(1) FROM Production.Product p\n  INNER JOIN Production.ProductSubcategory ps ON p.ProductSubcategoryID = ps.ProductSubcategoryID\n  INNER JOIN Production.ProductCategory pc ON ps.ProductCategoryID = pc.ProductCategoryID\nWHERE p.Color = 'Black'\nGROUP BY pc.ProductCategoryID, pc.Name\nORDER BY COUNT(1) DESC\n<\/code><\/pre>\n<p>\n<img decoding=\"async\" alt=\"Resultados de b\u00fasqueda y problemas de rendimiento\" src=\"\/wp-content\/uploads\/2020\/03\/3b59b68ce934c4ec1630ec649e68ccdb.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<pre><code class=\"sql\">SELECT Color, COUNT(1) FROM Production.Product p\n  INNER JOIN Production.ProductSubcategory ps ON p.ProductSubcategoryID = ps.ProductSubcategoryID\nWHERE ps.ProductCategoryID = 1 --Bikes\nGROUP BY Color\nORDER BY COUNT(1) DESC\n<\/code><\/pre>\n<p>\n<img decoding=\"async\" alt=\"Resultados de b\u00fasqueda y problemas de rendimiento\" src=\"\/wp-content\/uploads\/2020\/03\/82e1e6ab0fae683d383e3fecf4e3a15a.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n\u00bfQu\u00e9 est\u00e1 mal con esta soluci\u00f3n? Muy simple: no escala bien. Cada secci\u00f3n del filtro requiere una consulta separada para contar cantidades y estas consultas no son las m\u00e1s ligeras. En las tiendas en l\u00ednea, algunas categor\u00edas pueden tener varias decenas de secciones de filtro, lo que puede convertirse en un problema serio para el rendimiento.<\/p>\n<p>Normalmente, despu\u00e9s de estas afirmaciones, me proponen algunas soluciones, a saber:<\/p>\n<ul>\n<li>Combinar todos los conteos en una sola consulta. T\u00e9cnicamente, esto es posible con la palabra clave UNION, pero en t\u00e9rminos de rendimiento no ayudar\u00e1 mucho; la base de datos a\u00fan tendr\u00e1 que ejecutar cada uno de los fragmentos \"desde cero\".<\/li>\n<li>Cachear los conteos. Esto es algo que me sugieren pr\u00e1cticamente cada vez que describo el problema. La cuesti\u00f3n es que esto no es generalmente posible. Supongamos que tenemos 10 \"facetas\", cada una con 5 valores. Esta es una situaci\u00f3n muy \"modesta\" en comparaci\u00f3n con lo que se puede ver en las mismas tiendas en l\u00ednea. La elecci\u00f3n de un elemento de faceta afecta a los conteos en las 9 restantes; en otras palabras, para cada combinaci\u00f3n de criterios, los conteos pueden ser diferentes. En total, en nuestro ejemplo hay 50 criterios que el usuario puede seleccionar, lo que significa que habr\u00e1 250 combinaciones posibles. No hay suficiente memoria ni tiempo para llenar tal matriz de datos. Aqu\u00ed se podr\u00eda argumentar que no todas las combinaciones son reales y que el usuario rara vez selecciona m\u00e1s de 5-10 criterios. S\u00ed, se puede implementar una carga perezosa y cach\u00e9 de conteos solo para lo que alguna vez se ha seleccionado, pero cuanto m\u00e1s opciones haya, menos efectivo ser\u00e1 dicho cach\u00e9 y m\u00e1s evidentes ser\u00e1n los problemas de tiempo de respuesta (especialmente si el conjunto de datos cambia regularmente).<\/li>\n<\/ul>\n<p>\nAfortunadamente, este tipo de problema ya tiene soluciones bastante efectivas que funcionan de manera predecible en grandes vol\u00famenes de datos. Para cualquiera de estas opciones, tiene sentido dividir el rec\u00e1lculo de facetas y la obtenci\u00f3n de la p\u00e1gina de resultados en dos llamadas paralelas al servidor y organizar la interfaz de usuario de tal manera que la carga de datos de las facetas \"no interfiera\" con la visualizaci\u00f3n de los resultados de b\u00fasqueda.<\/p>\n<ul>\n<li>Llamar al rec\u00e1lculo completo de los \"facetas\" lo menos posible. Por ejemplo, en lugar de recalcular todo cada vez que se cambian los criterios de b\u00fasqueda, se puede encontrar el n\u00famero total de resultados que cumplen con las condiciones actuales y ofrecer al usuario mostrarlos: \"Se encontraron 1425 registros, \u00bfmostrar?\" El usuario puede continuar cambiando los criterios de b\u00fasqueda o presionar el bot\u00f3n \"mostrar\". Solo en este segundo caso se ejecutar\u00e1n todas las solicitudes para obtener resultados y recalcular las cantidades en todas las \"facetas\". Como es f\u00e1cil de notar, esto implica lidiar con la solicitud para obtener el n\u00famero total de resultados y su optimizaci\u00f3n. Este m\u00e9todo se puede encontrar en muchas peque\u00f1as tiendas en l\u00ednea. Es evidente que no es una panacea para este problema, pero en casos simples puede ser un buen compromiso.<\/li>\n<li>Utilizar motores de b\u00fasqueda para encontrar resultados y contar facetas, como Solr, ElasticSearch, Sphinx y otros. Todos ellos est\u00e1n dise\u00f1ados para construir \"facetas\" y lo hacen de manera bastante eficiente gracias al \u00edndice invertido. C\u00f3mo est\u00e1n configurados los motores de b\u00fasqueda, por qu\u00e9 son m\u00e1s efectivos en estos casos que las bases de datos de prop\u00f3sito general, cu\u00e1les son las pr\u00e1cticas y los escollos, es un tema para un art\u00edculo aparte. Aqu\u00ed quiero se\u00f1alar que el motor de b\u00fasqueda no puede reemplazar el almacenamiento de datos principal, se utiliza como complemento: cualquier cambio en la base de datos principal que sea relevante para la b\u00fasqueda se sincroniza en el \u00edndice de b\u00fasqueda; el mecanismo de b\u00fasqueda interacciona generalmente solo con el motor de b\u00fasqueda y no consulta la base de datos principal. Uno de los puntos m\u00e1s importantes aqu\u00ed es c\u00f3mo organizar esta sincronizaci\u00f3n de manera confiable. Todo depende de los requisitos de \"tiempo de respuesta\". Si el tiempo entre el cambio en la base de datos principal y su \"manifestaci\u00f3n\" en la b\u00fasqueda no es cr\u00edtico, se puede hacer un servicio que busque registros recientemente cambiados e indexe cada pocos minutos. Si se requiere el tiempo de respuesta m\u00e1s corto posible, se puede implementar algo como <noindex><a rel=\"nofollow\" href=\"https:\/\/microservices.io\/patterns\/data\/transactional-outbox.html\">outbox transaccional<\/a><\/noindex> para enviar actualizaciones al servicio de b\u00fasqueda.<\/li>\n<\/ul>\n<p><\/p>\n<h2>Conclusiones<\/h2>\n<p><\/p>\n<ol>\n<li>La implementaci\u00f3n de paginaci\u00f3n en el servidor es una complicaci\u00f3n seria, y su uso solo tiene sentido para conjuntos de datos que crecen r\u00e1pidamente o que son simplemente grandes. No hay una receta absolutamente precisa para evaluar si algo es \"grande\" o \"de r\u00e1pido crecimiento\", pero me adherir\u00eda a este enfoque:\n<ul>\n<li>Si la obtenci\u00f3n de la colecci\u00f3n completa de datos, teniendo en cuenta el tiempo del servidor y la transmisi\u00f3n a trav\u00e9s de la red, se ajusta a los requisitos de rendimiento, no tiene sentido implementar paginaci\u00f3n en el servidor.<\/li>\n<li>Puede haber situaciones en las que no se prevean problemas de rendimiento en el corto plazo, ya que hay pocos datos, pero la colecci\u00f3n de datos est\u00e1 creciendo constantemente. Si alg\u00fan conjunto de datos puede dejar de cumplir con el punto anterior en el futuro, es mejor implementar la paginaci\u00f3n desde el principio.<\/li>\n<\/ul>\n<\/li>\n<li>Si no hay un requisito estricto del negocio para mostrar el n\u00famero total de resultados o para mostrar los n\u00fameros de p\u00e1gina, y adem\u00e1s su sistema no tiene un motor de b\u00fasqueda, es mejor no implementar estos aspectos y considerar la opci\u00f3n #2.<\/li>\n<li>Si existe un requisito claro para la b\u00fasqueda facetada, usted tiene dos opciones para no sacrificar el rendimiento:\n<ul>\n<li>No recalcular todas las cantidades en cada cambio de criterio de b\u00fasqueda.<\/li>\n<li>Utilizar motores de b\u00fasqueda como Solr, ElasticSearch, Sphinx y otros. Pero debe entenderse que no puede ser un sustituto de la base de datos principal y debe usarse como complemento del almacenamiento principal para resolver tareas de b\u00fasqueda. <\/li>\n<\/ul>\n<\/li>\n<li>Adem\u00e1s, en el caso de la b\u00fasqueda facetada, tiene sentido dividir la obtenci\u00f3n de la p\u00e1gina de resultados de b\u00fasqueda y el conteo de cantidades en dos solicitudes paralelas. El conteo de cantidades puede llevar m\u00e1s tiempo que la obtenci\u00f3n de resultados, mientras que estos \u00faltimos son m\u00e1s importantes para el usuario.<\/li>\n<li>Si utiliza una base de datos SQL para la b\u00fasqueda, cualquier cambio de c\u00f3digo relacionado con esta parte debe probarse cuidadosamente en relaci\u00f3n con el rendimiento en un volumen de datos adecuado (que supere el volumen en la base \"en vivo\"). Tambi\u00e9n se recomienda usar un monitoreo del tiempo de ejecuci\u00f3n de las consultas en todas las instancias de la base de datos, y especialmente en la \"en vivo\". Incluso si en la fase de desarrollo los planes de consultas funcionaban bien, a medida que aumenta el volumen de datos, la situaci\u00f3n puede cambiar notablemente.<\/li>\n<\/ol>\n<p>Fuente: <a content=\"nofollow\" rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/company\/epam_systems\/blog\/493438\/\">habr.com<\/a> <\/p>","protected":false,"gt_translate_keys":[{"key":"rendered","format":"html"}]},"excerpt":{"rendered":"<p>\u041e\u0434\u0438\u043d \u0438\u0437 \u0442\u0438\u043f\u043e\u0432\u044b\u0445 \u0441\u0446\u0435\u043d\u0430\u0440\u0438\u0435\u0432 \u0432\u043e \u0432\u0441\u0435\u0445 \u043f\u0440\u0438\u0432\u044b\u0447\u043d\u044b\u0445 \u043d\u0430\u043c \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f\u0445 \u2014 \u043f\u043e\u0438\u0441\u043a \u0434\u0430\u043d\u043d\u044b\u0445 \u043f\u043e \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u043d\u044b\u043c \u043a\u0440\u0438\u0442\u0435\u0440\u0438\u044f\u043c \u0438 \u0432\u044b\u0432\u043e\u0434 \u0438\u0445 \u0432 \u0443\u0434\u043e\u0431\u043d\u043e\u043c \u0434\u043b\u044f \u0447\u0442\u0435\u043d\u0438\u044f \u0432\u0438\u0434\u0435. \u0422\u0443\u0442 \u0436\u0435 \u043c\u043e\u0433\u0443\u0442 \u0431\u044b\u0442\u044c \u0434\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0435 \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u0438 \u043f\u043e \u0441\u043e\u0440\u0442\u0438\u0440\u043e\u0432\u043a\u0435, \u0433\u0440\u0443\u043f\u043f\u0438\u0440\u043e\u0432\u043a\u0435, \u043f\u043e\u0441\u0442\u0440\u0430\u043d\u0438\u0447\u043d\u043e\u043c\u0443 \u0432\u044b\u0432\u043e\u0434\u0443. \u0417\u0430\u0434\u0430\u0447\u0430, \u043f\u043e \u0438\u0434\u0435\u0435, \u0442\u0440\u0438\u0432\u0438\u0430\u043b\u044c\u043d\u0430\u044f, \u043d\u043e \u043f\u0440\u0438 \u0435\u0435 \u0440\u0435\u0448\u0435\u043d\u0438\u0438 \u043c\u043d\u043e\u0433\u0438\u0435 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u0438 \u0434\u0435\u043b\u0430\u044e\u0442 \u0440\u044f\u0434 \u043e\u0448\u0438\u0431\u043e\u043a, \u0438\u0437-\u0437\u0430 \u043a\u043e\u0442\u043e\u0440\u044b\u0445 \u043f\u043e\u0442\u043e\u043c \u0441\u0442\u0440\u0430\u0434\u0430\u0435\u0442 \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u044c. \u041f\u043e\u043f\u0440\u043e\u0431\u0443\u0435\u043c \u0440\u0430\u0441\u0441\u043c\u043e\u0442\u0440\u0435\u0442\u044c \u0440\u0430\u0437\u043b\u0438\u0447\u043d\u044b\u0435 [&hellip;]<\/p>\n","protected":false,"gt_translate_keys":[{"key":"rendered","format":"html"}]},"author":1,"featured_media":75531,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[688],"tags":[],"class_list":["post-75530","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-administrirovanie"],"aioseo_notices":[],"aioseo_head":"\n\t\t<!-- All in One SEO 5.0.2 - aioseo.com -->\n\t<meta name=\"description\" content=\"\u041e\u0434\u0438\u043d \u0438\u0437 \u0442\u0438\u043f\u043e\u0432\u044b\u0445 \u0441\u0446\u0435\u043d\u0430\u0440\u0438\u0435\u0432 \u0432\u043e \u0432\u0441\u0435\u0445 \u043f\u0440\u0438\u0432\u044b\u0447\u043d\u044b\u0445 \u043d\u0430\u043c \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f\u0445 \u2014 \u043f\u043e\u0438\u0441\u043a \u0434\u0430\u043d\u043d\u044b\u0445 \u043f\u043e \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u043d\u044b\u043c \u043a\u0440\u0438\u0442\u0435\u0440\u0438\u044f\u043c \u0438 \u0432\u044b\u0432\u043e\u0434 \u0438\u0445 \u0432 \u0443\u0434\u043e\u0431\u043d\u043e\u043c \u0434\u043b\u044f \u0447\u0442\u0435\u043d\u0438\u044f \u0432\u0438\u0434\u0435.\" \/>\n\t<meta name=\"robots\" content=\"max-image-preview:large\" \/>\n\t<meta name=\"author\" content=\"Yuri Gagarin\"\/>\n\t<link rel=\"canonical\" href=\"https:\/\/prohoster.info\/es\/blog\/administrirovanie\/vyvod-rezultatov-poiska-i-problemy-s-proizvoditelnostyu\" \/>\n\t<meta name=\"generator\" content=\"All in One SEO (AIOSEO) 5.0.2\" \/>\n\t\t<meta property=\"og:locale\" content=\"es_ES\" \/>\n\t\t<meta property=\"og:site_name\" content=\"ProHoster | \u041a\u0443\u043f\u0438\u0442\u044c \u043d\u0430\u0434\u0435\u0436\u043d\u044b\u0439 \u0445\u043e\u0441\u0442\u0438\u043d\u0433 \u0434\u043b\u044f \u0441\u0430\u0439\u0442\u043e\u0432 \u0441 \u0437\u0430\u0449\u0438\u0442\u043e\u0439 \u043e\u0442 DDoS, VPS VDS \u0441\u0435\u0440\u0432\u0435\u0440\u044b\" \/>\n\t\t<meta property=\"og:type\" content=\"article\" \/>\n\t\t<meta property=\"og:title\" content=\"\ud83e\udd47\u0412\u044b\u0432\u043e\u0434 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u043e\u0432 \u043f\u043e\u0438\u0441\u043a\u0430 \u0438 \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u044b \u0441 \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u044c\u044e | ProHoster\" \/>\n\t\t<meta property=\"og:description\" content=\"\u041e\u0434\u0438\u043d \u0438\u0437 \u0442\u0438\u043f\u043e\u0432\u044b\u0445 \u0441\u0446\u0435\u043d\u0430\u0440\u0438\u0435\u0432 \u0432\u043e \u0432\u0441\u0435\u0445 \u043f\u0440\u0438\u0432\u044b\u0447\u043d\u044b\u0445 \u043d\u0430\u043c \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f\u0445 \u2014 \u043f\u043e\u0438\u0441\u043a \u0434\u0430\u043d\u043d\u044b\u0445 \u043f\u043e \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u043d\u044b\u043c \u043a\u0440\u0438\u0442\u0435\u0440\u0438\u044f\u043c \u0438 \u0432\u044b\u0432\u043e\u0434 \u0438\u0445 \u0432 \u0443\u0434\u043e\u0431\u043d\u043e\u043c \u0434\u043b\u044f \u0447\u0442\u0435\u043d\u0438\u044f \u0432\u0438\u0434\u0435.\" \/>\n\t\t<meta property=\"og:url\" content=\"https:\/\/prohoster.info\/es\/blog\/administrirovanie\/vyvod-rezultatov-poiska-i-problemy-s-proizvoditelnostyu\" \/>\n\t\t<meta property=\"og:image\" content=\"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg\" \/>\n\t\t<meta property=\"og:image:secure_url\" content=\"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg\" \/>\n\t\t<meta property=\"og:image:width\" content=\"350\" \/>\n\t\t<meta property=\"og:image:height\" content=\"350\" \/>\n\t\t<meta property=\"article:published_time\" content=\"2020-03-26T17:42:23+00:00\" \/>\n\t\t<meta property=\"article:modified_time\" content=\"2020-03-26T17:42:23+00:00\" \/>\n\t\t<meta property=\"article:publisher\" content=\"https:\/\/www.facebook.com\/prohoster\" \/>\n\t\t<meta property=\"article:author\" content=\"https:\/\/www.facebook.com\/prohoster\" \/>\n\t\t<!-- All in One SEO -->\n\n","aioseo_head_json":{"title":"\ud83e\udd47Resultados de b\u00fasqueda y problemas de rendimiento | ProHoster","description":"Uno de los escenarios t\u00edpicos en todas las aplicaciones que conocemos es buscar datos seg\u00fan ciertos criterios y presentarlos de una manera f\u00e1cil de leer.","canonical_url":"https:\/\/prohoster.info\/es\/blog\/administrirovanie\/vyvod-rezultatov-poiska-i-problemy-s-proizvoditelnostyu","robots":"max-image-preview:large","keywords":"","webmasterTools":{"miscellaneous":""},"schema":null,"og:locale":"es_ES","og:site_name":"ProHoster | \u041a\u0443\u043f\u0438\u0442\u044c \u043d\u0430\u0434\u0435\u0436\u043d\u044b\u0439 \u0445\u043e\u0441\u0442\u0438\u043d\u0433 \u0434\u043b\u044f \u0441\u0430\u0439\u0442\u043e\u0432 \u0441 \u0437\u0430\u0449\u0438\u0442\u043e\u0439 \u043e\u0442 DDoS, VPS VDS \u0441\u0435\u0440\u0432\u0435\u0440\u044b","og:type":"article","og:title":"\ud83e\udd47\u0412\u044b\u0432\u043e\u0434 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u043e\u0432 \u043f\u043e\u0438\u0441\u043a\u0430 \u0438 \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u044b \u0441 \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u044c\u044e | ProHoster","og:description":"\u041e\u0434\u0438\u043d \u0438\u0437 \u0442\u0438\u043f\u043e\u0432\u044b\u0445 \u0441\u0446\u0435\u043d\u0430\u0440\u0438\u0435\u0432 \u0432\u043e \u0432\u0441\u0435\u0445 \u043f\u0440\u0438\u0432\u044b\u0447\u043d\u044b\u0445 \u043d\u0430\u043c \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f\u0445 \u2014 \u043f\u043e\u0438\u0441\u043a \u0434\u0430\u043d\u043d\u044b\u0445 \u043f\u043e \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u043d\u044b\u043c \u043a\u0440\u0438\u0442\u0435\u0440\u0438\u044f\u043c \u0438 \u0432\u044b\u0432\u043e\u0434 \u0438\u0445 \u0432 \u0443\u0434\u043e\u0431\u043d\u043e\u043c \u0434\u043b\u044f \u0447\u0442\u0435\u043d\u0438\u044f \u0432\u0438\u0434\u0435.","og:url":"https:\/\/prohoster.info\/es\/blog\/administrirovanie\/vyvod-rezultatov-poiska-i-problemy-s-proizvoditelnostyu","og:image":"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg","og:image:secure_url":"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg","og:image:width":350,"og:image:height":350,"article:published_time":"2020-03-26T17:42:23+00:00","article:modified_time":"2020-03-26T17:42:23+00:00","article:publisher":"https:\/\/www.facebook.com\/prohoster","article:author":"https:\/\/www.facebook.com\/prohoster"},"aioseo_meta_data":{"post_id":"75530","title":null,"description":null,"keywords":null,"keyphrases":null,"primary_term":null,"canonical_url":null,"og_title":null,"og_description":null,"og_object_type":"default","og_image_type":"default","og_image_url":null,"og_image_width":null,"og_image_height":null,"og_image_custom_url":null,"og_image_custom_fields":null,"og_video":null,"og_custom_url":null,"og_article_section":null,"og_article_tags":null,"twitter_use_og":false,"twitter_card":"default","twitter_image_type":"default","twitter_image_url":null,"twitter_image_custom_url":null,"twitter_image_custom_fields":null,"twitter_title":null,"twitter_description":null,"schema":{"blockGraphs":[],"customGraphs":[],"default":{"data":{"Article":[],"Course":[],"Dataset":[],"FAQPage":[],"Movie":[],"Person":[],"Product":[],"ProductReview":[],"Car":[],"Recipe":[],"Service":[],"SoftwareApplication":[],"WebPage":[]},"graphName":"","isEnabled":true},"graphs":[]},"schema_type":null,"schema_type_options":null,"pillar_content":false,"robots_default":true,"robots_noindex":false,"robots_noarchive":false,"robots_nosnippet":false,"robots_nofollow":false,"robots_noimageindex":false,"robots_noodp":false,"robots_notranslate":false,"robots_max_snippet":null,"robots_max_videopreview":null,"robots_max_imagepreview":"large","priority":null,"frequency":null,"local_seo":null,"seo_analyzer_scan_date":null,"breadcrumb_settings":null,"limit_modified_date":false,"reviewed_by":null,"ai":null,"created":"2021-02-28 17:55:26","updated":"2022-10-02 02:12:16","focus_keyword":null,"additional_keywords":null,"truseo_locale":null},"gt_translate_keys":[{"key":"link","format":"url"}],"_links":{"self":[{"href":"https:\/\/prohoster.info\/es\/wp-json\/wp\/v2\/posts\/75530","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/prohoster.info\/es\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/prohoster.info\/es\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/prohoster.info\/es\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/prohoster.info\/es\/wp-json\/wp\/v2\/comments?post=75530"}],"version-history":[{"count":0,"href":"https:\/\/prohoster.info\/es\/wp-json\/wp\/v2\/posts\/75530\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/prohoster.info\/es\/wp-json\/wp\/v2\/media\/75531"}],"wp:attachment":[{"href":"https:\/\/prohoster.info\/es\/wp-json\/wp\/v2\/media?parent=75530"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/prohoster.info\/es\/wp-json\/wp\/v2\/categories?post=75530"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/prohoster.info\/es\/wp-json\/wp\/v2\/tags?post=75530"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}