Entwurfsdokumentmanagement

Der skalierbare JSON-Datenspeicher für IBM Cloudant verfügt über mehrere Abfagemechanismen, die allesamt Indizes generieren, die separat von den Kerndaten erstellt und verwaltet werden.

Beitrag von Glynn Bird, Developer Advocate bei IBM Cloudant, glynn@cloudant.com.

Die Indexierung wird nicht unmittelbar ausgeführt, wenn ein Dokument gespeichert wird. Stattdessen wird die Indexierung für einen späteren Zeitpunkt geplant, um einen höheren, nicht blockierenden Schreibdurchsatz zu erzielen.

  • MapReduce-Ansichten sind Indizes zu einer Datei, deren Schlüssel/Wert-Paare in einem B-Tree gespeichert sind, damit sie einfacher nach Schlüssel oder Schlüsselbereich abgefragt werden können.
  • Suchindizes werden mithilfe von Apache Lucene erstellt, um eine Suche nach Text mit freiem Format, Facettierung und komplexe Ad-hoc-Abfragen zuzulassen.

Die IBM® Cloudant® for IBM Cloud®-Suchindizes und MapReduce-Ansichten werden durch Hinzufügen von Entwurfsdokumenten zu einer Datenbank konfiguriert. Entwurfsdokumente sind JSON-Dokumente, die die Anweisungen dazu enthalten, wie die Ansicht oder der Index erstellt werden soll. Einfaches Beispiel: Angenommen, Sie haben eine einfache Sammlung von Datendokumenten, ähnlich dem folgenden Beispiel.

Beispiel für ein einfaches Datendokument:

{
    "_id": "23966717-5A6F-E581-AF79-BB55D6BBB613",
    "_rev": "1-96daf2e7c7c0c277d0a63c49b57919bc",
    "doc_name": "Markdown Reference",
    "body": "Lorem Ipsum",
    "ts": 1422358827
}

Jedes Datendokument enthält einen Namen, einen Hauptteil und eine Zeitmarke. Sie erstellen eine MapReduce-Ansicht, um Ihre Dokumente nach Zeitmarke zu sortieren.

Sie können Ihre Dokumente nach Zeitmarke sortieren, indem Sie eine 'map'-Funktion erstellen.

Beispiel für eine Map-Funktion, die das Zeitmarkenfeld eines Dokuments zurückgibt, falls vorhanden:

function(doc) {
    if (doc.ts) {
        emit( doc.ts, null);
    }
}

Die Funktion gibt die Zeitmarke des Dokuments aus, sodass sie als Schlüssel für den Index genutzt werden kann. Da wir uns nicht für den Wert im Index interessieren, null wird ausgegeben. So wird ein zeitlich sortierter Index in der Dokumentgruppe bereitgestellt.

Wir nennen diese Ansicht by_ts und fügen sie in ein Entwurfsdokument ein, das fetch heißt.

Beispiel für ein Entwurfsdokument, das eine Ansicht mithilfe einer 'map'-Funktion definiert:

{
    "_id": "_design/fetch",
    "views": {
      "by_ts": {
        "map": "function(doc) {
          if (doc.ts) {
            emit( doc.ts, null);
          }
        }"
      }
    },
    "language": "javascript"
}

Der 'map'-Code wurde in eine JSON-kompatible Zeichenfolge umgewandelt und in ein Entwurfsdokument eingefügt.

Sobald das Entwurfsdokument gespeichert wurde, löst IBM Cloudant serverseitige Prozesse aus, um die Ansicht fetch/by_ts zu erstellen. Es erstellt diese Ansicht durch das Iterieren jedes Dokuments in der Datenbank und das anschließende Senden jedes Dokuments an die 'map'-Funktion von JavaScript. Die Funktion gibt das ausgegebene Schlüssel/Wert-Paar (key-value) zurück. Im weiteren Verlauf der Iteration wird jedes Schlüssel/Wert-Paar (key-value) in einem B-Tree-Index gespeichert. Nach dem erstmaligen Erstellen des Index werden bei allen nachfolgenden Indexierungsvorgängen nur neue und aktualisierte Dokumente einbezogen. Gelöschte Dokumente werden aus dem Index entfernt. Dieser zeitsparende Prozess wird als inkrementeller MapReduce-Vorgang bezeichnet, wie im folgenden Diagramm dargestellt:

