Automatic mode
Thresholds for table size, average time and number of calls, fields per index, indexes per table, excluded collections, how often and how long to record: settings of the account, kept in git.
API Maker
The framework for AI era
Extension · license key
Records the queries your APIs run, finds the ones that use no index, and creates the index: on its own, on every environment.
Index Maker is part of API Maker + Extensions. It records the database queries of your APIs, grouped by the shape of their condition, and asks your database with explain whether each one uses an index. Switch on the automatic mode and it starts its own scanning periods, gives an index to the slow or frequent queries of big tables, and writes every index into the migration script am_index_maker, which every environment runs on deploy.
Step through it, slow it down, or open the full canvas.
A period records on its own. Every six hours the loop starts a scanning period of thirty minutes. The queries of your APIs are grouped by the shape of their condition, with their calls and their time.
Every six hours the loop starts a scanning period of thirty minutes. The queries of your APIs are grouped by the shape of their condition, with their calls and their time.
When the period ends, every shape is run through explain with a recorded sample. The orders query reads the whole table.
Slow or frequent, on a table big enough, not covered by an existing index, under the limit of indexes per table. Every decision is kept with its reason.
A migration script that always exists and can not be deleted. Index Maker rewrites only the list between the markers; the code applies it.
The index is created on this server at once. Every other environment runs the script on its next deploy: an index which exists already is left alone, so the script can run again and again.
The orders query drops from 310 ms to 4 ms. One process of the cluster ticks once a minute; nothing is added to your requests.
Thresholds for table size, average time and number of calls, fields per index, indexes per table, excluded collections, how often and how long to record: settings of the account, kept in git.
am_index_maker holds every index as a JSON entry with who added it and why. Deploy the repository and the environment creates the same indexes. Add your own entries too.
status = "paid" and status = "new" are the same query. Paging, sorting and selected fields do not split a group either.
The check is the explain of your own database, run with a recorded sample of the query. Nothing is guessed.
WRITTEN, SMALL_TABLE, NOT_WORTH_IT, COVERED_BY_EXISTING_INDEX, TOO_MANY_INDEXES, NO_INDEXABLE_FIELD... each shape of a period shows what was decided and why.
Plain CREATE INDEX statements, the existence check made in code, quoted identifiers, names within the limit of each database, and fields no plain index can hold are skipped with the reason.
g.sys.system.createIndexes, dropIndexes and getIndexes work the same on all six databases, from custom APIs, schedulers and your own migration scripts.
Select the shapes without an index and add them to the script in one click. The index form shows every field with whether an index can hold it, the existing indexes and the size of the table.
Every index created or removed is logged with its table, fields, date and source: the form, a suggestion, the automatic mode, custom code or the script on deploy.
Orders grew to millions of rows. The automatic period of the night records the orders query on customer_id and status: 1,240 calls, 310 ms on average, no index. It passes the thresholds, so the index is created on the production database and written into am_index_maker. The staging and the development environments get it on their next deploy, and the query takes 4 ms in the morning.
// <am-index-maker-indexes>const INDEXES: T.IIndexMakerScriptEntry[] = [ { "instance": "mysql8", "database": "inventory", "collection": "orders", "name": "am_ix_orders_customer_id_status_3f9a1c", "fields": [{ "name": "customer_id" }, { "name": "status" }], "source": "AUTOMATIC", "reason": "1,240 calls, 310 ms on average, orders has 2,400,000 rows." }];// </am-index-maker-indexes>Index Maker rewrites only the list between the markers. The code of the script calls g.sys.system.createIndexes with ifNotExists, so it runs on every deploy without harm.
const results = await g.sys.system.createIndexes({ indexes: [{ instance: 'mysql8', database: 'inventory', table: 'orders', fields: [{ name: 'customer_id' }, { name: 'status', order: 'DESC' }], }],});// [{ status: 'CREATED', name: 'am_ix_orders_customer_id_status_3f9a1c', ... }]// the second time : [{ status: 'EXISTS', ... }]CREATED, EXISTS, SKIPPED (a field no plain index can hold, with the reason) or FAILED (the message of the database).
The shapes that use no index (part of one, if you ask), on a table with at least minTableRows rows, when the shape is slower than minAvgQueryTimeMS on average or was called at least minQueryCount times during the period. The fields are the equalities first, then the ranges, up to maxFieldsPerIndex. The defaults are 10,000 rows, 100 ms and 50 calls.
Through the migration script am_index_maker, kept in your repository like every other migration script. Every change marks it "not executed" on the server that made it, so every environment runs it on its next deploy. An index which exists already is reported as EXISTS and left alone.
Yes. Index Maker only rewrites the JSON list between its two markers. Edit the code outside them, or add your own entries to the list, including DROP entries. The script can not be deleted while Index Maker uses it; remove its entries from the Index Maker page instead.
Nothing per request. A scanning period costs a few writes to the database of API Maker after each watched call, while it records. The loop ticks once a minute in one process of the cluster, evaluates a period once when it ends, and waits for a quieter minute when the event loop is busy. Table sizes come from the catalog and a count bounded by the threshold.
MongoDB: unique, ascending, descending, hashed and text. MySQL and MariaDB: FULLTEXT, SPATIAL and UNIQUE, with BTREE, HASH or RTREE. SQL Server: clustered, nonclustered, XML and spatial, unique or not. PostgreSQL: btree, hash, gist, gin, spgist and brin, with NULLS FIRST or LAST. Oracle: unique, ASC and DESC.
Yes. Queries are kept per tenant, and an index goes to the database of that tenant: the entry names it as crm::acme.
Yes. The index list of a table, on the instances page, shows its indexes and creates and removes them, and the system APIs createIndexes, dropIndexes and getIndexes are part of API Maker.