aster.Web Data / Docs Aster UI ↗

Aster Web Data / Operate

What makes Aster Web Data fast

Why a request through Aster Web Data costs about what the same query written by hand costs, on every database and with what each database adds.

Before you put a service in front of a production database, you want to know whether it will keep up. With Aster Web Data, a request costs about what the same query costs when a developer writes it by hand. Aster sends the database one statement for most reads, with the filter, the sort, the page and the related rows inside it, and the database's own indexes and planner do the work. Related rows come in that statement or in one more statement per level, never in one query per parent row. Aster's own work on a request is parsing it and planning it in memory against a model it built at start-up.

On every database

One statement per read

A read is one statement, and two when the client asks for a count. $filter, $orderby, $top and $skip are clauses of that statement, and a filter on related records, such as invoices/any(i: i/amount gt 500), is an EXISTS inside it. The count is a COUNT(*) with the same WHERE, run in the same snapshot as the page. From request to SQL shows both statements for a real request.

An $expand never sends one query per parent row. On PostgreSQL, MariaDB and SQLite the related rows of most tables are read in the parents' statement. Elsewhere, and wherever a navigation has no one-statement form, Aster reads the parents first and then each level of related rows in one statement, keyed by all the parents' keys on the page. A level with more than 8,192 parents is read in chunks of that size (limits.key_batch). The same batching serves a read whose related rows are on another database, and a virtual data source that accepts a batch of keys.

Several rows in one insert

A bulk insert sends its rows as one multi-row statement, in batches that stay within the database's limit on bound values, rather than one INSERT per row. In a deep insert, the children of one parent that have no children of their own go in one statement the same way. Oracle is the exception, and inserts each row with a statement of its own. A deep insert and a $batch atomic group each run in one transaction on one connection, and on PostgreSQL a whole deep insert or atomic group can be a single statement, as PostgreSQL below describes.

Filtering, sorting and paging in the database

Aster sends each condition to the database written so that an ordinary index on the column can serve it: columns are compared as they are stored, with no cast and no COLLATE. Every sort ends with the key, so pages are stable and an index on the sort column and the key serves the whole order. Values are bound as parameters, so the statement text depends only on the shape of the request.

Aster's own engine runs only what the database cannot, over rows the database has already filtered and paged where it can. It holds at most limits.residual_rows rows, 50,000 by default, and refuses a request that would need more. A large table is never filtered or sorted in Aster's memory.

Expensive requests refused early

Requests past a cost limit, such as an $expand nested too deep or an in list that is too long, are refused while the request is parsed and planned, before any SQL is sent. Each statement also runs under a timeout, and each request under a deadline. Limits lists every limit with its default.

Little work in Aster Web Data itself

When the database supports it

Each database has features that save a statement or a round trip, and Aster uses them where they exist. Each database's page gives the statements of every read and write shape.

PostgreSQL

PostgreSQL gives the conditions, such as the 48-column limit and tables with triggers.

MariaDB

MariaDB gives the statements of each write.

SQLite

SQLite gives the tables that read by key batches instead.

DuckDB

SQL Server

SQL Server gives the statements of each write.

Db2

Db2 gives the statements of each write.

Oracle

Oracle gives the statements of each write.

Indexes

Aster does not create indexes on your tables, and the statements above are only as fast as the indexes behind them. Index the columns your screens filter and sort by, the sort column with the key for a sorted page, and the foreign key of every navigation you expand. Indexes gives the rules with a table of requests and the index that serves each. Two databases add rules of their own: on PostgreSQL, a filter that wraps a column in an expression needs an index on the expression (PostgreSQL), and on MariaDB a descending sort needs its own index (MariaDB).

Checking your own requests

Every statement Aster sends starts with a fixed tag, such as /* aster read customers */, so your database's statement statistics and slow-query log show it under the table it reads, and you can run its own EXPLAIN on it. The server's plan-shape metrics show which kinds of request take the most time, and the explain header shows, for any one request, which parts ran in the database and which in Aster's engine. Finding the expensive requests shows where to look, and Observability how to turn the header on.