Skip to main content

How to create secondary index in Spring Data Aerospike

· 3 min read

Secondary indexes are on a non-primary key, which allows you to model one-to-many relationships. [https://www.aerospike.com/docs/architecture/secondary-index.html](https://www.aerospike.com/docs/architecture/secondary-index.html) Secondary indexes are on a non-primary key, which allows you to model one-to-many relationships. https://www.aerospike.com/docs/architecture/secondary-index.html

spring-data-aerospike supports creating secondary indexes in Aerospike out of the box.

There are two ways to accomplish this task:

  1. Using AerospikeTemplate createIndex method; or

  2. Using @Indexed annotation placed over the field in your entity.

    Let’s dive into more details.

Note: Before continuing it is expected that your project has spring-data-aerospike already setup. Please check this guide to find out how to do it.

First approach — creating index via AerospikeTemplate

In this example we will create an index at startup of the application manually.

package com.example.demo.persistence.index;

import com.aerospike.client.query.IndexType;
import com.example.demo.persistence.simplecrud.MovieDocument;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.aerospike.IndexAlreadyExistsException;
import org.springframework.data.aerospike.core.AerospikeTemplate;

@Slf4j
@Configuration
public class AerospikeIndexConfiguration {

private static final String INDEX_NAME = "movie-rating-index";

@Bean
@ConditionalOnProperty(
value = "aerospike." + INDEX_NAME + ".create-on-startup",
havingValue = "true",
matchIfMissing = true)
public boolean createAerospikeIndex(AerospikeTemplate aerospikeTemplate) {
try {
aerospikeTemplate.createIndex(MovieDocument.class, INDEX_NAME, "rating", IndexType.NUMERIC);
log.info("Index {} was successfully created", INDEX_NAME);
} catch (IndexAlreadyExistsException e) {
log.info("Index {} already exists, skipped creating", INDEX_NAME);
}
return true;
}
}

Second approach — creating index via @Indexed annotation

Place @Indexed annotation over the field that you want to index in your entity and specify required types of the index. This will make spring-data-aerospike to auto-create specified secondary index in Aerospike on startup of your application.

Note:@Indexed annotation is not supported for the fields annotated with @Id, @Expiration or @Version annotations.

package com.example.demo.persistence.index;

import lombok.Value;
import org.springframework.data.aerospike.annotation.Indexed;
import org.springframework.data.aerospike.mapping.Document;
import org.springframework.data.annotation.Id;

import java.util.List;

import static com.aerospike.client.query.IndexCollectionType.DEFAULT;
import static com.aerospike.client.query.IndexCollectionType.LIST;
import static com.aerospike.client.query.IndexType.NUMERIC;
import static com.aerospike.client.query.IndexType.STRING;

@Value
@Document
public class IndexedDocument {

@Id
String key;

@Indexed(type = STRING, collectionType = DEFAULT)
String author;

@Indexed(type = NUMERIC, collectionType = DEFAULT)
int likes;

@Indexed(type = NUMERIC, collectionType = LIST)
List<Integer> options;
}

Testing

Verify indexes were created using the following tests:

package com.example.demo;

import com.aerospike.client.AerospikeClient;
import com.aerospike.client.Info;
import com.aerospike.client.cluster.Node;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;

import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import static org.assertj.core.api.Assertions.assertThat;

public class IndexTests extends DemoApplicationTests {

@Value("${embedded.aerospike.namespace}")
String namespace;

@Autowired
AerospikeClient client;


@Test
void verifyCustomIndexCreated() {
List<String> existingIndexes = getIndexes(client, namespace);

assertThat(existingIndexes).contains("movie-rating-index");
}

@Test
void verifyAnnotationBasedIndexesCreated() {
List<String> existingIndexes = getIndexes(client, namespace);

assertThat(existingIndexes)
.contains(
"IndexedDocument_author_string_default",
"IndexedDocument_likes_numeric_default",
"IndexedDocument_options_numeric_list");
}

// DO NOT USE THIS CODE IN PRODUCTION
private static List<String> getIndexes(AerospikeClient client, String namespace) {
Node node = client.getNodes()[0];
String response = Info.request(node, "sindex/" + namespace);
return Arrays.stream(response.split(";"))
.map(info -> {
Map<String, String> keyValue = Arrays.stream(info.split(":"))
.map(part -> {
String[] kvParts = part.split("=");
return Map.entry(kvParts[0], kvParts[1]);
})
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
return keyValue.get("indexname");
})
.collect(Collectors.toList());
}

}

Demo project is located on GitHub — https://github.com/aerospike-community/spring-data-aerospike-demo.