Eine Datenbank wird mit 1000 Dokumenten erstellt. Ein Entwurfsdokument wird hinzugefügt, das die Erstellung einer oder mehrerer Ansichten auslöst. Die Ansicht wird asynchron aufgebaut, bis sie vollständig ist. Die Ankunft von 250 weiteren Dokumenten macht den Index wieder unvollständig. Die Ansicht wird automatisch im Hintergrund oder bei einer Abfrage erstellt.
Illustration der inkrementellen MapReduce

An dieser Stelle möchten wir Sie an folgende Punkte erinnern:

  • Die Erstellung eines Index vollzieht sich asynchron. IBM Cloudant bestätigt, dass das Entwurfsdokument gespeichert wurde. Um den Fortschritt der Indexerstellung zu prüfen, muss der IBM Cloudant-Endpunkt _active_tasks abgefragt werden.
  • Je mehr Daten vorhanden sind, desto länger dauert es, bis der Index bereit ist.
  • Während der ursprünglichen Indexerstellung werden alle an den Index gerichteten Abfragen blockiert.
  • Die Abfrage einer Ansicht löst die Zuordnung aller Dokumente aus, die nicht inkrementell indiziert wurden. Durch dieses Verfahren wird sichergestellt, dass die Ansicht der Daten aktuell ist. Ausnahmen von dieser Regel finden Sie in der folgenden Erläuterung des Parameters stale.

Mehrere Ansichten in demselben Entwurfsdokument

Wenn Sie verschiedene Ansichten in demselben Entwurfsdokument definieren, werden sie effektiv zum selben Zeitpunkt erstellt. Jedes Dokument wird nur einmal gelesen und durchläuft die 'map'-Funktion jeder Ansicht. Wenn Sie diesen Ansatz verwenden, beachten Sie, dass durch das Ändern eines Entwurfsdokuments alle vorhandenen MapReduce-Ansichten ungültig werden, die in dem Dokument definiert sind. Durch diesen Prozess werden die MapReduce-Ansichten auch dann ungültig, wenn einige der Ansichten unverändert bleiben.

Falls MapReduce-Ansichten unabhängig voneinander geändert werden müssen, platzieren Sie ihre Definitionen in verschiedene Entwurfsdokumente.

Dieses Verhalten gilt nicht für Lucene-Suchindizes. Sie können innerhalb desselben Entwurfsdokuments geändert werden, ohne dass andere, nicht geänderte Indizes in demselben Dokument ungültig werden.

Eine Datenbank wird mit 1000 Dokumenten erstellt. Es wird ein Entwurfsdokument hinzugefügt, das die Erstellung von 2 Ansichten und 2 Suchindizes auslöst. Die Ansicht wird asynchron aufgebaut, bis sie vollständig ist. Durch den Eingang einer zweiten Version des Entwurfsdokuments werden alle MapReduce-Ansichten und alle geänderten Suchindizes im Dokument ungültig.
Änderung der Version des Entwurfsdokuments

Änderungen an einem Entwurfsdokument verwalten

Angenommen, zu einem bestimmten Zeitpunkt in der Zukunft möchten Sie das Design der Ansicht ändern. Aber statt zum eigentlichen Zeitmarkenergebnis zurückzukehren, sind Sie nur an der Anzahl der Dokumente interessiert, die den Kriterien entsprechen. Um diese Anzahl zu erreichen, bleibt die 'map'-Funktion unverändert, Sie verwenden jedoch die reduce-Funktion _count.

Beispiel für ein Entwurfsdokument, das eine 'reduce'-Funktion verwendet:

{
    "_id": "_design/fetch",
    "_rev": "2-a2324c9e74a76d2a16179c56f5315dba",
    "views": {
        "by_ts": {
            "map": "function(doc) {
                if (doc.ts) {
                  emit( doc.ts, null);
                }
            }
        }",
        "reduce": "_count"
    },
    "language": "javascript"
}

Wenn dieses Entwurfsdokument gespeichert wird, macht IBM Cloudant den alten Index vollständig ungültig und startet die Erstellung eines völlig neuen Index, wobei jedes einzelne Dokument iteriert wird. Wie bei der ursprünglichen Erstellung hängt die dafür erforderliche Zeit von der Anzahl der Dokumente in der Datenbank ab. Zudem werden alle eingehenden Abfragen dieser Ansicht bis zur Fertigstellung blockiert.

Aber es gibt ein Problem...

