Automatic indexes
Automatic indexes (v3.3.0+)
- Switch the automatic mode on and Index Maker works on its own : it records the database queries of your APIs at regular intervals, asks the database with explain which ones use no index, and gives an index to the queries that are slow or frequent on tables that are big enough.
- Every index it decides on goes to two places : the database of the server that decided it (optional), and the migration script
am_index_maker, so every other environment creates the same index on its next deploy. - It is in API Maker + Extensions, in the Index Maker menu : the Automatic indexes panel next to the scanning periods.
The panel
- The switch turns the mode on and off for the account (an admin or a developer account has its own periods, script and settings).
- Scan now starts an automatic scanning period at once instead of waiting for the next one.
- Evaluate last period runs the evaluation of the last automatic period again.
- Script opens the entries of
am_index_maker: remove one, open the code, run the script on this server. - Run here runs the script on this server, like the play button of the migrations page.
- The panel shows the period recording right now, when the next one is due, the last evaluation with its decisions, and how many indexes were written and created.
What it does, step by step
- Record. A scanning period named
Automatic <date>records the database APIs of every instance forscanWindowMinutes. It costs what a scanning period costs : a few writes to the database of API Maker after each watched call, only while it records. - Explain. When the period ends, every recorded query shape is run through
explainon its database, like the index analysis of a scanning period. Only the shapes that use no index go on (includePartialIndexQueriesadds the ones that use part of an index). - Decide. For every shape, in the order of the queries that matter most (calls × time) :
- the collection is not in
excludedCollections; - the query is worth it : slower than
minAvgQueryTimeMSon average, or called at leastminQueryCounttimes ; - the table is big enough :
minTableRowsrows or more. The size comes from the catalog and a count bounded byminTableRows, so a big table is never counted to its end ; - the fields of the index : the equality fields first, then the
$infields, then one range or pattern field, at mostmaxFieldsPerIndex. A field of a type the database can not index (TEXT and BLOB on MySQL, json on PostgreSQL, CLOB on Oracle,varchar(max)on SQL Server...) is dropped ; - no existing index of the table starts with those fields, the script does not hold them already, and the table stays under
maxIndexesPerTable.
- the collection is not in
- Create. With
applyOnThisServer, the index is created on the database of this server right away, with the name Index Maker gives it. An index the database refuses stays out of the script. - Write. The definitions are written into the JSON list of
am_index_maker, and the script is marked "not executed" on this server so that every environment runs it on its next deploy. - Keep going. The next period starts
scanEveryMinutesafter the previous one ended. Old automatic periods are removed beyondkeepAutomaticPeriods, with their recorded queries.
Every decision is kept on the period : open it in the scanning periods grid, tab Automatic indexes, to see for each query shape what was decided and why (APPLIED, WRITTEN, SMALL_TABLE, NOT_WORTH_IT, COVERED_BY_EXISTING_INDEX, ALREADY_IN_SCRIPT, TOO_MANY_INDEXES, NO_INDEXABLE_FIELD, UNSUPPORTED_SHAPE, EXCLUDED_COLLECTION, TABLE_NOT_READABLE, FAILED).
Settings
- The settings are kept on the account (
settings.indexMaker), so they travel with the project in git. A number outside its limits is brought back inside them, never refused.
| Setting | Default | Limits | Meaning |
|---|---|---|---|
autoIndexEnabled |
false |
The master switch. Off, nothing runs on its own ; the script, the suggestions and the index form still work by hand. | |
applyOnThisServer |
true |
Besides the script, create the index on the database of this server as soon as it is decided. | |
scanEveryMinutes |
360 |
5 to 43200 | A new automatic period starts this long after the previous one ended. |
scanWindowMinutes |
30 |
1 to 1440 | How long an automatic period records. |
minTableRows |
10000 |
0 to 10⁹ | A table with fewer rows gets no index : a scan of a small table is cheap and every index costs on writes. |
minAvgQueryTimeMS |
100 |
0 to 600000 | A query shape slower than this, on average, gets an index. |
minQueryCount |
50 |
1 to 10⁸ | A query shape called at least this often during the period gets an index even when it is fast : the table grows. |
maxFieldsPerIndex |
3 |
1 to 8 | Fields of a compound index : the equality fields first, then the ranges, the rest is dropped. |
maxIndexesPerTable |
12 |
1 to 64 | A table that already carries this many indexes gets no more. |
includePartialIndexQueries |
false |
Also look at the shapes that use part of an index, not only the ones without any. | |
keepAutomaticPeriods |
3 |
1 to 50 | Older automatic periods, with their recorded queries, are removed. |
excludedCollections |
[] |
instance:database:collection patterns, * for any part. For a multi-tenant instance, the plain name of the instance. |
|
logInternalQueries |
false |
Also record the queries API Maker runs on its own (deep populate, unique checks). |
The migration script am_index_maker
- It is a normal migration script of the account, on the Databases migration page, created the first time the mode is switched on (or the first time an entry is added by hand). It can not be deleted while Index Maker uses it ; remove its entries from the Index Maker page instead.
- Index Maker only rewrites the JSON between the two markers. Edit the code outside them as you like, or add your own entries to the list (keep the JSON valid).
- Every environment that pulls the repository runs the script on deploy. An index which exists already is reported as
EXISTSand left alone, so the script can run again and again. A failed index is reported to the deploy after every other index of the list was created.
| Entry field | Meaning |
|---|---|
instance |
Instance name. crm::acme names the tenant acme of the multi-tenant instance crm. |
database |
Database name, as the account sees it (database name masking is applied). |
collection or table |
The table. PostgreSQL : public.orders, SQL Server : dbo.orders. |
name |
Index name. Missing : Index Maker builds one (see below). |
fields |
[{ "name": "status", "order": "ASC" }]. Orders : ASC, DESC, HASHED (MongoDB), TEXT (MongoDB) ; nullsFirst: true on PostgreSQL. |
unique |
A unique index. |
type |
MySQL : FULLTEXT, SPATIAL ; SQL Server : CLUSTERED, XML, SPATIAL ; PostgreSQL : hash, gin, gist, spgist, brin. Plain when missing. |
method |
MySQL : BTREE, HASH, RTREE. |
operation |
CREATE (default) or DROP (the name is enough). |
source, reason, addedAt, scanningPeriodId |
Who added the entry and why ; for people, not for the database. |
Index names
am_ix_<table>_<fields>_<hash>,am_ixu_for a unique index. The same fields give the same name in every environment, soEXISTSis found by name first and by fields second.- The name is cut to the shortest limit of the database : 100 characters on MongoDB, 64 on MySQL and MariaDB, 128 on SQL Server, 63 on PostgreSQL, 30 on Oracle. The hash keeps cut names apart.
By hand : suggestions and the index form
- Suggestions of a scanning period : select the shapes and click Add to script. Index Maker turns each shape into a definition the way the automatic mode does (without the thresholds : you chose them) and adds it to the script.
- The index form shows every field of the table with whether a plain index can hold it and why not, the indexes the table already has and its size. The name is built from the fields ; the switch Also write it to the migration script (on by default) adds the index to
am_index_makerafter creating it. - Index logs keep who created or dropped every index : the form (
UI), a suggestion (SUGGESTION), the automatic mode (AUTOMATIC), custom code (SYSTEM_API) or the script on deploy (MIGRATION_SCRIPT), with the reason and the scanning period.
From your code
- The same three operations are system APIs, for custom APIs, schedulers and your own migration scripts : createIndexes, dropIndexes and getIndexes. They are also HTTP APIs :
POST /api/system-api/user-path/create-indexes,/drop-indexes,/get-indexes. createIndexesanddropIndexeschange the database : the API Security Report lists them with the other sensitive system APIs, so give them only to the groups that need them.
Safe on every version
- The statements Index Maker runs are the plain
CREATE INDEXandDROP INDEXof each database, withoutIF NOT EXISTS(PostgreSQL 9.5+ only, MySQL never) : the existence check is made in code from the catalog, so the same script works on every version API Maker supports. - Identifiers are quoted on Oracle (
"INVENTORY"."am_ix_...") and bracketed on SQL Server ([dbo].[orders]), so tables with lower-case or mixed-case names work. - A field the database can not put in a plain index is
SKIPPEDbefore the database is asked, with the reason : TEXT and BLOB (a prefix is needed) and JSON on MySQL and MariaDB ;text,ntext,image,xml, the spatial types,(n)varchar(max),varbinary(max)and keys over 900 bytes on SQL Server ;json,xml, the geometric types,tsvectoron PostgreSQL ; LOBs, LONG, XMLTYPE and SDO_GEOMETRY on Oracle ; a primary key everywhere.
Performance
- Nothing changes for a request : the only cost is the recording of a scanning period while one runs, which is the cost Index Maker always had.
- The loop wakes once a minute in every process, but one process of the whole cluster does the work (a Redis claim of the minute), and its tick is one small read of the accounts with the mode on.
- The evaluation runs once per period, at its end, and waits for a quieter minute when the event loop of the process is busy. It reads the catalog of the tables, never the rows beyond
minTableRows.
👉 To install API Maker with extensions, follow API Maker + Extensions. For a license key, contact us at [email protected].