2016-01-04 6 views
0

Прямо сейчас запрос занимает около 2 минут, прежде чем я внес некоторые изменения, потребовалось 3: 48 м.Оптимизация медленного запроса xquery

Документы xml берутся с веб-страниц, из-за которых они меняют каждый 5 м и предоставляют информацию о шинах в реальном времени.

Не могли бы вы помочь мне оптимизировать этот запрос?

xquery version "3.0"; 
declare namespace bus="http://docs.gijon.es/sw/busgijon.asmx"; 

declare function local:getNombreParada($numero) 
{ 
    for $parada in doc("http://datos.gijon.es/doc/transporte/busgijoninfo.xml")//paradas/bus:parada 
    where $numero=$parada/bus:idparada 
    return $parada/bus:descripcion 
}; 

declare function local:getBusesPorLinea($linea) 
{ 

    let $numero:=$linea 
    let $nBuses:=count(doc("http://datos.gijon.es/doc/transporte/busgijontr.xml")//bus:llegada[bus:idlinea=$numero]) 

    return 
    if($nBuses=0) 
    then(<p>No hay ningun bus en esta linea</p>) 
    else(
    <div> 
     <h2>Numero de buses funcionando en la linea {$numero} : {$nBuses}</h2> 

    <table class="table table-hover"> 
     <thead> 
      <tr> 
      <th>Parada</th> 
      <th>Minutos hasta la llegada</th> 
      </tr> 
     </thead> 
     <tbody> 
      { 
      for $l in doc("http://datos.gijon.es/doc/transporte/busgijontr.xml")//bus:llegada[bus:idlinea=$numero] 
       for $parada in doc("http://datos.gijon.es/doc/transporte/busgijoninfo.xml")//paradas/bus:parada[bus:idparada=$l/bus:idparada] 


      return <tr> 
         <td>{$parada/bus:descripcion}</td> 
         <td>{$l/bus:minutos}</td></tr> 
      } 
     </tbody> 
    </table> 

    </div> 
    ) 


}; 

local:getBusesPorLinea(1) 

PD: я бегу это в существование Db

+0

Просто примечание стороны, что сказал Майкл Кей еще самое непосредственное отношение: Старайтесь избегать '' // проблем с производительностью, если вы имеете. Это всегда должно вызвать полное сканирование всех потомков, и конкретный поиск (т. Е. С полным путем), безусловно, будет быстрее. – dirkk

+1

@ dirkk На самом деле это не так для eXist. Скорее обратное верно, т. Е. '//' может быть намного быстрее, чем использование полного пути из-за того, как работает индексация; Это предполагает, что вы сначала создали индексы ;-) – adamretter

+0

@adamretter Интересно, я этого не знал. Я также удивлен, потому что конкретный путь всегда имеет больше информации, чем простой оператор-потомок-сам, т. Е. Оптимизатор может (теоретически) переписать конкретный путь к '//', тогда как это невозможно по-другому. Но я буду воздерживаться от этого утверждения в будущем для общего процессора XQuery, похоже, что это справедливо только для BaseX. – dirkk

ответ

1

кэшируются документы? Я не эксперт, но ваш код, похоже, несколько раз обращается к одному документу. Это нормально, если вы уверены, что содержимое кэшируется в исполняемой среде. В противном случае я постараюсь объявить

declare variable $docinfo := doc("http://datos.gijon.es/doc/transporte/busgijoninfo.xml"); 
declare variable $doctr := doc("http://datos.gijon.es/doc/transporte/busgijontr.xml"); 

, чтобы убедиться, что файлы выбраны только один раз.

Вы также сканируете документы как минимум дважды для данных того же типа. Я хотел бы сделать, что когда-то:

declare variable $paradas := $docinfo//paradas; 
declare variable $llegadas := $doctr//bus:llegada; 

только тогда фильтр коллекции:

declare function local:getNombreParada($numero) 
{ 
    $paradas/bus:parada[bus:idparada = $numero]/bus:descripcion 
}; 

declare function local:getBusesPorLinea($linea) 
{ 
    let $numero:=$linea 
    let $llegadasNum:=$llegadas[bus:idlinea=$numero] 
    let $nBuses:=count($llegadasNum) 

    return 

    if($nBuses=0) 
    then(<p>No hay ningun bus en esta linea</p>) 
    else(
    <div> 
     <h2>Numero de buses funcionando en la linea {$numero} : {$nBuses}</h2> 

    <table class="table table-hover"> 
     <thead> 
      <tr> 
      <th>Parada</th> 
      <th>Minutos hasta la llegada</th> 
      </tr> 
     </thead> 
     <tbody> 
      { 
      for $l in $llegadasNum 
       for $parada in $paradas/bus:parada[bus:idparada=$l/bus:idparada] 
       return <tr> 
         <td>{$parada/bus:descripcion}</td> 
         <td>{$l/bus:minutos}</td></tr> 
      } 
     </tbody> 
    </table> 

    </div> 
    ) 
}; 

Может быть, это не намного быстрее, но я надеюсь, что это немного более удобным для чтения.

2

без смарта-оптимизации, это соединение выражения:

for $l in doc("a.xml")//bus:llegada[bus:idlinea=$numero] 
    for $parada in doc("b.xml")//paradas/bus:parada[bus:idparada=$l/bus:idparada] 
return <tr>...</tr> 

будет иметь квадратную производительность. Вы ничего нам не сообщили о размерах документа, но я начну искать.

Как вы относитесь к такой проблеме в среде XML-базы данных, как правило, создавая соответствующие индексы.