Wenn eine Anwendung in Echtzeit auf diese Ansicht zugreift, entsteht möglicherweise ein Bereitstellungsdilemma:

  • Version 1 des Codes, die auf dem ursprünglichen Entwurfsdokument basiert, funktioniert unter Umständen nicht mehr, weil die alte Ansicht ungültig gemacht wurde.
  • Version 2 des Codes verwendet das neue Entwurfsdokument. Diese Version kann nicht sofort freigegeben werden, da die Erstellung der neuen Ansicht noch nicht abgeschlossen ist. Denken Sie daran, dass der Erstellungsprozess länger dauert, wenn die Datenbank viele Dokumente enthält.
  • Ein weniger augenfälliges Problem für den Code ist, dass Versionen 1 und 2 verschiedene Ergebnisdaten von der Ansicht erwarten: Version 1 erwartet eine Liste von passenden Dokumenten, während Version 2 eine durch die 'reduce'-Funktion bearbeitete Anzahl von Ergebnissen erwartet.

Änderungen an Entwurfsdokumenten koordinieren

Dieses Problem mit dem Änderungsmanagement können Sie auf zwei Arten angehen.

Versionierte Entwurfsdokumente

Eine Lösung ist, versionierte Entwurfsdokumentnamen zu verwenden:

  • Der Code wurde ursprünglich geschrieben, um eine Ansicht namens _design/fetchv1 zu verwenden.
  • Wenn Sie eine neue Version freigeben, erstellen Sie eine neue Ansicht namens _design/fetchv2 und fragen diese Ansicht ab, um sicherzustellen, dass sie erstellt wird.
  • IBM Cloudant fragt _active_tasks ab, bis die Erstellung des neuen Index abgeschlossen ist.
  • Jetzt können Sie den Code freigeben, der von der zweiten Ansicht abhängig ist.
  • Wir löschen _design/fetchv1, wenn wir sicher sind, dass es nicht mehr erforderlich ist.

Die Verwendung versionierter Entwurfsdokumente ist eine einfache Methode für das Änderungsmanagement Ihrer Entwurfsdokumente, vorausgesetzt, Sie denken daran, die älteren Versionen zu einem späteren Zeitpunkt zu entfernen.

Move and switch-Entwurfsdokumente

Eine andere Methode basiert darauf, dass IBM Cloudant erkennt, wenn zwei identische Entwurfsdokumente vorliegen, und keine Zeit und Ressourcen dafür verschwendet, bereits vorhandene Ansichten erneut zu erstellen. Mit anderen Worten: Wenn Sie von dem Entwurfsdokument _design/fetch ein exaktes Duplikat _design/fetch_OLD erstellen, funktionieren beide Endpunkte austauschbar, ohne eine erneute Indexierung auszulösen.

Führen Sie die folgenden Schritte aus, um zur neuen Ansicht zu wechseln:

  1. Erstellen Sie eine Duplikatkopie des Entwurfsdokuments, das Sie ändern möchten, indem Sie z. B. _OLD zum Namen hinzufügen: _design/fetch_OLD.
  2. Stellen Sie das neue oder "eingehende" Entwurfsdokument in die Datenbank ein, wobei Sie einen Namen mit dem Suffix _NEW: _design/fetch_NEW verwenden.
  3. Fragen Sie die Ansicht fetch_NEW ab, um sicherzustellen, dass sie erstellt wird.
  4. Fragen Sie den Endpunkt _active_tasks ab und warten Sie, bis die Indexerstellung abgeschlossen ist.
  5. Stellen Sie eine Duplikatkopie des neuen Entwurfsdokuments in _design/fetch ein.
  6. Löschen Sie das Entwurfsdokument _design/fetch_NEW.
  7. Löschen Sie das Entwurfsdokument _design/fetch_OLD.

Move and switch Tools

Das Node.js-Befehlszeilenscript couchmigrate automatisiert die Prozedur für das Move and switch. Es kann mit dem folgenden Befehl installiert werden:

npm install -g couchmigrate

Um das Skript couchmigrate zu verwenden, definieren Sie zunächst die URL der Instanz CouchDB/{{site. data.keyword.cloudant_short_notm }}, indem Sie eine Umgebungsvariable namens COUCH_URL setzen. Führen Sie den folgenden Befehl aus, um die URL für die IBM Cloudant-Instanz zu definieren:

export COUCH_URL=https://127.0.0.1:5984

Die URL muss mit https:// beginnen und kann Authentifizierungsdaten enthalten. Führen Sie den folgenden Befehl aus, um die URL für die IBM Cloudant-Instanz mit Authentifizierungsnachweisen zu definieren:

export COUCH_URL="https://$ACCOUNT:$PASSWORD@$HOST.cloudant.com"

