Skip to content

Primary index queries

For the complete documentation index see: llms.txt

All documentation pages available in markdown.

The digest of every record in the Aerospike database is indexed in the primary index (PI) of its namespace. This enables query access to all the records in a namespace, and with the assistance of a set index, fast query access to all the records in a set.

The Java SDK and Python SDK tabs use Developer SDK AEL text where it can express the example; the other tabs build the same expression with their client’s Exp builder. See the AEL reference and Query records in the Developer SDK.

Foreground queries

Foreground queries are read-only. The examples below show bin projection, filter expressions, and a partitioned metadata-only count.

Insert data

The examples on this page use an ad-tech user profile set profiles in namespace test. Each record represents a user with an integer age, a string region, and a map segments keyed by segment ID. The map values are lists of [ttl_epoch, category, tier].

{
"uid": "user42",
"age": 28,
"region": "NA",
"segments": {
1001: [1704067200, "sports", "premium"],
1002: [1672531200, "travel", "standard"],
1003: [1711929600, "news", "basic"]
}
}

Enable the set index for the profiles set so queries get direct access to the records in the set, without needing to traverse the entire primary index:

Terminal window
Admin+> manage config namespace test set profiles param enable-index to true

Insert two sample records:

DataSet profiles = DataSet.of("test", "profiles");
Map<Long, List<Object>> segs1 = new HashMap<>();
segs1.put(1001L, List.of(1704067200L, "sports", "premium"));
segs1.put(1002L, List.of(1672531200L, "travel", "standard"));
segs1.put(1003L, List.of(1711929600L, "news", "basic"));
session.upsert(profiles.id("user42"))
.bin("uid").setTo("user42")
.bin("age").setTo(28)
.bin("region").setTo("NA")
.bin("segments").setTo(segs1)
.execute();
Map<Long, List<Object>> segs2 = new HashMap<>();
segs2.put(2001L, List.of(1711929600L, "finance", "premium"));
session.upsert(profiles.id("user99"))
.bin("uid").setTo("user99")
.bin("age").setTo(35)
.bin("region").setTo("EU")
.bin("segments").setTo(segs2)
.execute();

Query all records in a set

Query the profiles set and project only the uid and region bins. Because the set index is enabled, the server does not need to traverse the primary index skipping records in other sets. The set index grants this query with direct access to the records of the profiles set.

DataSet profiles = DataSet.of("test", "profiles");
RecordStream stream = session.query(profiles)
.readingOnlyBins("uid", "region")
.execute();
stream.forEach(result -> {
Record rec = result.recordOrThrow();
System.out.printf("uid=%s region=%s%n",
rec.getString("uid"), rec.getString("region"));
});
stream.close();

Query with a filter expression

Filter records using a filter expression. The expression evaluates metadata predicates first for fast-path elimination, then bin predicates. This example selects profiles updated within the last five minutes (since_update), in the "NA" region, where age >= 21, and uses operation projection to return the number of segments as a computed bin.

DataSet profiles = DataSet.of("test", "profiles");
long fiveMinMs = 5L * 60 * 1000;
RecordStream stream = session.query(profiles)
.where(
"$.timeSinceLastUpdate() < " + fiveMinMs
+ " and $.region == 'NA' and $.age >= 21")
.withHint(hint -> hint.allowScansWithWhere())
.bin("uid").get()
.bin("active_segments").selectFrom("$.segments:MAP.count()")
.execute();
stream.forEach(result -> {
Record rec = result.recordOrThrow();
System.out.printf("uid=%s active_segments=%d%n",
rec.getString("uid"), rec.getLong("active_segments"));
});
stream.close();

Query a single partition

Query partition 1123 for "NA" profiles with includeBinData = false to count records without transferring bin data. This pattern is a building block for parallel processing across multiple clients, where each client handles a range of partitions.

DataSet profiles = DataSet.of("test", "profiles");
int count = 0;
try (RecordStream stream = session.query(profiles)
.onPartition(1123)
.withNoBins()
.where("$.region == 'NA'")
.withHint(hint -> hint.allowScansWithWhere())
.execute()) {
while (stream.hasNext()) {
stream.next().recordOrThrow();
count++;
}
}
System.out.printf("Partition 1123 NA count: %d%n", count);

Background queries

Background queries modify records in place on the server. The example below uses a background ops query with a map operation to remove expired ad-tech segments.

Remove expired segments

Remove ad-tech segments whose ttl_epoch has passed. The segments map values are lists [ttl_epoch, category, tier]. Because lists compare element-by-element, removeByValueRange(begin=null, end=[cutoff_ts]) removes all entries whose value list starts with an expired timestamp. This operation is idempotent — re-running it removes nothing if no expired segments remain.

DataSet profiles = DataSet.of("test", "profiles");
long cutoffTs = 1704067200L; // 2024-01-01T00:00:00Z
ExecuteTask task = session.backgroundTask()
.update(profiles)
.where("$.segments:MAP.count() > 0")
.bin("segments").onMapValueRange(SpecialValue.NULL, List.of(cutoffTs)).remove()
.execute();
task.waitTillComplete();