Loading

try {
  http:get("/blog/60028669088/a-time-saver-on-multiple-query-executions")
} catch http:not-found {
  <p>
    404 NOT FOUND.
    Take me home 
  </p>
}
~
~
-- INSERT -- All 6, 17

A Time Saver on Multiple Query Executions

Posted 1 year ago

When executing a query with Zorba, it goes through the following steps:

  • Parser - An abstract syntax tree (AST) is created from the query
  • Translator - The AST is converted into a tree of expressions
  • Optimizer - The expressions are rewritten for faster execution
  • Code Generator - The expressions are converted to a tree of iterators (aka, query plan)
  • Runtime - The query plan is executed

If you run a query multiple times, you might want to perform the steps from parsing to code generation only once and directly execute your query plan for every new runtime. Zorba provides the ability to save a query plan into a file. You then later simply load the query plan and execute it:


There are two ways to compile and execute query plans: using Zorba command line interface (CLI) or the C++ API.

Using the Zorba CLI

Zorba 2.7 provides two new options for the CLI: —compile-plan and —execute-plan.

—compile-plan outputs the plan in binary format.

—execute-plan executes the query plan that was given as input query.

In the figure below we request the query plan of 1+1 and store it into test.xqc. Then we execute test.xqc directly.

$zorba -q 1+1 --compile-plan -o test.xqc
$zorba -q test.xqc -f --execute-plan
<?xml version="1.0" encoding="UTF-8"?>
2

Now let’s look at the performance gain that can be achieved by using this feature. REx is a parser generator written by Gunther Rademacher that supports many output formats including XQuery. In the code snippet below, we parse a small piece of XQuery in XQuery.

import module namespace p="XQueryV30" at "parser.xq";

let $tree := p:parse-XQuery('("red", "blue", "green")!string-length()')
return $tree

Let’s run it:

$zorba -q test-parser.xq -f -t
<?xml version="1.0" encoding="UTF-8"?>
<XQuery><Module><MainModule><Prolog/>...</MainModule></Module><EOF/></XQuery>
Number of executions = 1
Engine Startup Time     : 0.000 (user: 0.000) milliseconds
Average Compilation Time: 6625.271 (user: 6453.332) milliseconds
Average Execution Time  : 602.584 (user: 531.234) milliseconds
Average Loading Time    : 0.000 (user: 0.000) milliseconds
Average Unloading Time  : 0.852 (user: 0.809) milliseconds
Average Total Time      : 7237.826 (user: 6990.856) milliseconds

Engine Shutdown Time     : 0.025 (user: 0.019) milliseconds

Could be faster… As you can see, most of the time is spent in compilation: more than 6 seconds. Now let’s look at the query plan loading and execution

$zorba -q test-parser.xq -f --compile-plan -o test-parser.xqc
$zorba -q test-parser.xqc -f --execute-plan -t
<?xml version="1.0" encoding="UTF-8"?>
<XQuery><Module><MainModule><Prolog/>...</MainModule></Module><EOF/></XQuery>
Number of executions = 1
Engine Startup Time     : 0.000 (user: 0.000) milliseconds
Average Compilation Time: 510.884 (user: 435.228) milliseconds
Average Execution Time  : 290.257 (user: 258.943) milliseconds
Average Loading Time    : 0.000 (user: 0.000) milliseconds
Average Unloading Time  : 1.034 (user: 0.970) milliseconds
Average Total Time      : 813.945 (user: 705.542) milliseconds

Engine Shutdown Time     : 0.008 (user: 0.006) milliseconds

In the example above, the loading time of the query plan appears in the Compilation Time category. As you can see, the loading and execution of the query plan is more than 9 times faster. This can be a very useful optimization when runing your queries in production.

Using the C++ API

In the C++ API, the XQuery class has a method named saveExecutionPlan() that takes an output stream as parameter. This is where the query plan will be saved. The second parameter is the output format. It can be binary or XML.

The XQuery class has a corresponding method XQuery::loadExecutionPlan() that takes an input stream as parameter and loads it as a compiled query.

The code snippet below demonstrates how to use these two methods. We compile 1+1 and save the query plan into an output string stream. Then, we create a second query to load and execute the query plan previously saved.

int main(int argc, char* argv[]) 
{
//Startup Zorba
void* lStore = zorba::StoreManager::getStore();
Zorba* lZorba = Zorba::getInstance(lStore);
std::ostringstream lOut;

{
//Compile 1+1
XQuery_t lQuery = lZorba->compileQuery("1+1");

//Save the query plan
lQuery->saveExecutionPlan(lOut, ZORBA_USE_BINARY_ARCHIVE, SAVE_UNUSED_FUNCTIONS);
}

{
std::istringstream lIn(lOut.str());
XQuery_t lQuery = lZorba->createQuery();
//Load the query plan
lQuery->loadExecutionPlan(lIn);
//Execute the query
std::cout << lQuery << std::endl;
}

//Shutdown Zorba
lZorba->shutdown();
return 0;
}

That’s All Folks!

We hope that you find this feature useful for your applications built on top of Zorba. Feel free to send us feedback and question on our mailing-list
Happy Coding with Zorba!