Anlegen von Indices

Wenn in eigenen Bundles OpenSearch Indices zur Persistierung von Daten (keine Einstellungen) verwendet werden sollen, dann empfiehlt es sich diese über den BPC Core anlegen zu lassen.

Dies bietet folgende Vorteile

  • es wird einiges an Boilerplate Code vermieden

  • einfache Festlegung der Index Settings und Mappings über eine JSON Datei

  • wenn der Index nicht vorhanden ist, dann wird er automatisch angelegt

  • die Indices werden mit einem Alias nach BPC Vorgaben erstellt

  • kann von der BPC Reindex Funktionalität aus aufgerufen werden

Vorgehen in Kürze

  • im resources Verzeichnis des eigenen Backend Bundles eine managed_indices.json anlegen

  • dessen Inhalt dem vom BPC Core zur Verfügung gestellten OpenSearchService bekannt geben

Verzeichnisstruktur

Diagram

Aufbau der JSON Datei

Im resources Verzeichnis des eigenen Backend Bundles eine managed_indices.json anlegen. Diese kann für mehrere anzulegenden Indices verwendet werden und muss folgenden Aufbau haben.

manages_indices.json
{
  "managedIndices": [
    {
      "name": "irgend-ein-index-name-1",
      "create_on_start": true|false,
      "hidden": true|false,
      "settings": { ... },
      "mappings": { ... }
    },
    {
      "name": "irgend-ein-index-name-2",
      "create_on_start": true|false,
      "hidden": true|false,
      "settings": { ... },
      "mappings": { ... }
    },
    ...
}
  • name - der Name des Index bzw. dies wird dann der Alias des Index.

  • create_on_start - Flag ob der Index gleich beim Start des Bundles (Registrierung) angelegt werden soll. Wenn nicht gesetzt, dann ist es wie wenn der Wert auf true gesetzt wurde.

  • hidden - Flag ob der Index im Deployment-Dialog und bei Combobox-Auswahlmöglichkeiten angezeigt werden soll. Wenn nicht gesetzt, dann ist es wie wenn der Wert auf false gesetzt wurde.

  • settings - die Settings eines OpenSearch Index. Diese nur setzen, wenn man weiß, was man macht. Sind sie nicht gesetzt, dann werden beim Anlegen die festgelegten Werte aus der Core-Einstellung verwendet. "Core_IndexCreationSettings" verwendet.

  • mappings - die Mappings eines OpenSearch Index

Einfaches Beispiel

für einen Index mit dem Namen 'example-queries'.

manages_indices.json
{
  "managedIndices": [
    {
      "name": "example-queries",
      "mappings": {
        "properties": {
          "queryName": {
            "type": "keyword"
          },
          "query": {
            "type": "keyword"
          },
          "chartConfig": {
            "type": "text",
            "index": false
          }
        }
      }
    }
  ]
}

Registrierung

Im folgenden ExampleBaseModule.java Beispiel wird der OpenSearchService vom BPC Core geholt und in der Methode createIndices(OpenSearchService es) wird dann das managed_indices.json geladen und über den OpenSearchService registriert. Bei der Gelegenheit auch mal die vom OpenSearchService zur Verfügung gestellten Methoden anschauen.

ExampleBaseModule.java
package com.company.example;

import de.virtimo.bpc.api.ModuleManager;
import de.virtimo.bpc.api.exception.OpenSearchRelatedException;
import de.virtimo.bpc.api.service.OpenSearchService;
import de.virtimo.bpc.module.AbstractInstantiableModule;
import de.virtimo.bpc.module.JsonDefaultsUtil;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.FrameworkUtil;
import org.osgi.framework.ServiceReference;
import org.osgi.util.tracker.ServiceTracker;

import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

public abstract class ExampleBaseModule extends AbstractInstantiableModule {
    private static final Logger LOG = Logger.getLogger(ExampleBaseModule.class.getName());

    private ServiceTracker openSearchServiceTracker;
    private OpenSearchService openSearchService;

    public ExampleBaseModule(ModuleManager moduleManager) {
        super(moduleManager);
    }

    @Override
    public void setModuleBundle(Bundle moduleBundle) {
        super.setModuleBundle(moduleBundle);

        if (openSearchServiceTracker == null) {
            openSearchServiceTracker = new OpenSearchServiceTracker(moduleBundle.getBundleContext());
            openSearchServiceTracker.open();
        }
    }

    @Override
    public void destroy() {
        super.destroy();

        if (openSearchServiceTracker != null) {
            openSearchServiceTracker.close();
            openSearchServiceTracker = null;
        }
    }

    public synchronized OpenSearchService getOpenSearchService() {
        LOG.info("getOpenSearchService");
        if (openSearchService == null) {
            throw new IllegalStateException("OpenSearch service is not available at the moment.");
        }
        return openSearchService;
    }

    private synchronized void createIndices(OpenSearchService oss) throws OpenSearchRelatedException {
        LOG.info("createIndices oss=" + oss);
        Bundle bundle = FrameworkUtil.getBundle(this.getClass());
        Map<String, Object> managedIndicesConfigMap = JsonDefaultsUtil.loadJsonFileAsMap(bundle, "managed_indices.json");
        oss.prepareManagedIndices(managedIndicesConfigMap);
    }

    private class OpenSearchServiceTracker extends ServiceTracker {
        public OpenSearchServiceTracker(BundleContext context) {
            super(context, OpenSearchService.class.getName(), null);
        }

        @Override
        public Object addingService(ServiceReference reference) {
            openSearchService = (OpenSearchService) context.getService(reference);
            try {
                createIndices(openSearchService);
            } catch (OpenSearchRelatedException ex) {
                LOG.log(Level.SEVERE, "Failed to create the managed indices.", ex);
            }
            return openSearchService;
        }

        @Override
        public void removedService(ServiceReference reference, Object service) {
            openSearchService = null;
            context.ungetService(reference);
        }
    }
}

Schreiben von Dokumenten

Das BPC geht davon aus, dass Indices folgenden Aufbau beim Namen haben <aliasname>_<timestamp>. Und daß ein Alias für diesen Index gesetzt ist. Beim lesen/schreiben/löschen der Dokumente wird immer mit dem Alias und nie mit dem physischen Indexnamen gearbeitet.

Beim Schreiben von Dokumenten legt OpenSearch automatisch einen Index an (wenn nicht vorhanden) und schätzt die Feldtypen. Das ist problematisch, wenn zum Beispiel zur Laufzeit ein Index gelöscht wird. Beim nächsten Schreiben eines Dokumentes würde dann der Index falsch angelegt werden.

Möglichkeit 1

Um das zu umgehen, die Methoden index, delete, search, searchByQuery, etc. vom OpenSearchService verwenden anstatt der vom RestHighLevelClient.

Die Methoden haben die gleiche Signatur. Allerdings werden die tückischen RuntimeExceptions (unchecked exception) von OpenSearch abgefangen und durch OpenSearchRelatedException (checked exception) ersetzt. Beim index-Aufruf wird dann, wenn nicht vorhanden, der Index anhand der Daten aus der managed_indices.json angelegt.

Für den bulk-Aufruf gibt es leider noch keine Umsetzung, da hier erst das OpenSearch-Ticket #12958 umgesetzt sein muss.

Beispiel RestHighLevelClient (managed_indices.json wird nicht verwendet)

IndexRequest myIndexRequest = ...;
RestHighLevelClient restHighLevelClient = openSearchService.getClient();
restHighLevelClient.index(myIndexRequest, RequestOptions.DEFAULT);

Beispiel OpenSearchService (managed_indices.json wird verwendet)

IndexRequest myIndexRequest = ...;
openSearchService.index(myIndexRequest, RequestOptions.DEFAULT);

Möglichkeit 2

Eine Hilfsmethode einführen, die vor jeder Schreib-Aktion ausgeführt wird. Folgend eine Beispielmethode für einen Index mit dem Alias 'books'. Der OpenSearchService hält den Status von Indices vor und kann dann bei nicht Existenz den Index per managed_indices.json anlegen.

private static final String BOOKS_INDEX_ALIAS_NAME = "books";

private void createBooksIndexIfMissing() throws SystemException {
    try {
        BpcIndexState booksIndexState = oss.getIndexState(BOOKS_INDEX_ALIAS_NAME);
        booksIndexState.prepareUsing(new BpcIndexCreateCallable() {
            @Override
            public String createIndex(OpenSearchService oss) throws OpenSearchRelatedException, ServiceNotFoundException, ModuleNotFoundException {
                return oss.getManagedIndicesHandler().createManagedIndex(BOOKS_INDEX_ALIAS_NAME);
            }
        });
    } catch (Exception ex) {
        throw new SystemException(CoreErrorCode.UNEXPECTED, "Failed to prepare the books index.", ex);
    }
}

Keywords: