{"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\/da\/blog\/administrirovanie\/vyvod-rezultatov-poiska-i-problemy-s-proizvoditelnostyu","title":{"rendered":"Visning af s\u00f8geresultater og ydeevneproblemer","gt_translate_keys":[{"key":"rendered","format":"text"}]},"content":{"rendered":"<p>En af de typiske scenarier i alle de velkendte applikationer er at s\u00f8ge data baseret p\u00e5 bestemte kriterier og vise dem i en letl\u00e6selig form. Her kan der ogs\u00e5 v\u00e6re ekstra muligheder for sortering, gruppering og paginering. Opgaven er, i teorien, triviel, men mange udviklere beg\u00e5r en r\u00e6kke fejl i l\u00f8sningen af den, hvilket p\u00e5virker ydeevnen. Lad os se p\u00e5 forskellige l\u00f8sninger p\u00e5 denne opgave og formulere anbefalinger til valg af den mest effektive implementation.<\/p>\n<p><img decoding=\"async\" alt=\"Visning af s\u00f8geresultater og ydeevneproblemer\" 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>Paginering #1<\/h2>\n<p>\nDen enkleste mulighed, der falder en ind, er at vise s\u00f8geresultaterne side-for-side i den mest klassiske form.<\/p>\n<p><img decoding=\"async\" alt=\"Visning af s\u00f8geresultater og ydeevneproblemer\" src=\"\/wp-content\/uploads\/2020\/03\/4957d9ad21a8a0c70390b224fe76cb33.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nLad os antage, at der anvendes en relationel database i applikationen. I s\u00e5 fald skal der udf\u00f8res to SQL-foresp\u00f8rgsler for at vise informationen i denne form:<\/p>\n<ul>\n<li>Hente r\u00e6kkerne til den aktuelle side.<\/li>\n<li>T\u00e6lle det samlede antal r\u00e6kker, der opfylder s\u00f8gekriterierne \u2014 dette er n\u00f8dvendigt for at vise siderne.<\/li>\n<\/ul>\n<p>\nLad os se p\u00e5 den f\u00f8rste foresp\u00f8rgsel ved hj\u00e6lp af en test MS SQL database <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/Microsoft\/sql-server-samples\/releases\/download\/adventureworks\/AdventureWorks2016_EXT.bak\">AdventureWorks <\/a><\/noindex>til server 2016. Til dette form\u00e5l vil vi anvende tabellen 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>\nDen ovenst\u00e5ende foresp\u00f8rgsel vil returnere de f\u00f8rste 50 ordrer fra listen, sorteret efter faldende tilf\u00f8jelsesdato, med andre ord \u2014 de 50 seneste ordrer.<\/p>\n<p>Den k\u00f8rer hurtigt p\u00e5 testdatabasen, men lad os se p\u00e5 udf\u00f8relsesplanen og input-output statistikkerne:<\/p>\n<p><img decoding=\"async\" alt=\"Visning af s\u00f8geresultater og ydeevneproblemer\" src=\"\/wp-content\/uploads\/2020\/03\/248e4b64593c7000216b46777183d639.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<pre><code class=\"plaintext\">Tabel 'SalesOrderHeader'. Scan count 1, logical reads 698, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.<\/code><\/pre>\n<p>\n<i>For at f\u00e5 input\/output statistikker for hver foresp\u00f8rgsel kan man udf\u00f8re kommandoen SET STATISTICS IO ON i sp\u00f8rringsudf\u00f8relsesmilj\u00f8et.<\/i><\/p>\n<p>Som det fremg\u00e5r af udf\u00f8relsesplanen, er den mest ressourcekr\u00e6vende del sorteringen af alle r\u00e6kkerne i den originale tabel efter tilf\u00f8jelsesdato. Problemet er, at jo flere r\u00e6kker der kommer i tabellen, jo 'tungere' vil sorteringen blive. I praksis b\u00f8r s\u00e5danne situationer undg\u00e5s, s\u00e5 lad os tilf\u00f8je et indeks p\u00e5 tilf\u00f8jelsesdatoen og se, om ressourceforbruget \u00e6ndrer sig:<\/p>\n<p><img decoding=\"async\" alt=\"Visning af s\u00f8geresultater og ydeevneproblemer\" src=\"\/wp-content\/uploads\/2020\/03\/a33e1eadf6f8f8b516b9bb834ad7ad86.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<pre><code class=\"plaintext\">Tabel 'SalesOrderHeader'. Scan count 1, logical reads 165, physical reads 0, read-ahead reads 5, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.\n<\/code><\/pre>\n<p>\nDet er tydeligt, at det er blevet meget bedre. Men er alle problemer l\u00f8st? Lad os \u00e6ndre foresp\u00f8rgslen til at s\u00f8ge efter ordrer, hvor den samlede varev\u00e6rdi overstiger 100 dollars:<\/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=\"Visning af s\u00f8geresultater og ydeevneproblemer\" src=\"\/wp-content\/uploads\/2020\/03\/e057bfc89e11a31c6d2855b93ec3c747.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<pre><code class=\"plaintext\">Tabel 'SalesOrderHeader'. Scan t\u00e6lling 1, logiske l\u00e6sninger 1081, fysiske l\u00e6sninger 0, l\u00e6sninger p\u00e5 forh\u00e5nd 0, lob logiske l\u00e6sninger 0, lob fysiske l\u00e6sninger 0, lob l\u00e6sninger p\u00e5 forh\u00e5nd 0.<\/code><\/pre>\n<p>\nVi har en sjov situation: foresp\u00f8rgslen er ikke meget d\u00e5rligere end den tidligere, men det faktiske antal logiske l\u00e6sninger er n\u00e6sten det dobbelte af, hvad det ville v\u00e6re ved en fuld scanning af tabellen. Der er en l\u00f8sning \u2014 hvis vi laver et sammensat indeks ud fra det eksisterende indeks og tilf\u00f8jer den samlede pris p\u00e5 varer som det andet felt, f\u00e5r vi igen 165 logiske l\u00e6sninger:<\/p>\n<pre><code class=\"sql\">CREATE INDEX IX_SalesOrderHeader_OrderDate_SubTotal on Sales.SalesOrderHeader(OrderDate, SubTotal);\n<\/code><\/pre>\n<p>\nDenne serie af eksempler kan forts\u00e6tte meget l\u00e6ngere, men der er to hovedpunkter, som jeg gerne vil uddybe her:<\/p>\n<ul>\n<li>Tilf\u00f8jelsen af ethvert nyt kriterium eller sorteringsr\u00e6kkef\u00f8lge til foresp\u00f8rgslen kan have en betydelig indflydelse p\u00e5 hastigheden af dens udf\u00f8relse.<\/li>\n<li>Men hvis vi kun har brug for at tr\u00e6kke en del af dataene, snarere end alle resultater, der opfylder s\u00f8gekriterierne \u2014 er der mange m\u00e5der at optimere en s\u00e5dan foresp\u00f8rgsel p\u00e5.<\/li>\n<\/ul>\n<p>\nLad os nu g\u00e5 videre til den anden foresp\u00f8rgsel, der blev n\u00e6vnt i starten \u2014 den, der t\u00e6ller antallet af poster, der opfylder s\u00f8gekriteriet. Lad os tage det samme eksempel \u2014 at s\u00f8ge efter ordrer, der er dyrere end 100 dollars:<\/p>\n<pre><code class=\"sql\">SELECT COUNT(1) FROM Sales.SalesOrderHeader\nWHERE SubTotal &gt; 100\n<\/code><\/pre>\n<p>\nMed det sammensatte indeks n\u00e6vnt ovenfor, f\u00e5r vi:<\/p>\n<p><img decoding=\"async\" alt=\"Visning af s\u00f8geresultater og ydeevneproblemer\" src=\"\/wp-content\/uploads\/2020\/03\/7beab0c83a50e2d68a83a965df555ec9.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<pre><code class=\"plaintext\">Tabel 'SalesOrderHeader'. Scan count 1, logical reads 698, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.<\/code><\/pre>\n<p>\nAt foresp\u00f8rgslen g\u00e5r igennem hele indekset er ikke overraskende, da feltet SubTotal ikke st\u00e5r i f\u00f8rste position, s\u00e5 foresp\u00f8rgslen kan ikke drage fordel af det. Problemet l\u00f8ses ved at tilf\u00f8je et endnu et indeks p\u00e5 feltet SubTotal, som endelig giver os kun 48 logiske l\u00e6sninger.<\/p>\n<p>Man kan give flere eksempler p\u00e5 foresp\u00f8rgsler til opt\u00e6lling, men essensen forbliver den samme: <b>at f\u00e5 en portion data og t\u00e6lle det samlede antal \u2014 det er to grundl\u00e6ggende forskellige foresp\u00f8rgsler<\/b>, og hver kr\u00e6ver sine egne foranstaltninger til optimering. Generelt set vil det ikke v\u00e6re muligt at finde en kombination af indekser, der fungerer lige godt for begge foresp\u00f8rgsler.<\/p>\n<p>Derfor er et af de vigtige krav, der skal afklares ved udviklingen af en s\u00e5dan s\u00f8gel\u00f8sning, om det faktisk er vigtigt for virksomheden at se det samlede antal fundne objekter. Ofte er det ikke tilf\u00e6ldet. Og navigation ved hj\u00e6lp af specifikke sidetal er, efter min mening, et meget sn\u00e6vert anvendelsesomr\u00e5de, da de fleste scenarier med paging ser ud som \"g\u00e5 til n\u00e6ste side\".<\/p>\n<h2>Paging mulighed #2<\/h2>\n<p>\nAntag, at brugerne ikke er interesseret i at vide det samlede antal fundne objekter. Lad os pr\u00f8ve at forenkle s\u00f8gesiden:<\/p>\n<p><img decoding=\"async\" alt=\"Visning af s\u00f8geresultater og ydeevneproblemer\" src=\"\/wp-content\/uploads\/2020\/03\/66f5ee6dc1a52f14e55ae31e05c502b2.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nI realiteten er der kun \u00e6ndret det, at der ikke er mulighed for at navigere til specifikke sidetal, og nu beh\u00f8ver denne tabel for visning ikke at vide, hvor mange sider der kan v\u00e6re i alt. Men sp\u00f8rgsm\u00e5let opst\u00e5r \u2014 hvordan finder tabellen ud af, om der er data til den n\u00e6ste side (for korrekt at vise \"N\u00e6ste\" linket)?<\/p>\n<p>Svaret er meget simpelt: Man kan l\u00e6se fra databasen en r\u00e6kke mere, end n\u00f8dvendigt for visning, og eksistensen af denne \"ekstra\" post vil vise, om der er en n\u00e6ste portion. P\u00e5 denne m\u00e5de vil det kun kr\u00e6ve \u00e9n foresp\u00f8rgsel for at f\u00e5 en post data, hvilket v\u00e6sentligt forbedrer ydelsen og letter vedligeholdelsen af s\u00e5dan funktionalitet. Jeg har i praksis haft en situation, hvor frav\u00e6ret af at t\u00e6lle det samlede antal poster accelererede resultatudgivelsen med 4-5 gange.<\/p>\n<p>For denne tilgang er der flere muligheder for brugergr\u00e6nsefladen: \"tilbage\" og \"frem\" knapper, som i eksemplet ovenfor, en \"indl\u00e6s flere\" knap, der simpelthen tilf\u00f8jer en ny portion til de viste resultater, \"uendelig rulle\", som fungerer efter princippet om \"indl\u00e6s flere\", men signalet for at hente den n\u00e6ste portion er, at brugeren ruller til bunden af alle viste resultater. Uanset hvad den visuelle l\u00f8sning er, forbliver princippet for dataudtagning det samme.<\/p>\n<h2>Nuancer ved implementering af paging<\/h2>\n<p>\nI alle de tidligere n\u00e6vnte foresp\u00f8rgsler anvendes metoden \"offset + antal\", hvor der i selve foresp\u00f8rgslen angives, hvilken r\u00e6kkef\u00f8lge r\u00e6kke resultatet skal starte fra og hvor mange r\u00e6kker der skal returneres. F\u00f8rst vil vi se p\u00e5, hvordan man bedst organiserer overf\u00f8rslen af parametre i dette tilf\u00e6lde. I praksis har jeg set flere metoder:<\/p>\n<ul>\n<li>Den sekventielle nummer for den forespurgte side (pageIndex), side st\u00f8rrelse (pageSize).<\/li>\n<li>Den sekventielle nummer for den f\u00f8rste post, der skal returneres (startIndex), det maksimale antal poster i resultatet (count).<\/li>\n<li>Den sekventielle nummer for den f\u00f8rste post, der skal returneres (startIndex), den sekventielle nummer for den sidste post, der skal returneres (endIndex).<\/li>\n<\/ul>\n<p>\nVed f\u00f8rste \u00f8jekast kan det virke s\u00e5 element\u00e6rt, at der ikke er nogen forskel. Men det er ikke tilf\u00e6ldet \u2014 det mest bekvemme og alsidige valg er det andet (startIndex, count). Der er flere grunde til dette:<\/p>\n<ul>\n<li>For den tilgang med at tr\u00e6kke +1 post, som n\u00e6vnt ovenfor, er den f\u00f8rste mulighed med pageIndex og pageSize ekstremt ubelejlig. For eksempel \u00f8nsker vi at vise 50 poster p\u00e5 en side. If\u00f8lge den tidligere n\u00e6vnte algoritme skal vi hente en post mere end n\u00f8dvendigt. Hvis denne \"+1\" ikke er indregnet p\u00e5 serveren, skal vi for den f\u00f8rste side anmode om poster fra 1 til 51, for den anden fra 51 til 101 osv. Hvis vi angiver side st\u00f8rrelsen til 51 og \u00f8ger pageIndex, vil den anden side returnere fra 52 til 102 osv. Derfor er den eneste m\u00e5de at implementere en knap til at g\u00e5 til n\u00e6ste side ordentligt i f\u00f8rste mulighed, at indregne en \"overskydende\" post p\u00e5 serveren, hvilket vil v\u00e6re en meget utydelig detalje.<\/li>\n<li>Den tredje mulighed giver overhovedet ikke mening, da der for at udf\u00f8re foresp\u00f8rgsler i de fleste databaser stadig skal videregives antallet og ikke indekset for den sidste post. Lad os sige, at det at tr\u00e6kke startIndex fra endIndex og en grundl\u00e6ggende aritmetisk operation er simpelthen, men det er un\u00f8dvendigt her.<\/li>\n<\/ul>\n<p>\nNu b\u00f8r vi beskrive ulemperne ved at implementere paging via \"offset + antal\":<\/p>\n<ul>\n<li>At hente hver n\u00e6ste side vil v\u00e6re dyrere og langsommere end den forrige, fordi databasen stadig skal g\u00e5 gennem alle poster \"fra begyndelsen\" i henhold til s\u00f8ge- og sorteringskriterierne, hvorefter den stopper ved det n\u00f8dvendige segment.<\/li>\n<li>Ikke alle DBMS kan st\u00f8tte denne tilgang.<\/li>\n<\/ul>\n<p>\nDer er alternativer, men de er heller ikke perfekte. Den f\u00f8rste af disse tilgange kaldes \"keyset paging\" eller \"seek method\" og g\u00e5r ud p\u00e5 f\u00f8lgende: efter at have modtaget et parti kan man huske v\u00e6rdierne for felterne i den sidste post p\u00e5 siden og derefter bruge dem til at f\u00e5 n\u00e6ste parti. For eksempel udf\u00f8rte vi en s\u00e5dan foresp\u00f8rgsel:<\/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>\nOg i den seneste post fik vi v\u00e6rdien af ordredatoen '2014-06-29'. S\u00e5 for at hente den n\u00e6ste side kan man fors\u00f8ge at udf\u00f8re f\u00f8lgende:<\/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>\nProblemet er, at OrderDate ikke er et unikt felt, og betingelsen angivet ovenfor vil sandsynligvis udelade mange n\u00f8dvendige r\u00e6kker. For at tilf\u00f8je entydighed til denne foresp\u00f8rgsel skal der tilf\u00f8jes et unikt felt til betingelsen (antag at 75074 er den sidste v\u00e6rdi af den prim\u00e6re n\u00f8gle fra den f\u00f8rste batch):<\/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>\nDenne mulighed vil fungere korrekt, men generelt vil det v\u00e6re sv\u00e6rt at optimere, da betingelsen indeholder operatoren OR. Hvis v\u00e6rdien af den prim\u00e6re n\u00f8gle stiger med OrderDate, kan betingelsen forenkles ved kun at beholde filteret baseret p\u00e5 SalesOrderID. Men hvis der ikke er en streng korrelation mellem v\u00e6rdierne for den prim\u00e6re n\u00f8gle og feltet, som resultatet er sorteret efter, vil det i de fleste DBMS'er v\u00e6re umuligt at undg\u00e5 denne OR. Et kendt undtagelse er PostgreSQL, hvor sammenligning af tuples underst\u00f8ttes fuldt ud, og ovenst\u00e5ende betingelse kan skrives som 'WHERE (OrderDate, SalesOrderID) &lt; (&#039;2014-06-29&#039;, 75074)&#039;. Hvis der er en sammensat n\u00f8gle med disse to felter, b\u00f8r en s\u00e5dan foresp\u00f8rgsel v\u00e6re forholdsvis let.<\/p>\n<p>En anden alternativ tilgang kan ses for eksempel i <noindex><a rel=\"nofollow\" href=\"https:\/\/www.elastic.co\/guide\/en\/elasticsearch\/reference\/6.8\/search-request-scroll.html\">ElasticSearch scroll API<\/a><\/noindex> eller <noindex><a rel=\"nofollow\" href=\"https:\/\/medium.com\/@gary.strange\/understanding-cosmosdb-continuation-tokens-hasmoreresults-and-connectionpolicy-requesttimeouts-3ed1fadfa81d\">Cosmos DB<\/a><\/noindex> \u2014 n\u00e5r foresp\u00f8rgslen udover data returnerer en s\u00e6rlig identifikator, som kan bruges til at hente den n\u00e6ste batch af data. Hvis denne identifikator har en ubegribelig levetid (som i Cosmos DB), s\u00e5 er det en fremragende m\u00e5de at implementere paging med sekventiel overgang mellem sider (den n\u00e6vnte variant #2 ovenfor). Dens mulige ulemper: underst\u00f8ttes ikke i alle DBMS; den opn\u00e5ede identifikator for den n\u00e6ste batch kan have en begr\u00e6nset levetid, hvilket generelt set ikke passer til implementeringen af brugerinteraktion (som f.eks. ElasticSearch scroll API).<\/p>\n<h2>Sv\u00e6r filtrering<\/h2>\n<p>\nLad os komplicere opgaven yderligere. Antag, at der er krav om at implementere det s\u00e5kaldte facetterede s\u00f8gning, som mange er bekendt med fra internetbutikker. De tidligere n\u00e6vnte eksempler baseret p\u00e5 bestillingstabellen er ikke s\u00e6rlig illustrative i dette tilf\u00e6lde, s\u00e5 lad os skifte til Product-tabellen fra AdventureWorks-databasen:<\/p>\n<p><img decoding=\"async\" alt=\"Visning af s\u00f8geresultater og ydeevneproblemer\" src=\"\/wp-content\/uploads\/2020\/03\/070890a3cb79de3edc69b60b5300efe7.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nHvad er ideen bag facetteret s\u00f8gning? Det er, at for hver filterelement vises antallet af poster, der opfylder dette kriterium. <i>under hensyntagen til de valgte filtre i alle andre kategorier.<\/i>.<\/p>\n<p>For eksempel, hvis vi v\u00e6lger kategorien Bikes og farven Black i dette eksempel, vil tabellen kun vise cykler i sort, men samtidig:<\/p>\n<ul>\n<li>For hver kriterie i gruppen 'Categories' vises antallet af produkter i denne kategori i sort.<\/li>\n<li>For hver kriterie i gruppen 'Colors' vises antallet af cykler i denne farve.<\/li>\n<\/ul>\n<p>\nHer er et eksempel p\u00e5 resultatet for s\u00e5danne betingelser:<\/p>\n<p><img decoding=\"async\" alt=\"Visning af s\u00f8geresultater og ydeevneproblemer\" src=\"\/wp-content\/uploads\/2020\/03\/7c8ec4d8b427f1f59f0129afebe74b08.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nHvis vi yderligere markerer kategorien 'Clothing', vil tabellen ogs\u00e5 vise t\u00f8j i sort, der er tilg\u00e6ngeligt. Antallet af produkter i sort i sektionen 'Color' vil ogs\u00e5 blive genberegnet i henhold til de nye betingelser, mens der ikke vil ske nogen \u00e6ndringer i sektionen 'Categories'... Jeg h\u00e5ber, at disse eksempler er tilstr\u00e6kkelige til at forst\u00e5 den velkendte algoritme bag facetteret s\u00f8gning.<\/p>\n<p>Lad os nu forestille os, hvordan dette kan implementeres i en relationel database. Hver kriteriegruppe, s\u00e5som Category og Color, vil kr\u00e6ve en separat foresp\u00f8rgsel:<\/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=\"Visning af s\u00f8geresultater og ydeevneproblemer\" 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=\"Visning af s\u00f8geresultater og ydeevneproblemer\" src=\"\/wp-content\/uploads\/2020\/03\/82e1e6ab0fae683d383e3fecf4e3a15a.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nHvad er galt med denne l\u00f8sning? Det er meget simpelt \u2014 den skalerer d\u00e5rligt. Hver filtersektion kr\u00e6ver en separat foresp\u00f8rgsel for at t\u00e6lle m\u00e6ngderne, og disse foresp\u00f8rgsler er ikke de letteste. I internetbutikker kan der i nogle kategorier v\u00e6re flere dusin filtersektioner, hvilket kan vise sig at v\u00e6re et seri\u00f8st problem for ydeevnen.<\/p>\n<p>Normalt efter disse udsagn bliver jeg pr\u00e6senteret for nogle l\u00f8sninger, nemlig:<\/p>\n<ul>\n<li>Flet alle antalberegninger i \u00e9n foresp\u00f8rgsel. Teknisk set er dette muligt ved hj\u00e6lp af n\u00f8gleordet UNION, men det vil ikke g\u00f8re meget for ydeevnen \u2013 databasen skal stadig udf\u00f8re hver af fragmenterne \"fra bunden\".<\/li>\n<li>Cache antal. Dette bliver i det v\u00e6sentlige foresl\u00e5et hver gang, jeg beskriver problemet. Det er dog generelt set umuligt. Antag, at vi har 10 \"facetter\", hvor hver har 5 v\u00e6rdier. Dette er en meget \"beskedne\" situation i forhold til, hvad man kan se i de samme webshops. Valget af et enkelt facettelement p\u00e5virker antallet i de 9 andre; med andre ord kan antallet variere for hver kombination af kriterier. I vores eksempel er der i alt 50 kriterier, som brugeren kan v\u00e6lge, hvilket f\u00f8rer til 250 mulige kombinationer. At udfylde et s\u00e5dant datast\u00f8rrelse vil ikke v\u00e6re muligt med hverken hukommelse eller tid. Man kunne indvende, at ikke alle kombinationer er realistiske, og brugeren v\u00e6lger sj\u00e6ldent mere end 5-10 kriterier. Ja, man kan implementere lazy loading og cachen antal kun for det, der nogensinde er blevet valgt, men jo flere valgmuligheder der er, desto mindre effektiv vil den cache v\u00e6re, og jo mere udtalte problemer med responstider vil der v\u00e6re (is\u00e6r hvis datas\u00e6ttet \u00e6ndres regelm\u00e6ssigt).<\/li>\n<\/ul>\n<p>\nHeldigvis har denne type opgave l\u00e6nge haft tilstr\u00e6kkeligt effektive l\u00f8sninger, der fungerer forudsigeligt p\u00e5 store datam\u00e6ngder. For hver af disse muligheder giver det mening at opdele genberegning af facetter og hentning af resultatsiden i to parallelle anmodninger til serveren og organisere brugergr\u00e6nsefladen p\u00e5 en m\u00e5de, s\u00e5 datal\u00e6sning af facetter \"ikke forstyrrer\" visningen af s\u00f8geresultaterne.<\/p>\n<ul>\n<li>Kald for en fuld genberegning af \"facetter\" s\u00e5 sj\u00e6ldent som muligt. For eksempel, genberegn ikke alt ved hver \u00e6ndring af s\u00f8gekriterier, men find i stedet det samlede antal resultater, der stemmer overens med de nuv\u00e6rende betingelser, og tilbyd brugeren at vise dem \u2014 \"1425 poster fundet, vise?\" Brugeren kan enten forts\u00e6tte med at \u00e6ndre s\u00f8gebetingelserne eller trykke p\u00e5 knappen \"vis\". Kun i det andet tilf\u00e6lde vil alle foresp\u00f8rgsler til at hente resultater og genberegning af antal p\u00e5 alle \"facetter\" blive udf\u00f8rt. Det er ikke sv\u00e6rt at bem\u00e6rke, at man skal h\u00e5ndtere foresp\u00f8rgslen om at f\u00e5 det samlede antal resultater og optimere det. Denne metode kan ses i mange sm\u00e5 webshops. Det er tydeligt, at det ikke er en universall\u00f8sning p\u00e5 dette problem, men i simple tilf\u00e6lde kan det v\u00e6re et rimeligt kompromis.<\/li>\n<li>Brug en s\u00f8gemaskine til at finde resultater og t\u00e6lle facetter, s\u00e5som Solr, ElasticSearch, Sphinx og andre. Alle disse er designet til at opbygge \"facetter\" og g\u00f8r det ret effektivt p\u00e5 grund af det inverterede indeks. Hvordan s\u00f8gemaskiner fungerer, hvorfor de i s\u00e5danne tilf\u00e6lde er mere effektive end generiske databaser, hvilke praksisser og faldgruber der findes \u2014 det er emnet for en separat artikel. Her vil jeg dog p\u00e5pege, at en s\u00f8gemaskine ikke kan erstatte det prim\u00e6re datalager, men bruges som et supplement: alle \u00e6ndringer i den prim\u00e6re database, som er relevante for s\u00f8gningen, synkroniseres i s\u00f8geindekset; s\u00f8gemekanismen interagerer normalt kun med s\u00f8gemaskinen og henvender sig ikke til den prim\u00e6re database. Et af de vigtigste punkter her er, hvordan man organiserer denne synkronisering p\u00e5lideligt. Det hele afh\u00e6nger af kravene til \"reaktionstid\". Hvis tiden mellem \u00e6ndringen i den prim\u00e6re database og dens \"manifestation\" i s\u00f8gningen ikke er kritisk, kan man lave en service, der hver par minutter s\u00f8ger efter nyligt \u00e6ndrede poster og indekserer dem. Hvis den mindst mulige reaktionstid er n\u00f8dvendig, kan man implementere noget som <noindex><a rel=\"nofollow\" href=\"https:\/\/microservices.io\/patterns\/data\/transactional-outbox.html\">transactional outbox<\/a><\/noindex> til at sende opdateringer til s\u00f8getjenesten.<\/li>\n<\/ul>\n<p><\/p>\n<h2>Konklusioner<\/h2>\n<p><\/p>\n<ol>\n<li>Implementeringen af paging p\u00e5 serversiden er en alvorlig kompleksitet, og det giver kun mening at anvende det for hurtigt voksende eller bare store datam\u00e6ngder. Hvordan man vurderer, hvad der er \"stort\" eller \"hurtigt voksende\" \u2014 der er ingen absolut pr\u00e6cis opskrift, men jeg ville f\u00f8lge denne tilgang:\n<ul>\n<li>Hvis det at hente den fulde datakollektion under hensyntagen til serverens behandlingstid og netv\u00e6rksoverf\u00f8rsel normalt holder sig inden for ydeevnekravene, giver det ikke mening at implementere paging p\u00e5 serversiden.<\/li>\n<li>Der kan v\u00e6re situationer, hvor der i den n\u00e6rmeste fremtid ikke er forventet problemer med ydeevnen, da der er f\u00e5 data, men datakollektionen vokser konstant. Hvis en bestemt datam\u00e6ngde i perspektiv potentielt kan stoppe med at opfylde det forrige punkt \u2014 er det bedre at inkludere paging fra starten.<\/li>\n<\/ul>\n<\/li>\n<li>Hvis der fra forretningens side ikke er et strengt krav til at vise det samlede antal resultater eller til at vise sidetallene, og samtidig er der ikke en s\u00f8gemaskine i dit system \u2014 er det bedre ikke at implementere disse ting og overveje mulighed #2.<\/li>\n<li>Hvis der er et klart krav om faceteret s\u00f8gning, har du to muligheder for ikke at g\u00e5 p\u00e5 kompromis med ydeevnen:\n<ul>\n<li>Ikke at genberegne alle antal ved hver \u00e6ndring af s\u00f8gekriterierne.<\/li>\n<li>Brug s\u00f8gemaskiner som Solr, ElasticSearch, Sphinx og andre. Men det skal forst\u00e5s, at det ikke kan erstatte den prim\u00e6re database og skal bruges som et supplement til det prim\u00e6re lager til at l\u00f8se s\u00f8geopgaver. <\/li>\n<\/ul>\n<\/li>\n<li>Desuden giver det ogs\u00e5 mening i tilf\u00e6lde af faceteret s\u00f8gning at opdele modtagelsen af resultatsiden og antalsberegningen i to parallelle foresp\u00f8rgsler. Antalsberegningen kan tage l\u00e6ngere tid end at hente resultaterne, mens resultaterne er vigtigere for brugeren.<\/li>\n<li>Hvis du bruger en SQL-database til s\u00f8gning, skal enhver kode\u00e6ndring relateret til denne del testes grundigt med hensyn til ydeevne p\u00e5 det relevante datavolumen (der overstiger volumen i den \"live\" database). Det anbefales ogs\u00e5 at bruge overv\u00e5gning af foresp\u00f8rgslernes behandlingstid p\u00e5 alle databaser, og is\u00e6r \u2014 p\u00e5 den \"live\". Selv hvis alt var godt i udviklingsfasen med foresp\u00f8rgselsplanerne, kan situationen \u00e6ndre sig m\u00e6rkbart med stigende datavolumen.<\/li>\n<\/ol>\n<p>Kilde: <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.3 - 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\/da\/blog\/administrirovanie\/vyvod-rezultatov-poiska-i-problemy-s-proizvoditelnostyu\" \/>\n\t\t<meta name=\"generator\" content=\"All in One SEO (AIOSEO) 5.0.3\" \/>\n\t\t<meta property=\"og:locale\" content=\"da_DK\" \/>\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\/da\/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\udd47Visning af s\u00f8geresultater og ydeevneproblemer | ProHoster","description":"En af de almindelige scenarier i alle de applikationer, vi kender, er at s\u00f8ge efter data baseret p\u00e5 bestemte kriterier og vise dem i et letl\u00e6seligt format.","canonical_url":"https:\/\/prohoster.info\/da\/blog\/administrirovanie\/vyvod-rezultatov-poiska-i-problemy-s-proizvoditelnostyu","robots":"max-image-preview:large","keywords":"","webmasterTools":{"miscellaneous":""},"schema":null,"og:locale":"da_DK","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\/da\/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\/da\/wp-json\/wp\/v2\/posts\/75530","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/prohoster.info\/da\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/prohoster.info\/da\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/prohoster.info\/da\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/prohoster.info\/da\/wp-json\/wp\/v2\/comments?post=75530"}],"version-history":[{"count":0,"href":"https:\/\/prohoster.info\/da\/wp-json\/wp\/v2\/posts\/75530\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/prohoster.info\/da\/wp-json\/wp\/v2\/media\/75531"}],"wp:attachment":[{"href":"https:\/\/prohoster.info\/da\/wp-json\/wp\/v2\/media?parent=75530"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/prohoster.info\/da\/wp-json\/wp\/v2\/categories?post=75530"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/prohoster.info\/da\/wp-json\/wp\/v2\/tags?post=75530"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}