2

Прежде всего, лучший способ оптимизировать запросы в eXist - хранить XML локально и с индексами. Пожалуйста, используйте встроенную документацию для настройки индексов.

Однако ваш код страдает от повторной загрузки одних и тех же данных из сети. Давайте позаботимся об этой и другой проблеме, о вашем использовании XML-запросов в памяти, о другом узком месте оптимизации.

Важнейшим первым шагом является предоставление XML-запроса в локальной базе данных. Запросы узлов в базе данных быстрее и меньше памяти, чем запросы к узлам XML в памяти. (По крайней мере, это был тот случай с версиями до 2.2.)

Итак, вот способ кэширования данных локально, обновляя кеш после последнего обновления более 5 минут.

xquery version "3.0"; 

declare namespace bus="http://docs.gijon.es/sw/busgijon.asmx"; 

(: Store the XML data in the collection /db/busgijon/data :) 
declare variable $COL := "/db/busgijon/data"; 
declare variable $INFO-FILE := "busgijoninfo.xml"; 
declare variable $TR-FILE := "busgijontr.xml"; 

(: Fetch a page from cache or from web site, updating the cache :) 
declare function local:fetchPage($filename) { 
    (: If the page was fetched more than 5 minutes ago, refresh it :) 
    let $expire := current-dateTime() - xs:dayTimeDuration('PT5M') 
    let $page := doc($COL || "/" || $filename)/page 
    return if (exists($page)) 
     then if ($page/xs:dateTime(@timestamp) ge $expire) 
      then $page 
      else (update replace $page/* with doc("http://datos.gijon.es/doc/transporte/" || $filename)/* 
       , update value $page/@timestamp with current-dateTime() 
       , $page) 
     else doc(xmldb:store($COL, $filename, <page timestamp="{current-dateTime()}">{doc("http://datos.gijon.es/doc/transporte/" || $filename)/*}</page>))/page 
}; 

declare function local:getBusesPorLinea($linea) 
{ 
    (: Get the two pages from the database cache for querying :) 
    let $info := local:fetchPage($INFO-FILE)/bus:BusGijonInfo 
    let $tr := local:fetchPage($TR-FILE)/bus:BusGijonTr 

    let $numero:=$linea 
    let $nBuses:=count($tr//bus:llegada[bus:idlinea=$numero]) 

    return 
    if($nBuses=0) 
    then(<p>No hay ningun bus en esta linea</p>) 
    else(
    <div> 
     <h2>Numero de buses funcionando en la linea {$numero} : {$nBuses}</h2> 

    <table class="table table-hover"> 
     <thead> 
      <tr> 
      <th>Parada</th> 
      <th>Minutos hasta la llegada</th> 
      </tr> 
     </thead> 
     <tbody> 
      { 
      (: Loop through the TR page - fetched just once from cache :) 
      for $l in $tr//bus:llegada[bus:idlinea=$numero] 
       (: Loop through the Info page - fetched just once from cache :) 
       for $parada in $info//paradas/bus:parada[bus:idparada=$l/bus:idparada] 


      return <tr> 
         <td>{$parada/bus:descripcion}</td> 
         <td>{$l/bus:minutos}</td></tr> 
      } 
     </tbody> 
    </table> 

    </div> 
    ) 


}; 

local:getBusesPorLinea(1) 

Единственная часть, что я изменил в локальной: функция getBusesPorLinea была выборка два документа в верхней части из кэша и те, которые находятся на встроенных шлейфов.

Местный: функция fetchPage - это место, где происходит большая часть ускорения. Вот что он делает:

  • Установите срок действия до 5 минут в прошлом.
  • Попробуйте извлечь указанную страницу из кеша.
  • Если страница существует, сравните выбранную временную метку с меткой истечения срока годности.
  • Если метка времени страницы меньше 5 минут назад (больше, чем временная метка истечения срока действия), верните эту страницу.
  • Если временная метка страницы больше 5 минут назад, обновите ее, обновите содержимое страницы с обновленным документом, обновите временную метку страницы и верните новую страницу.
  • Если страница еще не существует, сохраните страницу в указанной коллекции с текущей меткой времени, возвращая элемент страницы.

Первый человек, получивший доступ к этому XQuery через 5 минут, будет иметь дополнительные 5-10 секунд после обновления кеша. Это позволяет кэшу быть пассивным, поэтому вам не нужно вручную обновлять его каждые пять минут.

Надеюсь, это поможет.

+0

Спасибо, я добавлю это в другое решение, предлагаемое CiaPan, и теперь он занимает примерно 2 секунды (с использованием предложения CiaPan требуется 4 с, и до этого он занимает 2 минуты, я не действительно знаю, почему разница настолько велика) –

+0

Если вы добавите введенные индексы в eXist-db и настройте свои предикаты, вы сможете получить этот запрос до 10 секунд. – adamretter

+1

@RobertoFernandez В 'for $ l' вы запрашиваете загрузку файла' busgijoninfo.xml' для сканирования элементов 'bus: parada'. Если документы не кэшируются, вы извлекаете файл столько раз, сколько в файле 'busgijontr.xml' содержатся элементы' bus: llegada', удовлетворяющие условию предиката '[bus: idlinea = $ numero]. Это означает около 30 загрузок файлов для '$ numero = 4', 60 для' $ numero = 1' или 65 для '$ numero = 20'. Возможно, поэтому одна выборка уменьшает время работы до ок. 1/30 от предыдущего значения. – CiaPan

Смежные вопросы