SQL execution steps

Detailed SQL execution analysis using “SQL execution steps” has become available starting in release 24.0.

SQL execution steps is fine-grained logging of the execution of a SQL-statement, including the execution plan, I/O activity and sleep activity, in the Invantive UniversalSQL environment where also an individual SQL statement executes in parallel, returning streaming data.

This article is inteded for professionals and enthusiasts who have a solid foundation in SQL and are looking to delve deeper into how SQL queries are executed, optimized, and analyzed within a sophisticated Universal’s SQL-engine environment, such as database administrators, developers and data analysts. Prior knowledge on SQL execution plans and the importance of analyzing query performance is recommended.

Simple Sample

When executing the following statement with two Exact Online-company selected:

select *
from   exactonlinerest..journals@eol

the SQL execution steps can be retrieved in real-time from the data dictionary. First step is to find the SQL statement ID using:

select ID
,      SQL_STATEMENT
from   SystemSqlArea@datadictionary
order
by     LAST_END_TIME desc

In this case, the ID is 59580a0d-2c72-4737-848e-7ce480bc310f. The SQL execution steps can be retrieved using:

select ID
,      PARENT_ID
,      DEPTH
,      DURATION_MS
,      DESCRIPTION
from   SystemSqlExecutionSteps@datadictionary
where  STATEMENT_ID = to_guid('59580a0d-2c72-4737-848e-7ce480bc310f')

such as:

The structure of SQL execution steps is a “tree”. In this case, the last row is the root of the tree and has ID 128.

More Complex Example

A more complex example is the execution of the SQL-statement:

select *
from   exactonlinerest..journals@eol jnl
join   TransactionLines@eol tle
on     tle.journalcode = jnl.code
and    tle.financialyear = 2024
where  jnl.code = '60'

which retrieves all financial transaction lines booked in 2024 for the general ledger 60 (Purchase) in a very slow way (it is better to use TransactionLinesIncremental in general for reporting purposes or for volumes exceeding few thousand entry lines). The UniversalSQL optimizer creates an efficient execution plan, using knowledge about the data model. This execution plan retrieves the data from the (slow) API using extensive filters. This can be seen in the SQL execution steps using:

select ID
,      PARENT_ID
,      DEPTH
,      DURATION_MS
,      ROWS_IN
,      ROWS_OUT
,      DESCRIPTION
from   SystemSqlExecutionSteps@datadictionary
where  STATEMENT_ID = to_guid('58d554fb-ff8e-4250-94dc-52dee71f9d2f')

as:

The “inner join by” with ID 177 is the parent of both 178 on Journals as well as 180 on TransactionLines.

Was the Query well Optimized?

There were only 4 API-calls needed to retrieve the data as can be established using:

select url
from   sessionios@datadictionary
where  SQL_EXECUTION_STEP_ID in (179, 181)

as:

The UniversalOptimizer has optimized the calls, since it discovered there were just few (as in ‘1’) journal with code 60. This filter is added to the query on TransactionLines as a filter:

...$filter=JournalCode eq '60' and FinancialYear eq 2024...

Sleeps during Processing

Besides session I/O’s, there can also be moment during processing when UniversalSQL deems a delay necessary, for instance due to a rate limiter on an API. Delays introduced by sleeps can be found in the table SystemSqlExecutionSleeps, such as:

select *
from   SystemSqlExecutionSleeps@datadictionary

as:

As sample of a rate limiter on Exact Online is the minutely limit of 60 API calls per division for apps not provided by Exact itself. This limit can be triggered by calling a relatively fast API continuously as in:

select /*+ http_disk_cache(false) http_memory_cache(false) */ *
from   transactionlines@eol
limit  5000

The execution steps are:

This executes over 80 API calls on Exact Online and takes 71 seconds to execute on a single company/partition. Each API call takes 200 ms, which causes the minutely limit to be reached within approximately 16 seconds. The UniversalSQL-driver then pauses for the rest of the minute after which 60 more API calls become available again.

The 211 ms sleep are introduced after the long rest since the first API call on Exact Online after the sleep took a little shorter (210 ms) compared to the average of 268 ms, and the long sleep was actually a little too short.

The most common message codes found are:

  • itgenpmr024: spinning between 0 and 100 ms (125 ms when parallel) on data consumer while waiting for lower nodes to produce data.
  • itgenpmr055: random sleep between 0 and 10 ms on data consumer to allow parallel threads to distribute peaks in CPU-load more evenly in time.
  • itgensrt018: slot-based rate limiter (see ).
  • itgenncr002: delay native platform calls on maximum parallel capacity of HTTP-based drivers across ALL partitions as defined by requests-parallel-max.
  • itgenase442: delay to wait for a free connection in the connection pool for ANSI SQL-based drivers.
  • itgenhbr135 and itgentpr096: pre-HTTP request delay when HTTP-based drivers signals excessive speed such as through a HTTP header or 429 error. This can happen despite slot-based rate limiters since there can be multiple independent processes or servers sharing the same API rate limit.
  • itgenhbr136: post-HTTP request delay when HTTP-based drivers signals excessive speed.
  • itgensql637: delay to reduce speed of INTERNETTABLE table since there is no work currently across all spiders.

There are dozens of other message codes used to signal delays, but these will cover most scenarios.

Sleeps Across Multiple Companies in Parallel

When retrieving 25.000 entries across five Exact Online companies, the default behaviour for parallelism is controlled by the setting of requests-parallel-max on the driver instance. The default value is 8 in most scenarios. This setting is available on all platform drivers that support partitioning such as Twinfield or Visma.net (before Visma Connect was introduced).

This allows the five Exact Online companies to be retrieved in parallel.
The sleep behaviour during execution is then controlled per company, since the minutely rate limit is per company. The column PARTITION signals for which partition a sleep is introduced in the execution.

Log SQL Execution to Disk

The SQL engine has settings to log the execution steps per SQL-statement to disk for analysis purposes. The following two SQL-engine properties configure the logging to disk:

  • invantive-sql-execution-profile-to-disk: boolean setting whether to log SQL execution steps to disk.
  • invantive-sql-execution-profile-disk-path: file name and path to append the SQL execution steps to.

The file will contain detailed SQL execution steps information in a NDJSON format. The file is updated at the end of the execution of a SQL statement.

The current values of SQL-engine properties can be retrieved through a query on driver attribute values, such as:

select code
,      value
from   SystemProviderAttributes@DataDictionary
where  code 
       in
       ( 'invantive-sql-execution-profile-to-disk'
       , 'invantive-sql-execution-profile-disk-path'
       )

Through environment variables these properties can be configured:

  • INVANTIVE_PROFILE_TO_DISK: configure value of invantive-sql-execution-profile-to-disk.
  • INVANTIVE_PROFILE_PATH: configure value of invantive-sql-execution-profile-disk-path.