Skip to content

Tutorial for Expression Indexes

For the complete documentation index see: llms.txt

All documentation pages available in markdown.

This tutorial introduces expression indexes in Aerospike Database. This tutorial assumes you are familiar with secondary indexes and expressions in Aerospike Database.

Overview

Expression indexes build sparse secondary indexes over computed values defined by an Aerospike expression. Instead of indexing every record or persisting derived bins, the index evaluates your expression and includes only the records that match.

In this tutorial, you will:

  • Create your first expression index
  • Run your first query against it

Why it matters

  • Simpler application code: push filter logic into the index; no extra bins or duplicated client filters.
  • Reduced memory footprint: indexes include only relevant records.
  • Total cost of ownership: reduced RAM and compute requirements lower infrastructure spend.
  • Faster queries: less to scan means lower latency.

When to use expression indexes

Use them when you would otherwise:

  • Persist a derived value solely to make it indexable.
  • Index every record even though only a subset are needed for business logic.
  • Re-implement the same filter logic across multiple clients or languages.

Get started

Prerequisites

Verify your server version using asinfo:

Terminal window
# Verify you’re running Aerospike 8.1.0+
asinfo -v build

Create expression, index, and query

  1. Create the expression.

    The following example creates the “adult users in target countries” expression. For this example, let’s assume an adult is someone 18 or older, and our target countries are Australia, Canada and Botswana.

    For this tutorial, we’ll generate the Base64 representation of the expression in the client and then use the asadm command-line tool to create the index. This approach separates application logic (defining the filter) from database administration (creating the index).

    import com.aerospike.client.sdk.exp.Expression;
    // Build an expression that indexes age only for adults in AU/CA/BW that are 18 or older
    // In AEL: when the customer is 18 or older and lives in a target country, their age;
    // otherwise `unknown`, which leaves the record out of the index
    String ael = "when ($.age >= 18 and ($.country == 'Australia'"
    + " or $.country == 'Canada' or $.country == 'Botswana')"
    + " => $.age, default => unknown)";
    // Get the base64 for asadm's `exp_base64`; the server compiles the AEL when it creates the index
    String b64 = Expression.fromServerCompiledFilter(ael).getBase64();
    System.out.println(b64);
    // Prints: ksyA2YJ3aGVuICgkLmFnZSA+PSAxOCBhbmQgKCQuY291bnRyeSA9PSAnQXVzdHJhbGlhJyBvciAkLmNvdW50cnkgPT0gJ0NhbmFkYScgb3IgJC5jb3VudHJ5ID09ICdCb3Rzd2FuYScpID0+ICQuYWdlLCBkZWZhdWx0ID0+IHVua25vd24p
    // The SDK can also create the index itself, from the AEL:
    // session.createIndex(DataSet.of("test", "cust_data"), "cust_index",
    // IndexType.INTEGER, IndexCollectionType.DEFAULT, ael)
    // .waitTillComplete();

    The client libraries print the same base64 string. The Java SDK and Python SDK print their AEL’s instead, which the server compiles to the same expression when it creates the index.

  2. Create the expression index.

    Use asadm or sindex-create through asinfo to create an integer secondary index on the expression. Both accept the integer key type in Database 8.2.0 and later. Use asadm for interactive work and asinfo for automation. Copy the full Base64 string from the previous step’s output and use it in one of the following commands.

    asadm
    asadm -e "enable; manage sindex create integer cust_index ns test set cust_data exp_base64 lHuTEJMEk1ECo2FnZRKUEZMBk1EDp2NvdW50cnmqA0F1c3RyYWxpYZMBk1EDp2NvdW50cnmnA0NhbmFkYZMBk1EDp2NvdW50cnmpA0JvdHN3YW5hk1ECo2FnZZEA"
    asinfo
    asinfo -v 'sindex-create:namespace=test;set=cust_data;indexname=cust_index;indextype=default;exp=lHuTEJMEk1ECo2FnZRKUEZMBk1EDp2NvdW50cnmqA0F1c3RyYWxpYZMBk1EDp2NvdW50cnmnA0NhbmFkYZMBk1EDp2NvdW50cnmpA0JvdHN3YW5hk1ECo2FnZZEA;type=integer'
  3. Insert some test data.

    DataSet custData = DataSet.of("test", "cust_data");
    // Insert sample customers to demonstrate which records get indexed
    session.upsert(custData)
    .bins("name", "age", "country")
    .id(1).values("Tim", 31, "Australia")
    .id(2).values("Bob", 47, "Canada")
    .id(3).values("Jo", 15, "USA") // not indexed
    .id(4).values("Steven", 23, "Botswana")
    .id(5).values("Susan", 32, "Canada")
    .id(6).values("Jess", 17, "Botswana") // not indexed
    .id(7).values("Sam", 18, "USA") // not indexed
    .id(8).values("Alex", 47, "Canada")
    .id(9).values("Pam", 56, "Australia")
    .id(10).values("Vivek", 12, "India") // not indexed
    .id(11).values("Kiril", 22, "Sweden") // not indexed
    .id(12).values("Bill", 23, "UK") // not indexed
    .execute();
  4. Validate index entries.

    A single-node cluster shows 6 entries in the expression index (records 1, 2, 4, 5, 8, 9). sindex-stat reports each node’s own entries, replicas included, so on a larger cluster the nodes’ counts add up to 6 times the replication factor.

    Terminal window
    # Confirm only matching records (6) were indexed
    asinfo -v 'sindex-stat:namespace=test;indexname=cust_index' -l
    entries=6
    ...
  5. Query your Expression Index.

    Run a query to filter for ages 5 and above. Notice the results still only shows ages 18 and above.

    The Java SDK and Python SDK don’t name the index. Their where repeats the index’s expression, and the query optimizer serves the query from the index. Write the expression exactly as the index was defined: $.country in [...] builds a different expression from the or the index uses, and the query fails.

    // Query via the expression index to fetch records for customers that are 5 and older
    // A `where` on the index's expression is served by the index; `forIndex` names it.
    String ael = "when ($.age >= 18 and ($.country == 'Australia'"
    + " or $.country == 'Canada' or $.country == 'Botswana')"
    + " => $.age, default => unknown)";
    try (RecordStream rs = session.query(DataSet.of("test", "cust_data"))
    .where(ael + " >= 5")
    .withHint(hint -> hint.forIndex("cust_index"))
    .execute()) {
    rs.forEach(result -> System.out.println(result.recordOrThrow().bins));
    }

    Since we stored age, you can filter for records above 25 by simply modifying the query:

    // Query via the expression index to fetch records for customers that are 25 and older
    // A `where` on the index's expression is served by the index; `forIndex` names it.
    String ael = "when ($.age >= 18 and ($.country == 'Australia'"
    + " or $.country == 'Canada' or $.country == 'Botswana')"
    + " => $.age, default => unknown)";
    try (RecordStream rs = session.query(DataSet.of("test", "cust_data"))
    .where(ael + " >= 25")
    .withHint(hint -> hint.forIndex("cust_index"))
    .execute()) {
    rs.forEach(result -> System.out.println(result.recordOrThrow().bins));
    }
  6. That’s it. You’re done!