Wenn Sie ein Entwurfsdokument im JSON-Format haben, das in einer Datei gespeichert ist, können Sie den Befehl 'migrate' ausführen.

In diesem Beispiel verwendet db gibt den Namen der zu ändernden Datenbank an, und dd gibt den Pfad zu der Entwurfsdokumentendatei an. Führen Sie den Befehl couchmigrate aus:

couchmigrate --db mydb --dd /path/to/my/dd.json

Das Script koordiniert die Prozedur Move and switch und wartet, bis die Ansicht erstellt ist, bevor es zurückkehrt. Falls das eingehende Entwurfsdokument dem etablierten entspricht, kehrt das Script praktisch sofort zurück.

Der Quellcode für das Script ist hier verfügbar: couchmigrate.

Parameter stale

Falls ein Index abgeschlossen wurde, aber neue Datensätze zur Datenbank hinzugefügt werden, wird das Update des Index im Hintergrund geplant. Der Status der Datenbank ist im folgenden Diagramm dargestellt:

Die Ansicht ist vollständig. Die Ankunft von 250 weiteren Dokumenten macht den Index wieder unvollständig.
Zur Aktualisierung vorgesehener Index

Beim Abfragen der Ansicht stehen die folgenden Auswahlmöglichkeiten zur Verfügung:

  • Beim Standardverhalten wird sichergestellt, dass der Index aktuell ist, d. h. er enthält die aktuellen Dokumente in der Datenbank, bevor die Antwort zurückgegeben wird. Wenn Sie die Ansicht abfragen, indiziert IBM Cloudant zuerst die 250 neuen Dokumente und gibt dann die Antwort zurück.
  • Alternativ können Sie den Parameter stale=ok zum API-Aufruf hinzufügen. Der Parameter veranlasst, dass die bereits indizierten Daten zurückgegeben und neue Aktualisierungen nicht berücksichtigt werden. return me the data that is already indexed. I don't care about the latest updates. Mit anderen Worten, wenn Sie die Ansicht mit stale=ok abfragen, gibt IBM Cloudant die Antwort sofort zurück, ohne zusätzliche Neuindexierung.
  • Eine zweite Alternative ist das Hinzufügen des Parameters stale=update_after zum API-Aufruf. Der Parameter veranlasst, dass die bereits indizierten Daten zurückgegeben und neue Aktualisierungen nicht berücksichtigt werden. return me the data that is already indexed, and then reindex any new documents. Mit anderen Worten, wenn Sie die Ansicht mit stale=update_after abfragen, gibt IBM Cloudant die Antwort sofort zurück und plant dann eine Hintergrundtask zum Indizieren der neuen Daten.

stale=ok oder stale=update_after hinzuzufügen, kann eine gute Methode sein, Antworten schneller von einer Ansicht zu bekommen, aber sie geht auf Kosten der Aktualität der Daten.

Im Standardverhalten wird die Arbeitslast gleichmäßig auf die Zugriffsknoten im IBM Cloudant-Cluster verteilt. Wenn Sie die alternativen Optionen stale=ok oder stale=update_after nutzen, wird unter Umständen eine Untergruppe von Clusterknoten bevorzugt bedient, um konsistente Ergebnisse aus einer sukzessive konsistenten Gruppe zurückzugeben. Der Parameter stale bietet keine perfekte Lösung für alle Anwendungsfälle. Er kann jedoch zeitgerechte Antworten zu sich schnell ändernden Datengruppen bereitstellen, wenn Ihre Anwendung nicht aktuelle Ergebnisse akzeptiert. Wenn die Änderungsrate Ihrer Daten gering ist, führt das Hinzufügen von stale=ok oder stale=update_after zu keinem Leistungsvorteil, sondern u.U. zu einer ungleichmäßiger verteilten Arbeitslast in größeren Clustern.

Vermeiden Sie nach Möglichkeit die Verwendung von stale=ok oder stale=update_after, da das Standardverhalten die aktuellsten Daten bereitstellt und sie im gesamten Cluster verteilt. Sie können eine Client-App darauf aufmerksam machen, dass eine umfangreiche Datenverarbeitungstask (z. B. eine regelmäßige Massenaktualisierung) in Bearbeitung ist, indem während dieser Zeit temporär zu stale=ok gewechselt wird. Anschließend kann die App zum Standardverhalten zurückkehren.

Obwohl die Option stale weiterhin verfügbar ist, müssen stattdessen die hilfreicheren Optionen stable und update verwendet werden. Weitere Informationen finden Sie im Abschnitt Auf eine veraltete Ansicht zugreifen.