Blog

How to create secondary index in Spring Data Aerospike

Secondary indexes are on a non-primary key, which allows you to model one-to-many relationships.

ana
Anastasiia Smirnova
January 16, 2021|3 min read

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.