Aljoscha Rittnerhttps://plume.atsuchan.page/@/beandev@write.tchncs.de/atom.xml2023-02-02T13:48:18.121063+00:00<![CDATA[Die tchncs.de Domains sind nicht zu erreichen]]>https://write.tchncs.de/~/BubbleBla/Die%20tchncs.de%20Domains%20sind%20nicht%20zu%20erreichen/2023-02-02T13:48:18.121063+00:00Aljoscha Rittnerhttps://write.tchncs.de/@/beandev/2023-02-02T13:48:18.121063+00:00<![CDATA[<p dir="auto">Schon heute Morgen bemerkte ich über social.tchncs.de Probleme. Über den Lauf des Tages wurden immer wieder Medien nicht angezeigt. Zwischenzeitig war auch die Domain gar nicht zu erreichen, ging dann aber wieder.</p>
<p dir="auto">Aber nun ist seit ein paar Stunden Schicht im Schacht.</p>
<p dir="auto">Laut https://dnsviz.net/d/social.tchncs.de/dnssec/ sind wohl die Zertifikate kaputt, was an einigen Stellen validiert und für Nutzer*innen bedeuten kann, dass man abgeschnitten wird:</p>
<p dir="auto"><img src="https://write.tchncs.de/static/media/83CD5840-9F98-1947-6389-D310536A1557.png" alt="Zertifikat abgelaufen"></p>
<p dir="auto">Hoffen wir mal, dass es bald wieder ok ist.</p>
<p dir="auto">Update:</p>
<p dir="auto">Also gestern Abend war das Zertifikat als unsicher eingestuft. Inzwischen ist die Zertifikatskette wieder valide und sicher. Aber für meine im Router eingetragene DNS-Server ist der Hostname <code>social.tchncs.de</code> weiterhin nicht existent.</p>
<p dir="auto"><img src="https://write.tchncs.de/static/media/66181996-BB90-B627-8386-BD56351A7226.png" alt="Zertifikat sicher"> </p>
<p dir="auto">Update 2:</p>
<p dir="auto">Auch die Telekom hat soeben ihre DNS-Server aufgefrischt und social.tchncs.de ist wieder erreichbar.</p>
]]><![CDATA[Hach]]>https://write.tchncs.de/~/BubbleBla/Hach/2022-11-17T13:29:20.104204+00:00Aljoscha Rittnerhttps://write.tchncs.de/@/beandev/2022-11-17T13:29:20.104204+00:00<![CDATA[<p dir="auto">Heutiges Feedback:</p>
<iframe src="https://social.tchncs.de/@Erdrandbewohner/109359339343292249/embed" width="600" height="250"></iframe>
<p dir="auto">Hach ♥</p>
]]><![CDATA[Mastodon-Mythen über Timelines, Threads und die Sichtbarkeit von Toots]]>https://write.tchncs.de/~/BubbleBla/Mastodon-Mythen%20über%20Timelines,%20Threads%20und%20die%20Sichtbarkeit%20von%20Toots/2022-11-14T15:58:45.264324+00:00Aljoscha Rittnerhttps://write.tchncs.de/@/beandev/2022-11-14T15:58:45.264324+00:00<![CDATA[<h1 dir="auto">Einleitung</h1>
<p dir="auto">Ich schreibe das auch ein wenig für mich, weil es doch erheblich komplexer ist, als auf anderen Plattformen. Zudem bin ich selbst (zum Beispiel bei ungelisteten Toots) einer Urban Legend aufgesessen. Deswegen gibt es den hier etwas umfassenden Artikel, der beschreibt, wie sich eure Toots verteilen.</p>
<p dir="auto">Wenn ihr komplett neu im Fediverse und Mastodon seid, dann solltet ihr vielleicht zunächst den Artikel “<a href="https://write.tchncs.de/%7E/BubbleBla/%23neuHier%20-%20und%20wie%20interagiere%20ich%3F" rel="noopener noreferrer">#neuHier - und wie interagiere ich?</a>” anschauen. Ansonsten könnte dieser Artikel etwas zu abstrakt sein. Wer sich aber für die Details interessiert, liest bitte weiter:</p>
<p dir="auto">Ich verwende in dem Artikel die Begriffe “Timeline”, “Eingangskörbe” und “Ausgangskörbe”, um modellhaft zu beschreiben, wie sich Mastodon analog zu bekannten Dingen der IT-gestützten Kommunikation verhält (Mail, andere soziale Medien). Das wird sich nicht mit der technischen Implementierung bis ins Detail decken. So gibt es im Protokoll “ActivityPub” gar keine “Local Timeline”. Das wird durch Mastodon so implementiert (und bildet sich ab, über die Outboxen der Accounts der eigenen Instanz). Dazu aber später mehr. Wichtiger ist es mir, ein Verständnis zu bringen, was passiert, ohne zu sehr technische Details auszugraben, wie es funktioniert. Dadurch, dass Mastodon einige Timelines komplett selbst implementiert, unterscheidet es sich übrigens in Details auch zu anderen Diensten im Fediverse. Hubzilla, Misskey oder Pleroma machen das teilweise anders!</p>
<h1 dir="auto">Verteilung der Toots, das Fediverse und Instanzen</h1>
<h2 dir="auto">Grundlagen der Verteilung</h2>
<p dir="auto">Ich gehe nicht sehr tief in technische Details ein, aber es ist wichtig zu verstehen, wie das Fediverse funktioniert, in dem sich Mastodon als einer der Dienste bewegt. Das Fediverse beschreibt ein Universum an föderierten Diensten. Föderiert bedeutet, die Dienste kommunizieren untereinander. Universum bedeutet, es gibt nicht <a href="https://en.wikipedia.org/wiki/Fediverse#Fediverse_software_platforms" rel="noopener noreferrer">nur einen Dienst (wie Mastodon)</a> und es gibt nicht <a href="https://instances.social/list#lang=de&allowed=&prohibited=&min-users=&max-users=" rel="noopener noreferrer">nur einen Server (Instanz)</a>. Es gibt inzwischen sehr viele gute Beschreibungen davon, deswegen beschränke ich mich auf den Mechanismus der Verteilung. </p>
<p dir="auto">Es gibt verschiedene Verfahren, wie man Daten zwischen zwei Systemen synchronisieren kann. Dazu gibt auch Verfahren und Methoden, wie man Daten zwischen vielen Systemen austauschen kann. Eine Mastodon-Instanz ist so ein System. Zum Beispiel <em>social.tchncs.de</em>. Den meisten wird eine viel größere Instanz bekannt sein: <em>mastodon.social</em>. Auf beiden Instanzen (und tausenden mehr), sind sehr viele Menschen mit ihren Accounts angemeldet. Diese Menschen leben einen regen Austausch miteinander. Nicht nur auf derselben Instanz, sondern auch über die Instanzen hinweg. Das bedeutet, ich kann mit meinem Account auf <em>social.tchncs.de</em> mit anderen Accounts auf <em>mastodon.social</em> interagieren.</p>
<p dir="auto">Damit das technisch funktioniert, hat man sich für das Fediverse, für ein ganz bestimmtes Verfahren des Datenaustauschs entschieden: das Inbox/Outbox-Verfahren. Damit unterscheidet es sich vom Verfahren nicht sonderlich von E-Mail. Interessanterweise ergeben sich einige Besonderheiten zur Verteilung und Sichtbarkeit von Toots aus der Tatsache, dass das Verteilungssystem darauf aufbaut, dass eure Nachrichten, ähnlich wie E-Mail, von euren Postausgangsfächern zu den Posteingangsfächern anderer Accounts verteilt werden. Dazu später mehr.</p>
<p dir="auto">Das Fediverse nutzt allerdings (und glücklicherweise) nicht das E-Mail-Protokoll und es beschränkt sich nicht auf das Austauschen von Nachrichten zwischen Accounts. Auch die Nachrichten, die ihr verschickt, werden strukturiert (d.h. in maschinenlesbaren, strukturierten Datenpaketen) codiert und (natürlich verschlüsselt) an die Zielsysteme übertragen. Das inzwischen etablierte Protokoll im Fediverse ist <a href="https://de.wikipedia.org/wiki/ActivityPub" rel="noopener noreferrer">ActivityPub</a>. Das müsst ihr euch nicht merken, aber ihr werdet immer mal wieder darauf stoßen und wisst jetzt, wofür es ist.</p>
<p dir="auto">Eure Accountadressen sind nicht zufälligerweise ein Hybrid aus Handles (At-Zeichen mit Namen, wie in vielen sozialen Medien) und Server-Adresse (eurer Instanzadresse, <a href="https://write.tchncs.de/@//" rel="noopener noreferrer">@</a> instanz-name), beispielsweise <a href="https://write.tchncs.de/@//" rel="noopener noreferrer">@</a>beandev@social.tchncs.de. Denn Server gibt es ja auch wie Sand am Meer. Das ist fast identisch zu E-Mail-Adressen. Eure E-Mail-Adressen beschreiben ebenfalls die Namen der E-Mail-Server (google.com, posteo.de, …). </p>
<p dir="auto">Anhand dieser Accountadressen wird der Austausch der Nachrichten im Fediverse organisiert. Hier endet die Vergleichbarkeit zur E-Mail, weil Instanzen im Fediverse komplexere Regeln haben, was die Verteilung betrifft.</p>
<h2 dir="auto">Public und Private Timelines</h2>
<p dir="auto">Man unterscheidet bei Mastodon zwischen zwei Typen von Timelines. Private und Public Timelines. Die Timelines werdet ihr sicher schon kennen. Hauptsächlich seid ihr in der Home-Timeline (Start, Startseite, Home), die im Kern sogar sehr stark auf die ActivityPub-Inbox/Outbox basiert. Dies ist eure private Timeline. Fast alle anderen Timelines sind öffentliche (public) Timelines, unter anderem die föderierte oder die lokale Timeline (die eurer Heimat-Instanz).</p>
<p dir="auto">Private Timelines sind aus technischer Sicht eine besondere Sicht eurer Eingangs- und Ausgangskörbe auf der eigenen Mastodon Heimat-Instanz. Die öffentlichen “Timelines” sind besondere gefilterte Abfragen oder Aggregationen von Nachrichten von eurer und/oder fremder Instanzen, die für alle Accounts gleich aussieht (wenn man mal Filteroptionen, Blocken und Muten außer Acht lässt). Anders gesagt, die privaten Timelines werden aus ganz persönlichen Auswahlkriterien gefüllt und bilden ab, mit wem ich kommuniziere, wen ich folge, welche Accounts ich kategorisiere. Die public Timelines bilden öffentliche Gruppierungskriterien ab, wie zum Beispiel Zugehörigkeit zu Instanzen oder gemeinsame Hashtags. Für zwei verschiedene Accounts auf derselben Instanz sieht eine Suche nach einem Hashtag gleich aus. Auch die lokale Timeline ist gleich. Aber die private Home-Timeline ist für jeden Account komplett anders, je nachdem welchen Accounts man folgt und was man selbst schreibt!</p>
<p dir="auto">Das hört sich jetzt sehr abstrakt und theoretisch an, ich weiß, aber die folgenden Kapitel beschreiben, was damit gemeint ist. Es ist essenziell für das Verständnis, warum Mastodon so innerhalb des Fediverse funktioniert (und nicht etwas so wie Twitter oder andere Social Media Dienste). </p>
<h3 dir="auto">Private Timelines</h3>
<p dir="auto">Die privaten Timelines sind in Mastodon die Timelines, die ich für mich individuell gestalte und für außenstehende Accounts nur mittelbar (oder gar nicht) sichtbar sind. Listen sind (entgegen anderer Social-Media-Plattformen) privat, es gibt keine öffentlichen Listen. Die Home-Timeline ist die Kommunikationsschnittstelle zwischen den Accounts und mir, denen ich folge und die mir folgen. Niemand erhält Einblick zu den Inhalten und wie sie sich einem in diesen privaten Timelines darstellen. Natürlich sind dort öffentliche Nachrichten drin, die auch in anderen Home-Timelines wiederfinden, aber das Kommunikationsnetzwerk in der Struktur ist für jeden Menschen einzigartig. </p>
<h4 dir="auto"><em>Home Timeline</em></h4>
<p dir="auto">Die Home-Timeline ist euer privater Eingangskorb aller Nachrichten, die das Fediverse nach ein paar Regeln euch zustellt. Die offensichtlichste Regel ist: Ich folge einem Account und lese damit mit, was der Account für Nachrichten schreibt. Ok, das ist jetzt komplett anders als E-Mail (und ich habe es ja oben angekündigt, dass es anders ist). Es ist so ähnlich wie eine Anmeldung an einen Newsletter. Wenn man einem anderen Account folgt, sagt man der Instanz (auf die der Account beheimatet ist), dass man die Nachrichten dieses einen Accounts abonnieren möchte. Im Hintergrund überträgt meine Instanz meine Account-Adresse zur anderen Instanz und richtet ein “Abo” ein. Künftig pusht die andere Instanz die Nachrichten aus der Outbox des von mir gefolgten Accounts in die Inbox meines Accounts. </p>
<p dir="auto">Mit Mastodon 4 ist es auch möglich, Hashtags (und nicht nur Accounts) zu folgen. Sucht man das Hashtag <a href="https://write.tchncs.de/tag/neuHier" title="neuHier" rel="noopener noreferrer">#neuHier</a>, kann man diesem nun folgen und Nachrichten mit diesem Hashtag erscheinen ebenfalls in der Home-Timeline. Auch von Accounts, denen man nicht folgt. </p>
<p dir="auto">Durch die Technik des Eingangskorbs und der besonderen Aktion des “Abonnierens” erhaltet ihr erst ab dem Zeitpunkt des Folgens die Nachrichten von den Accounts und Hashtags eures Interesses. Wenn ihr an älteren Nachrichten interessiert seid, müsst ihr auf das Profil der Instanz wechseln, um dort die vergangenen Nachrichten zu sehen oder eine Hashtag-Suche machen.</p>
<p dir="auto">Als private Timeline, zeigt die Home-Timeline auch Boosts und Antworten von Accounts an, denen ihr folgt. Das kann man für die Home Timeline deaktivieren, aber für public Timelines auf Mastodon nicht aktivieren.</p>
<h4 dir="auto"><em>Direct Message Timeline</em></h4>
<p dir="auto">Einfacher mit E-Mail zu vergleichen, sind “Direct Messages” (DM). Denn das ist modellhaft ein Push-Verfahren, wie man es von Mail auch kennt. Ich erwähne einen oder mehrere Accounts in dem Text, den ich schreibe und meine Instanz sendet diese Nachrichten an <em>alle</em> Accounts, die im Text aufgelistet sind (ja alle, also passt auf, wenn ihr über andere lästert und den Account-Handle mit in den Text schreibt - aber wir lästern ja nicht auf Mastodon).</p>
<p dir="auto">Es gibt die Möglichkeit bei Mastodon, DMs in einer eigenständigen Timeline anzuschauen (das geht z.B. im erweiterten Browser UI von Mastodon und einige Smartphone-Apps unterstützen das auch und die Ansicht ist sehr rudimentär), aber das ist nur eine gefilterte Ansicht der empfangenen Direktnachrichten aus eurer Home-Timeline. Ihr werdet auch schon bemerkt haben, dass DM tatsächlich ganz normal in eurer Home-Timeline auftauchen (und das Umschlag-Symbol wird gerne mal übersehen).</p>
<p dir="auto">Die DM Timeline ist also eine Schimäre unter den Timelines. Sie ist eher eine Suche auf das Kriterium “Empfangene Nachrichten vom Typ <em>Direct Messages</em>”.</p>
<p dir="auto">Da DMs sich wie normale Nachrichten verhalten (also auch Antworten in Unterhaltungen sein können, die vorab öffentlich waren) und sogar mehreren erwähnten Accounts zugestellt werden, gibt es eine sehr fundamentale Sache, die man wissen sollte: Diese DMs sind, wie alle anderen öffentlichen Nachrichten in meinem Eingangskorb, <em>nicht</em> verschlüsselt. </p>
<h4 dir="auto"><em>List-Timelines</em></h4>
<p dir="auto">Man kann sich Listen von Accounts zusammenstellen, denen man folgt. Diese List-Timelines sind nur für mich sichtbar und auf Mastodon sammeln die Nachrichten der Accounts in dieser Liste in einem gesonderten Eingangskorb, es handelt sich damit um eine private Timeline. </p>
<p dir="auto">Stecke ich einen Account neu in die Liste, erhalte ich erst ab diesem Zeitpunkt die Nachrichten des Accounts in diese Liste. Nehme ich einen Account aus der Liste heraus, bleiben die Nachrichten (die bisher von dem Account kamen) in der List-Timeline, aber es kommen keine weiteren von diesem Account hinzu. </p>
<p dir="auto">Um es mal technisch (vereinfacht) herunterzubrechen: Eine List-Timeline ist wie eine E-Mail-Client-Regel, die festlegt, dass aus dem Eingangskorb die Nachrichten von bestimmten Accounts in einen separaten Ordner (meine Liste) zu kopieren sind. </p>
<p dir="auto">Diese Art von Funktionalität der Listen ist, wie es Mastodon implementiert. Für andere Dienste im Fediverse funktionieren Listen ggf. anders. </p>
<h3 dir="auto">Public Timelines</h3>
<p dir="auto">Public Timelines sind eine Sicht auf öffentlich zugängliche Nachrichten, die nicht nach persönlichen Netzwerkkriterien strukturiert werden (ausgenommen mal die Wahl der Instanz, wo der eigene Account beheimatet ist). Für die Inhalte muss man bei Mastodon nicht mit einem Account angemeldet sein. Bin ich aber angemeldet, könnte sich auch der Inhalt der public Timelines für mich verändern, weil der Client oder die Mastodon-Instanz, mit der ich auf die Nachrichten zugreife, aufgrund meiner Konfiguration filtern könnten. Es kommt auch auf die Einrichtung der Mastodon-Instanz an, ob die Instanz “offen” einsehbar ist. Es gibt einige Instanzen, die eine Einsicht der lokalen Timeline nur für lokal angemeldete Accounts erlauben.</p>
<p dir="auto">Allen public Timelines ist gemeinsam, dass sie aufgrund von Filterkriterien erstellt werden zu Daten, die der lokalen Instanz zur Verfügung stehen (also aus der Synchronisation mit anderen Nachrichten anderer Instanzen und/oder der eigenen Instanz).</p>
<h4 dir="auto"><em>Local Timeline</em></h4>
<p dir="auto">Die lokale Timeline ist die öffentliche Zeitleiste eurer Mastodon-Instanz. Es ist die Zusammenfassung aller öffentlichen Nachrichten von allen Accounts, die auf derselben Instanz beheimatet sind. In einem späteren Kapitel kommen wir zu der Sichtbarkeit von Nachrichten, womit ihr manuell festlegen könnt, ob eure Nachrichten dort erscheinen. Allerdings gibt es auch eine technische Besonderheit: Es werden bei Mastodon keine geteilten Nachrichten (Boosts), in der lokalen Timeline angezeigt. Öffentliche Antworten sind dafür in der Local Timeline ebenfalls zu sehen. Boosts seht ihr nur in der Home-Timeline von Accounts, denen ihr folgt. Die lokale Timeline ist im Prinzip eine synthetische Timeline der Ausgangskörbe der Accounts mit einem Filter auf Sichtbarkeit und Typ der Nachrichten. So wie es Mastodon macht, muss nicht für Dienste wie Hubzilla, Pleroma, Misskey oder andere gelten. Pleroma beschreibt das auch in <a href="https://docs-develop.pleroma.social/frontend/user_guide/timelines/" rel="noopener noreferrer">seiner Dokumentation</a>.</p>
<p dir="auto">Auf jeder Mastodon-Instanz ist also der Inhalt der lokalen Timeline komplett anders, weil ja jede Instanz ihre eigene Community beinhaltet (die auf vielen Instanzen eigenen Themen und Regeln haben).</p>
<p dir="auto">Da die Nachrichten der lokalen Timeline ohnehin immer auf eurem Mastodon-Heimat-Server liegen, kann man beliebig weit in die Vergangenheit gehen und sich die Nachrichten anschauen. Auch wenn ihr ganz neu auf einer Instanz seid, habt ihr Zugriff auf alles, was mal geschrieben und öffentlich gepostet (und nicht wieder gelöscht) wurde.</p>
<h4 dir="auto"><em>Federated Timeline</em></h4>
<p dir="auto">In der föderierten Timeline findet ihr alle öffentlichen Nachrichten eurer beheimateten Instanz und aller verbundenen (Peer) Instanzen. D.h., es sind nicht die Nachrichten <em>aller</em> Instanzen des gesamten Fediverse drin, sondern nur die, zwischen denen es einen Austausch gibt. Die Regel, welche Instanzen im Austausch miteinander sind, kann auf eine grundlegende Sache reduziert werden: folgen sich Accounts auf von zwei unterschiedlichen Instanzen, dann synchronisieren sich die Instanzen und zeigen in ihrer föderierten Timeline alle öffentlichen Nachrichten <em>der jeweils bekannten Accounts</em> der anderen Instanz mit an. </p>
<p dir="auto">Das bedeutet, folgt ein Account einem anderen Account einer anderen Instanz, werden die öffentlichen Nachrichten des Accounts auf der entfernten Instanz in der eigenen Instanz in der föderierten Timeline eingespielt. Damit ist die föderierte Timeline ein sehr komplexer Ausschnitt an Nachrichten, aus vielen “sich bekannten” Instanzen, gespeist von verknüpften Accounts. </p>
<p dir="auto">Somit ist die Sicht der föderierten Timeline, so wie die lokale Timeline, auf jeder Instanz ebenfalls komplett unterschiedlich. Sie ist die Abbildung des Kommunikationsnetzwerkes der sich bekannten Accounts. </p>
<h4 dir="auto"><em>Hashtag Timeline</em></h4>
<p dir="auto">Wenn man neu im Fediverse und bei Mastodon ist, wird man nicht gleich mitbekommen, dass man auf Mastodon Hashtags als Timelines haben kann. Diese Hashtag-Timelines sind auch öffentliche Timelines und verhalten sich wie eine gespeicherte Suche. Nach der Suche eines Hashtags hat man eine Timeline der Ergebnisse. Allerdings kann man diese Timeline “anheften”. Bei Mastodon 3 gibt es im einfachen UI einen Link, im erweiterten UI erscheint eine neue Spalte mit den Nachrichten, die das Hashtag verwenden. </p>
<p dir="auto">Mit Mastodon 4 gibt es dazu die Funktion: “Hashtag folgen”, was aber keine neue Timeline erzeugt. Ihr bekommt die Hashtag-Nachrichten dann direkt in die Home-Timeline gespielt (auch wenn ihr den Accounts nicht gefolgt seid).</p>
<p dir="auto">Dadurch, dass Hashtag-Timelines ein Suchergebnis abbilden, beinhalten diese auch vergangene Nachrichten und man kann die Timeline weit in die Vergangenheit anschauen. Die getaggten Nachrichten sind aber nur aus den Instanzen und Accounts, mit denen eure Heimat-Instanz föderiert. In der Hashtag-Timeline werden auch Antworten aus Unterhaltungen angezeigt (soweit diese öffentlich sind) und die Antworten den Hashtag enthalten. </p>
<h4 dir="auto"><em>Profile Timeline</em></h4>
<p dir="auto">Die Timeline eines Profils einer Mastodon-Instanz wird häufig nicht als solche wahrgenommen. Es ist nicht die Home-Timeline des Profils, sondern eine teil-öffentliche Ansicht der Nachrichten eines bestimmten Accounts (und anderer Accounts, wenn deren Nachrichten auf dem Profil geboostet wurde). Im Profil sieht man grundsätzlich <em>öffentliche</em> und <em>nicht gelistete</em> Nachrichten. Ist man angemeldet, sieht man noch die <em>Nur Follower</em> Nachrichten, wenn man den Account tatsächlich folgt. Auf dem Profil gibt es zwei Ansichten: Beiträge und Beiträge mit Antworten. Die zweite Sicht zeigt einfach auch zusätzlich öffentliche Antworten an. </p>
<p dir="auto">Im Prinzip ist es auch eine gefilterte Ansicht von Nachrichten für einen Account. Das besondere ist, dass das die aktuellste Sicht ist, die man für die Nachrichten des Accounts haben kann. Alle Statistiken der Nachrichten dort in der Ansicht und auch die Statistiken des Account (Beiträge, Folgt, Folgende) sind der aktuelle Stand auf der Heimatinstanz des Profils. Das kann abweichen von den Informationen, die man in den Timelines der eigenen Instanz stehen (weil es dazu keine aktuelle Synchronisation gab). </p>
<h1 dir="auto">Sichtbarkeit von Toots</h1>
<p dir="auto">Ich habe bereits zwei Artikel zu Mastodon geschrieben, wo ich auf die <em>Sichtbarkeit</em> von Toots einging und hier möchte ich noch einmal eingehender beschreiben, was das bedeutet. Zudem gilt es ein paar Missverständnisse aus dem Weg zu räumen, was die unterschiedlichen Sichtbarkeitseinstellungen bedeuten.</p>
<p dir="auto">Grundsätzlich lässt sich jeder Toot manuell auf eine Sichtbarkeit einstellen. Erst mal versendet, ist es nicht mehr möglich, diese zu ändern. Auch nicht mit der neuen Bearbeiten-Funktion. Man kann auch in seinen Einstellungen festlegen, dass man grundsätzlich nicht öffentlich Toots posten will. Man kann dann trotzdem jederzeit einzelne Toots auf “öffentlich” setzen, bevor ich ihn poste.</p>
<p dir="auto">Folgende Sichtbarkeits-Einstellungen gibt es:</p>
<ul dir="auto">
<li>🌐 Öffentlich </li>
<li>🔒Nur Follower</li>
<li>🔓Nicht gelistet</li>
<li>✉ Nur Leute, die ich erwähne</li>
</ul>
<p dir="auto">Je nach eingestellter Sichtbarkeit, ist eine Nachricht unter dieser Einstellung, nur in bestimmten Timelines oder bestimmten Accounts zugänglich. Überdies entscheidet auch der Typ eines Toots (Nachricht, Antwort/Reply, Geteilt/Boost), in welchen Timelines die Nachricht sichtbar wird. Außerdem kann die Kombination des Typs, die Erwähnungen in der Nachricht und die Gefolgschaft beeinflussen, was ich letztendlich sehen kann oder mir verborgen bleibt.</p>
<p dir="auto">Das sind ganz schön viele Parameter. Einen Teil habe ich oben schon in den Kapiteln zu den Timelines abgefrühstückt. Hier versuche ich Licht in das restliche Dunkel zu bringen.</p>
<h2 dir="auto">Öffentliche Nachrichten 🌐</h2>
<p dir="auto">Sende ich eine neue Nachricht (die keine Antwort und kein Boost ist) mit der Sichtbarkeit “Öffentlich” (mit dem Weltkugel-Symbol), dann wird diese zunächst für alle sichtbar, die auf derselben Mastodon-Instanz sind und in die Local Timeline schauen. Ebenso sehen das auch alle meine Follower*innen. Föderiert meine Instanz mit anderen Instanzen, ist die Nachricht auch in dieser Timeline zu sehen, auch auf den anderen verbundenen Instanzen (deren Accounts bekannt sind).</p>
<p dir="auto">Antworte ich öffentlich oder booste ich von einem anderen Account eine Nachricht, verändert sich die tatsächliche Sichtbarkeit. Diese Interaktion wird weder in der lokalen, noch in der föderierten Timeline auf Mastodon-Instanzen sichtbar. Mir sollte aber bewusst sein, dass andere Dienste im Fediverse meine Replies und Boosts ggf. trotzdem anzeigen. Aber alle, die mir folgen, können alle Typen von Nachrichten in ihrer Home-Timeline sehen. Das kann zwar jeder für sich individuell abschalten (also ausblenden), aber zugestellt werden Antworten und Boosts immer. </p>
<p dir="auto">Wähle ich auf Mastodon in der lokalen oder föderierten Timeline eine Nachricht aus (und wechsele damit zur Unterhaltungs-/Thread-Ansicht), sehe ich die öffentlichen Antworten. Wähle ich in meiner Home-Timeline die öffentliche Antwort (oder einen Boost) einer meiner Follower aus, sehe ich ebenfalls die Unterhaltung (ggf. aber nicht immer vollständig, was dem geschuldet ist, dass jede Nachricht eine individuelle Sichtbarkeit haben kann - auch in Unterhaltungen).</p>
<h2 dir="auto">Nur Follower 🔒</h2>
<p dir="auto">Fast einfacher als “Öffentliche Nachrichten” ist die Sichtbarkeitseinstellung “Nur Follower”. Jeder Account, der euch folgt und wenn die Person hinter dem Account angemeldet ist, kann diese Nachrichten lesen. Diese Nachrichten werden mit einem geschlossenen Vorhängeschloss gekennzeichnet und macht mit dem Symbol deutlich, dass eine Anmeldung zwingend erforderlich ist. </p>
<p dir="auto">Was bedeutet das für die Timelines? Wenn ich einem anderen Account folge, sehe ich alle Nachrichten des Accounts, die mit der Sichtbarkeitseinstellung “Nur Follower” gesetzt sind. Wenn ich auf die Mastodon-Profilseite des Accounts gehe, dann übrigens auch. Melde ich mich ab, sehe ja meine Home-Timeline ohnehin nicht, wechsele ich nun (abgemeldet) zum Profil des Accounts (zum Beispiel im Browser), das auch “Nur Follower” Nachrichten verschickt, sehe ich diese Nachrichten dann <em>nicht</em> mehr (aber die öffentlichen Nachrichten!). </p>
<p dir="auto">Mastodon filtert hier also “aktiv”, die Nachrichten sind hinter einer Anmelde-Barriere. </p>
<p dir="auto">Aber es gibt eine andere Konsequenz, die fast wichtiger ist: Wenn ein Account seine Privatsphäre wahren will, in dem es “Nur Follower” Nachrichten sendet, kann ich als Account einfach nur dem Account folgen und sehe dann die Nachrichten. Ups. Also muss ein Account sich mit einem “Schlossaccount” vor diesem Automatismus zusätzlich schützen und den Folgewunsch nach Abwägung freigeben. </p>
<p dir="auto">Durch den aktiven Abgleich: “Folge ich und bin ich angemeldet”, gibt es aber eine weitere Konsequenz: Lasse ich zu, dass jemand mir folgt, sieht der Follower dann auch alle alten Nachrichten auf der Profil-Timeline (nicht in der Home-Timeline). Mastodon unterscheidet nicht, ab <em>wann</em> ein Follower dazukommt. Folgt er mir, sieht er alle Nachrichten, die eben für die Folgenden freigegeben wurden. </p>
<p dir="auto">Es ist fast klar, dass die Toots mit der Sichtbarkeit “Nur Follower” auch nicht in der lokalen Timeline und auch nicht in der föderierten Timeline erscheinen. </p>
<p dir="auto">Vielleicht ist die Frage zunächst nicht offensichtlich, aber wie verhält es sich bei Boosts? Ist da eine Sichtbarkeit festzulegen? Da das Browser UI da keine Auswahl gibt (und viele Apps auch nicht), mag man vermuten, das ginge nicht. Man kann aber eine Standard-Sichtbarkeit in den Mastodon Optionen festlegen. Die gilt dann auch für Boosts (das sieht man manchmal, wenn man Schloss-Accounts folgt und der Account konsequent “Nur Follower” Sichtbarkeit nutzt).</p>
<h2 dir="auto">Nicht gelistet 🔓</h2>
<p dir="auto">Das absolute Mysterium des Fediverse und Mastodon: Ungelistete Nachrichten. Es gibt einige <em>urbane Legenden</em> dazu und es ist nicht wirklich einfach zu erklären, wofür man diesen Typ an Sichtbarkeit benötigt. </p>
<p dir="auto">Die Dokumentation von Mastodon sagt, dass <em>Nicht gelistete</em> Nachrichten sich wie öffentliche Nachrichten verhalten, aber nicht in der lokalen oder föderierten Timeline auftauchen. Ja, so habe ich auch geschaut. </p>
<p dir="auto">Also wo sind dann ungelistete, trotzdem öffentliche Nachrichten dann zu sehen?</p>
<p dir="auto">In meiner Home-Timeline. Und zwar unter allen Bedingungen, die auch öffentliche Nachrichten betreffen: Antworten, Boosts und neue Nachrichten von allen meinen gefolgten Accounts. Somit bringt es für die Thread-Ansicht in der Home-Timeline keine Vereinfachung, wenn man die 2. bis n. Nachricht auf “Nicht gelistet” setzt. Schreibt ein Account, dem ich folge, einen Thread und setzt die 2. bis n. Nachricht auf “Nicht gelistet”, sehe ich alle diese Nachrichten trotzdem einzeln in meiner Home-Timeline. Für die Local Timeline hat es aber Auswirkungen, denn dort sieht man ungelistete Nachrichten nicht.</p>
<p dir="auto">Ansonsten sehe auf Mastodon auch Nachrichten vom Typ “Nicht gelistet” in meiner Home-Timeline, die von einem gefolgten Account geboostet wurden. Sogar dann, wenn die Ursprungsnachricht von einem Schlossaccount “ungelistet” geschrieben wurde.</p>
<p dir="auto">Ok, jetzt könnte man sich langsam fragen, wofür das Ganze nun? </p>
<p dir="auto">Die Nachrichten mit dem Typ “Nicht gelistet” tauchen bei Mastodon in keiner Hashtag-Timeline auf (obwohl ja öffentlich). “Nicht gelistet” Nachrichten, kann man sehen, wenn man in Mastodon nicht angemeldet ist (also man kann den Link an andere schicken, die nicht im Fediverse oder bei Mastodon sind). Man spricht in diesem Fall davon (und so in der Mastodon-Doku zu lesen), dass der Permalink der Nachricht öffentlich ist - es gibt keine Anmelde-Barriere. Das gilt auch für öffentliche Mastodon-Profil-Ansichten, unter der ihr eine Timeline des Profils sehen könnt. Dort seht ihr neben den öffentlichen Nachrichten und Antworten auch die “ungelisteten” Nachrichten. Geht ihr zum Beispiel auf <a href="https://social.tchncs.de/@beandev" rel="noopener noreferrer">mein Profil</a> und scrollt euch durch die Beiträge, werdet ihr neben den Nachrichten mit der Weltkugel auch Nachrichten sehen, die ein geöffnetes Schloss haben. Das auch für Boosts und <a href="https://social.tchncs.de/@beandev/with_replies" rel="noopener noreferrer">Antworten</a>. </p>
<p dir="auto">Wenn die Accounts, denen ich folge, ungelistete Nachrichten boosten, kann ich diese sehen, auch wenn ich ihnen nicht folge. Das macht “Nur ungelistet” zu <em>öffentlichen</em> Nachrichten (auch wenn sie nicht in den public Timelines von Mastodon erscheinen).</p>
<p dir="auto">Für Mastodon-Schlossaccounts (also Accounts, die Follower nur mit Anfrage zulassen), gilt ebenfalls zu beachten: Auf eurer Profil-Seite werden alle Nachrichten vom Typ “Öffentlich” und “Nur gelistet” angezeigt. Wenn ihr das nicht wollt, müsst ihr als Sichtbarkeit bei “Nur Follower” bleiben. Dann müssen Interessenten an euren Nachrichten angemeldet und eure Follower sein, damit man die Nachrichten gesehen werden können. </p>
<p dir="auto">Also noch mal zusammengefasst für Mastodon: “Nicht gelistet” bedeutet, die Nachrichten sind in den Home-Timelines, nicht in der lokalen und föderierten Timeline, aber grundsätzlich öffentlich zugänglich. Sowohl als Permalinks und auch in jeder Profil-Ansicht, wo die Nachrichten erwähnt oder gepostet wurden, egal ob ihr ein öffentliches oder geschlossenes Profil habt.</p>
<p dir="auto">Noch ein wichtiger Hinweis: Es gibt Clients, die “Nicht gelistet” gar nicht als Sichtbarkeit für Toots anbieten. Mindestens eine Apple App für Mastodon/Fediverse unterstützt diese Sichtbarkeits-Einstellung nicht.</p>
<h2 dir="auto">Nur Leute, die ich erwähne ✉</h2>
<p dir="auto">Direct Messages werden Nachrichten genannt, die diese Sichtbarkeit erhalten. Man kann das naturgemäß nur bei neuen Nachrichten setzen oder bei Antworten. Boosts kann man nicht als DM verschicken.</p>
<p dir="auto">Wo kommen die DMs an und wie sichtbar sind diese? Zunächst gilt für DM im Prinzip das Gleiche, wie für “Nur Follower”. DMs sind hinter einer Anmelde-Schranke. Ist man angemeldet, kann man eine Direktnachricht aber nur sehen, wenn man in der DM erwähnt wurde, also wenn die vollständige Fediverse-Adresse im Text (das gilt für Mastodon) geschrieben wurde. Das dürfen auch mehrere Adressen sein. Dann ist es eine “Gruppennachricht”. Alle erwähnten Accounts (Mentions) bekommen die DM! Die Nachricht erscheint dann in deren Home-Timelines mit dem Symbol eines Briefumschlags. Das Mastodon-Browser UI bietet noch eine Timeline, die DMs (die ich empfangen habe) extra filtert. Im erweiterten UI kann ich die Timeline auch anpinnen. Fraglos, dass man DMs nie in der lokalen, föderierten oder in der Profil-Ansicht sehen wird. </p>
<p dir="auto">Direct Messages werden ansonsten wie alle anderen Nachrichten behandelt. Man kann in zunächst öffentlichen Unterhaltung, auf DM wechseln (damit man in kleinerem Kreis weiterdiskutiert), aber die DM (und die Antworten darauf) bleiben in der Unterhaltung “eingehängt”. Mastodon speichert Direct Messages auch nicht verschlüsselt ab. Man kann nachträglich Mentions aus einer DM herausnehmen oder hinzufügen. Man sollte aber beachten, dass bei Unterhaltungen die älteren Antworten dann für neue Teilnehmer nicht sichtbar werden. </p>
<h1 dir="auto">Epilog</h1>
<p dir="auto">Der Verteilmechanismus von Nachrichten im Fediverse und durch Mastodon implementiert und mit ActivityPub ausgeführt, ist sehr komplex und hängt von vielen Parametern ab. Es gibt mehrere Sichtbarkeits-Einstellungen, es gibt die Wahl, wem ich diese Nachrichten zukommen lassen will, es hängt davon ab, ob ich auf derselben Instanz oder auf einer entfernten Instanz bin, ob ich folge oder nicht und in welche Timelines ich schaue. Es gibt sogar unterschiedliche Behandlung, je nachdem, auf welchem Dienst ihr euch befindet. Ich hatte mich hier auf die Mastodon-Sicht fokussiert, d.h. aus der Sicht eines Accounts auf einer Mastodon-Instanz.</p>
<p dir="auto">Alle diese Parameter haben ihr Gründe und sind über die Jahre gewachsen. Sie machen das Fediverse mittels Mastodon sicher nicht unmittelbar intuitiv benutzbar, aber wurden über die Jahre als sinnvoll erachtet. Wenn man sich ein wenig damit auseinandersetzt, wird das irgendwann in Fleisch und Blut übergehen. </p>
<p dir="auto">Ich habe bewusst auf tiefere technische Details verzichtet und hoffe für Anweder*innen ist es verständlich genug. Wenn es trotzdem Verständnisschwierigkeiten gibt, einfach fragen. Ich werde über die Zeit den Artikel auch aktualisieren, wenn notwendig. </p>
<p dir="auto">Inzwischen wurden einige Anregungen von den Kommentaren aufgenommen. Danke für eure Hilfe 😃!</p>
]]><![CDATA[#neuHier - wie privat kann ich bei Mastodon sein?]]>https://write.tchncs.de/~/BubbleBla/%23neuHier%20-%20wie%20privat%20kann%20ich%20bei%20Mastodon%20sein%3F/2022-11-02T09:00:38.759642+00:00Aljoscha Rittnerhttps://write.tchncs.de/@/beandev/2022-11-02T09:00:38.759642+00:00<![CDATA[<h1 dir="auto">Einleitung</h1>
<p dir="auto">Ganz vorn weg, ihr seid in einem sozialen Netzwerk, das darauf ausgelegt ist, dass viele Menschen miteinander kommunizieren. Alle Funktionen sind so eingestellt, dass erst mal eine hohe Sichtbarkeit erreicht wird.</p>
<p dir="auto">Wenn man das ändern will, muss man ein klein wenig mehr machen, als in anderen sozialen Netzwerken. Das liegt nicht daran, dass man es einem besonders schwer machen will, Mastodon erlaubt es nur etwas feiner, die Einstellungen zu verwalten. </p>
<p dir="auto">Wenn man beispielsweise von Twitter kommt, hat man ggf. auch eine andere Erwartungshaltung, wie einige Einstellungen funktionieren sollten. Aber Mastodon, innerhalb des Fediverse, mit dem Protokoll ActivityPub, funktioniert da ganz anders. </p>
<p dir="auto">Am Ende des Artikels gehe ich auf ein paar Besonderheiten (und Diskussionen ein), die eure Privatsphäre betreffen.</p>
<p dir="auto">Es gibt inzwischen einen weiteren Artikel: <a href="https://write.tchncs.de/%7E/BubbleBla/Mastodon-Mythen%20%C3%BCber%20Timelines,%20Threads%20und%20die%20Sichtbarkeit%20von%20Toots" rel="noopener noreferrer">Mythen über Timelines, Threads und die Sichtbarkeit von Toots</a>, der nochmal sehr in die Tiefe geht. Ich würde aber mal mit diesem Artikel anfangen.</p>
<h1 dir="auto">Sichtbarkeit von Toots</h1>
<p dir="auto">Jeder Beitrag, den ihr in das Fediverse veröffentlicht, hat eine individuelle Sichtbarkeit. Die ist, wenn festgelegt und gepostet, unveränderlich (ausgenommen, die neue Edit-Funktion erlaubt eine Änderung - aber das weiß ich noch nicht). Das ist ein wesentlicher Unterschied zu anderen Netzwerken (ausgenommen das gute alte Google+). In Twitter ist die Sichtbarkeit der eigenen Tweets häufig nur global davon abhängig, ob ich einen Schloss-Account habe oder nicht und wechselt auch für alle Tweets, ändere ich den Status des Accounts. </p>
<p dir="auto">In Mastodon habe ich die Möglichkeit, jeden Toot manuell mit einer Privatsphäre-Einstellung zu versehen:</p>
<p dir="auto"><img src="https://write.tchncs.de/static/media/E9F53F56-5DEC-3734-BA05-268409B42AC1.png" alt="Sichtbarkeit von Toots"></p>
<ol dir="auto">
<li><em>Öffentlich</em> - Der Toot wird, wie schon oben beschrieben, in der lokalen Instanz und in andere Instanzen gelistet, die sich über gegenseitige Follower sich mal vernetzt haben. </li>
<li><em>Nicht gelistet</em> - Der Toot ist für deine Follower sichtbar, aber nicht in der föderierten Zeitleiste. Mastodon hat eine “Entdecken” und “Suchen” Funktion, um Toots zu finden. Das ist damit abgeschaltet. Zum Beispiel sieht man den Post nur in deinem Profil. Diese Sichtbarkeit wird auch für 2. bis n. Beiträge eines Threads benutzt, damit diese nicht “einzeln” in der Home-Timeline zu sehen sind (sondern nur in der Unterhaltungs-Ansicht).</li>
<li><em>Nur Follower</em> - Das sehen dann nur die User*innen, die mir folgen. Das kann auch auf anderen Instanzen sein. </li>
<li><em>Nur Leute, die ich erwähne</em> - das sind Direct Messages (DM), Private Messages (PM) oder “Gruppennachrichten”, je nach Kontext (und wie viele ich erwähne). Diese Nachrichten kann man nicht boosten.</li>
</ol>
<p dir="auto">Bei der Sichtbarkeit für <em>Nur Folgende</em> muss man noch berücksichtigen, dass man bei einem Account ohne Schloss, dir jederzeit Accounts folgen können (ohne deine Einwilligung), und dann nachträglich deine “Nur Folgende” Nachrichten lesen können. Ich beschreibe später, wie es mit dem Schloss-Account funktioniert.</p>
<p dir="auto">Hier eine Übersicht über Sichtbarkeiten und die Konsequenzen in Tabellenform:</p>
<table dir="auto"><thead><tr><th dir="auto">Sichtbarkeit</th><th dir="auto" align="center">Föderierte Timelines</th><th dir="auto" align="center">Permalink</th><th dir="auto" align="center">Sichtbar im Profil</th><th dir="auto" align="center">Sichtbar in Home Timeline</th></tr></thead><tbody>
<tr><td dir="auto">Öffentlich</td><td dir="auto" align="center">Ja</td><td dir="auto" align="center">Ja</td><td dir="auto" align="center">Ja</td><td dir="auto" align="center">Ja</td></tr>
<tr><td dir="auto">Ungelistet</td><td dir="auto" align="center">Nein</td><td dir="auto" align="center">Ja</td><td dir="auto" align="center">Ja</td><td dir="auto" align="center">Ja</td></tr>
<tr><td dir="auto">Nur Folgende</td><td dir="auto" align="center">Nein</td><td dir="auto" align="center">Angemeldet auf der selben Instanz</td><td dir="auto" align="center">Angemeldet</td><td dir="auto" align="center">Ja</td></tr>
<tr><td dir="auto">Direkt</td><td dir="auto" align="center">Nein</td><td dir="auto" align="center">Angemeldet und erwähnt</td><td dir="auto" align="center">Angemeldet</td><td dir="auto" align="center">Nein</td></tr>
</tbody></table>
<p dir="auto">Man sieht, das ist schon ziemlich speziell, was Mastodon einem da zumutet. Es ist auch nicht immer einfach, daran zu denken, für jeden Post das einzustellen. </p>
<h1 dir="auto">Mehr Privatsphäre bitte</h1>
<p dir="auto">Also steigen wir mal in die Konfiguration von Mastodon ein, um die Privatsphäre zu erhöhen. </p>
<h2 dir="auto">Standard für Sichtbarkeit</h2>
<p dir="auto">Man kann grundsätzlich festlegen, welche Standard-Sichtbarkeit meine Toots haben sollen. Nicht alle obigen Optionen stehen da zur Verfügung (weil nicht alle sinnvoll sind), aber man kann damit schon viel erreichen.</p>
<p dir="auto">Dazu muss man in die Einstellungen gehen:</p>
<p dir="auto"><img src="https://write.tchncs.de/static/media/66863AD6-CF3D-9561-F4EA-BD630674B0B2.png" alt="Mastodon - Zu Einstellungen"></p>
<p dir="auto">In dem Einstellungsmenü, wechselt man auf “Weiteres” und kann dort die Beitragssichtbarkeit festlegen:</p>
<p dir="auto"><img src="https://write.tchncs.de/static/media/77D7836A-5C0A-784D-EDF7-0BBB2069D57A.png" alt="Sichtbarkeit der Nachrichten in Mastodon festlegen"></p>
<ol dir="auto">
<li><em>Öffentlich</em> - Der Toot wird, wie schon oben beschrieben, in der lokalen Instanz und in andere Instanzen gelistet, die sich über gegenseitige Follower sich mal vernetzt haben. </li>
<li><em>Nicht gelistet</em> - Der Toot ist für deine Follower sichtbar, aber nicht in der föderierten Zeitleiste. </li>
<li><em>Nur Folgende</em> - Das sehen dann nur die User*innen, die mir folgen. Das kann auch auf anderen Instanzen sein.</li>
</ol>
<p dir="auto">Diese <em>Voreinstellung</em> gilt für zukünftige Toots. Vergangene Toots <em>ändern sich nicht</em> damit! Wenn ihr nun einen neuen Toot erstellt, seht ihr im Nachrichten-Editor die neue Einstellung (also z.B. nicht mehr die Weltkugel). Es ist aber auch natürlich noch möglich für ein bestimmten Toot die Sichtbarkeit wieder für alle öffentlich zu machen. </p>
<p dir="auto">Wenn ihr also eine möglichst hohe Privatsphäre haben wollt, dann solltet ihr die Beitragssichtbarkeit auf <em>Nur Folgende</em> stellen. Dann werden nur Follower*innen deine Beiträge sehen.</p>
<h2 dir="auto">Wer mir folgt, bestimme ich!</h2>
<p dir="auto">Wen ich in meine Privatsphäre lassen will, darf ich auch selbst entscheiden. In Mastodon muss man das explizit festlegen, wenn man diese “Freigabe” übernehmen möchte. Mit der Standardeinstellung kann jeder Account mir zunächst einfach folgen.</p>
<p dir="auto">Man spricht bei Mastodon (wie bei Twitter) von einem Schloss-Account, in den Einstellungen nennt es sich seit Mastodon 4 “Geschütztes Profil” (in Mastodon 3 wurde es als “Profil sperren” bezeichnet). </p>
<p dir="auto">Geht dafür wieder in die Einstellung, diesmal zum <em>Profil</em> -> <em>Aussehen</em>:</p>
<p dir="auto"><img src="https://write.tchncs.de/static/media/1F0D9E97-900C-EB37-ECCE-9213F5AE2E61.png" alt="Mastodon - Profil schützen - Schlossaccount"></p>
<p dir="auto">Wenn ihr den Haken setzt, erscheint direkt darüber in der Vorschau ein kleines Schloss. Nach der Speicherung hat sich nur <em>eine</em> Sache verändert: Neue Follower*innen können euch nicht mehr direkt hinzufügen, sondern das wird nun eine Anfrage sein, die ihr bestätigen müsst (oder ablehnen könnt). Der Account, der anfragt, sieht in der Zwischenzeit, dass die Anfrage zum Folgen noch aussteht. </p>
<p dir="auto">Die Aktivierung zu einem Schloss-Account ändert nicht die Sichtbarkeit eurer vergangenen und zukünftigen Toots. Das müsst ihr mit den oben beschriebenen Einstellungen festlegen.</p>
<p dir="auto">Übrigens, es gibt auch eine Besonderheit: Auch eure Instanz (d.h. die Admins und Moderatoren), können festlegen, dass Folge-Anfragen von ganz bestimmten anderen Instanzen, von euch bestätigt werden müssen. Auch wenn ihr <em>kein</em> Schloss-Account habt. Das übersieht man schnell mal. Schaut damit gelegentlich nach, ob es Anfragen von Followern gibt und bestätigt sie, wenn ihr das möchtet.</p>
<h1 dir="auto">Wer mich anschreibt, bestimme ich!</h1>
<p dir="auto">Was besonders auf Twitter eine wichtige Funktion ist, das Abriegeln der Direkt-Nachrichten, ist auch in Mastodon möglich, aber sehr versteckt (und meiner Meinung nach auch etwas missverständlich beschrieben).</p>
<p dir="auto">In den Einstellungen findet man den Abschnitt Benachrichtigungen. Dieser fängt auch mit Ereignissen an und danach gibt es weiter unten “Weitere Benachrichtigungseinstellungen”. Es geht nicht darum, dass man (k)einen Hinweis für eine Nachricht bekommt, sondern man kann dort festlegen, wie man erreichbar ist:</p>
<p dir="auto"><img src="https://write.tchncs.de/static/media/767B310D-700D-08D0-4B0F-9FE6F42BCC60.png" alt="Mastodon - Direktnachrichten"></p>
<p dir="auto">Die folgenden Optionen sind aktuell mit Mastodon 3 möglich:</p>
<ul dir="auto">
<li>Benachrichtigungen von Profilen blockieren, die mir nicht folgen</li>
<li>Benachrichtigungen von Profilen blockieren, denen ich nicht folge</li>
<li>Private Nachrichten von Profilen, denen ich nicht folge, blockieren</li>
</ul>
<p dir="auto">Aktiviert man die letzte Option, hat man ähnliches erreicht, wie man es ähnlich von Twitter gewohnt ist. Technisch gesehen ist es ein “Mute” der Direkt-Nachrichten. D.h., solange die Option aktiv ist, werden die eingehenden DMs von Accounts, <em>denen ich nicht folge</em>, für mich unsichtbar gemacht. Und sie bleiben es. Auch wenn ich die Option wieder deaktiviere, werde ich nur neu gesendete Direkt-Nachrichten sehen. Die Sender<em>innen der Direkt-Nachrichten bekommen keinerlei Information darüber, dass die Nachricht niemals gelesen wird! Als Empfänger</em>in erfährt man nicht, dass es Direkt-Nachrichten von den Accounts gab.</p>
<p dir="auto">Es gibt ein kleines Problem mit DM, wann man sie in einem Ausnahmefall doch sehen kann. Wenn ich mit jemandem in einer öffentlichen Unterhaltung bin und dieser Account als Antwort eine DM (<em>in</em> dieser öffentlichen Unterhaltung schickt), dann sehe ich die DM, wenn ich mir die komplette Unterhaltung anschaue. Das wurde als <a href="https://github.com/mastodon/mastodon/issues/9424" rel="noopener noreferrer">Bug</a> eingestuft und wird irgendwann mal korrigiert. </p>
<h1 dir="auto">Noch mehr Privatsphäre, bitte!</h1>
<p dir="auto">Es gibt noch ein paar wichtige Einstellungen, die euch noch mehr Privatsphäre bieten. </p>
<h2 dir="auto">Nicht in Suchen erscheinen</h2>
<p dir="auto">Wenn ihr mit eurem Profil nicht in Suchmaschinen auftauchen wollt, muss euer Profil speziell gekennzeichnet werden. Das ist ebenfalls in den Einstellungen (Weiteres) zu finden:</p>
<p dir="auto"><img src="https://write.tchncs.de/static/media/EF1BC51D-4122-323E-8B47-3ACC25389BF2.png" alt="Mastodon - Suchmaschinen-Indexierung festlegen"></p>
<p dir="auto">Wenn der Haken für diese Option gesetzt wird, verhält sich eure Mastodon-Instanz so, dass Suchmaschinen euer Profil nicht erfassen soll(t)en. Das kann etwas tricky sein. Wurde nämlich von einer Suchmaschine euer Profil bereits gefunden und ihr ändert das erst später, wird es eine Weile dauern (und hoffentlich überhaupt), bis ihr wieder aus dem Suchindex der Suchmaschine verschwindet. </p>
<h2 dir="auto">Innerhalb des Fediverse unsichtbar werden</h2>
<p dir="auto">Wenn ihr auch innerhalb des Fediverse weniger sichtbar sein wollt, gibt es dafür auch eine Einstellung. Leider wieder an einer anderen Stelle. Diese ist im <em>Profil -> Aussehen</em>:</p>
<p dir="auto"><img src="https://write.tchncs.de/static/media/2929E8D8-BB8C-AE7A-8D2D-FE87544D2E29.png" alt="Mastodon - Profilverzeichnis"></p>
<p dir="auto">Schaltet ihr die Option ab, werdet ihr innerhalb des Fediverse mit den Such- und Trendfunktionen nicht mehr gelistet. </p>
<h2 dir="auto">Meine Netzwerke ausblenden</h2>
<p dir="auto">Es kann dein Wunsch sein, nicht allen mitzuteilen, wem du folgst und wer dir folgt. Dann kannst du diese Ansicht komplett abschalten. In den Einstellungen findet sich das unter <em>Profil -> Aussehen</em>:</p>
<p dir="auto"><img src="https://write.tchncs.de/static/media/EC3474EC-D1EF-5220-A6B8-88C1C97DCB35.png" alt="Mastodon - Netzwerke"></p>
<p dir="auto">Ist die Option aktiviert, weiß niemand mehr, wem du folgst (und wer dir folgt), kann das aber ggf. aus deiner Interaktion ermitteln. </p>
<h1 dir="auto">Anmerkungen zur Privatsphäre im Fediverse</h1>
<p dir="auto">Privatsphäre hat nichts mit Datenschutz zu tun. Das wird bedauerlicherweise immer wieder verwechselt. Alle obigen Vorschläge, eure Privatsphäre zu etablieren, ist eine Vereinbarung in diesem sozialen Netzwerk, wie man eure Toots anderen Accounts präsentiert. Zwar schränken einige Optionen die Verteilung eurer Toots deutlich ein, aber alle eure Follower sind ggf. auf vielen verschiedenen Instanzen und eure Toots werden mit diesen Instanzen (anderen Servern) synchronisiert. </p>
<p dir="auto">Es ist wie E-Mail. Jede Mastodon-Instanz verwaltet Eingangs- und Ausgangs-Körbe für jeden Account und überträgt über diese Körbe die Nachrichten zu allen anderen Followern. Diese Ein- und Ausgangskörbe werden in Datenbanken verwaltet. Diese Datenbanken werden nach besten Wissen und Gewissen durch die Admins geschützt, aber die Daten darin sind unverschlüsselt (wie bei vielen Mail-Providern auch). Die Übertragung der Daten erfolgt übrigens immer <a href="https://de.wikipedia.org/wiki/Transport_Layer_Security" rel="noopener noreferrer">transportverschlüsselt</a>. D.h. auf dem Weg von einem Server zu einem anderen, kann niemand sehen, was für Daten übertragen werden. Es wird immer wieder überlegt, dass man zumindest Direkt-Nachrichten (das ist eine spezielle Sichtbarkeit der Nachrichten, die nur Accounts zugestellt wird, die erwähnt wurden) in den Ziel-Eingangskörben verschlüsselt abgelegt werden. Ob und wann die Funktion kommt, weiß ich aktuell nicht. </p>
<p dir="auto">Wenn ihr wirklich sensible Kommunikation zwischen anderen Menschen habt, nutzt dafür spezielle verschlüsselnde Messenger, die Nachrichten direkt von Messenger zu Messenger übertragen. </p>
<p dir="auto">Das Fediverse ist da aber bei dieser Frage nichts Besonderes ggü. Netzwerken wie Twitter, Facebook, Instagram oder was auch immer. Aber in allen Netzwerken gilt, dass es gesetzliche Bestimmungen gibt, an die sich auch die Admins eurer und anderer Instanzen halten müssen. </p>
]]><![CDATA[ORM, SQL und Driver Crates für Rust]]>https://write.tchncs.de/~/BeanDevRust/ORM,%20SQL%20und%20Driver%20Crates%20für%20Rust/2022-07-19T15:12:46.698541+00:00Aljoscha Rittnerhttps://write.tchncs.de/@/beandev/2022-07-19T15:12:46.698541+00:00<![CDATA[<h1 dir="auto">Einleitung</h1>
<p dir="auto">Dieser Blogartikel ist dafür gedacht, eine fortlaufende Liste an Crates zu beschreiben, die Zugriffe auf Persistenzen erlauben. Die Liste wird über die Zeit aktualisiert.</p>
<hr>
<h2 dir="auto">Treiber</h2>
<h3 dir="auto">PostgreSQL</h3>
<p dir="auto"><a href="https://www.postgresql.org/" rel="noopener noreferrer">Postgres</a> ist eine sehr beliebte RDBMS Datenbank mit objektrelationalen Prinzip (inklusive Table Inheritence und Function overloading). Dazu erlaubt Postgres die Unterstützung von komplexen Datentypen und bietet dafür eine sehr große Anzahl von Erweiterungen. Postgres ist Multi-Prozess-fähig (was aber mehr Speicherverbrauch bedeutet) und hat hohe Performance in Lesend- und Schreibzugriffen, die durch eine (nicht zu verändernde) ACID Storage Engine realisiert wird. Postgres ist sehr gut skalierbar und eignet ich hervorragend auch für den Enterprise Bereich. Der SQL Standard wird ausgezeichnet eingehalten. </p>
<ul dir="auto">
<li>Postgres (postgres)
<ul dir="auto">
<li>Rust docs: <a href="https://docs.rs/postgres/latest/postgres/" rel="noopener noreferrer">https://docs.rs/postgres/latest/postgres/</a></li>
<li>Repository: <a href="https://github.com/sfackler/rust-postgres" rel="noopener noreferrer">https://github.com/sfackler/rust-postgres</a></li>
<li>Async: Nein, <a href="https://docs.rs/postgres/latest/postgres/#ssltls-support" rel="noopener noreferrer">SSL/TLS</a>: Ja, Native: Ja</li>
</ul>
</li>
<li>Tokio PostgreSQL (tokio_postgres)
<ul dir="auto">
<li>Rust docs: <a href="https://docs.rs/tokio-postgres/latest/tokio_postgres/" rel="noopener noreferrer">https://docs.rs/tokio-postgres/latest/tokio_postgres/</a></li>
<li>Repository: <a href="https://github.com/sfackler/rust-postgres" rel="noopener noreferrer">https://github.com/sfackler/rust-postgres</a></li>
<li>Async: Ja (<a href="https://docs.rs/tokio-postgres/latest/tokio_postgres/index.html#pipelining" rel="noopener noreferrer">pipelining</a>), <a href="https://docs.rs/tokio-postgres/latest/tokio_postgres/index.html#ssltls-support" rel="noopener noreferrer">SSL/TLS</a>: Ja, Native: Ja</li>
</ul>
</li>
</ul>
<h3 dir="auto">SQLite</h3>
<p dir="auto"><a href="https://www.sqlite.org/index.html" rel="noopener noreferrer">SQLite</a> ist eine prozessinterne Datenbank (damit ist ein Client/Server Szenario nicht möglich) mit SQL-Unterstützung. Sie gehört inzwischen zu den meistverbreiteten RDBMS, insbesondere wegen ihrer Nutzung im Android Bereich als Standard-Persistenz für Apps. Aber auch in der Entwicklung ist SQLite sehr beliebt, weil sie In-Memory arbeiten kann. Damit ist sie ideal für Testumgebungen und Integrationstests. Sie wird auch immer dann eingesetzt, wenn Applikationen einfach mit einer relationalen DB ausgestattet werden sollen, wo ein Client/Server Szenario nicht benötigt wird. </p>
<ul dir="auto">
<li>Rust sqlite (rusqlite)
<ul dir="auto">
<li>Rust docs: <a href="https://docs.rs/rusqlite/latest/rusqlite/" rel="noopener noreferrer">https://docs.rs/rusqlite/latest/rusqlite/</a></li>
<li>Repository: <a href="https://github.com/rusqlite/rusqlite" rel="noopener noreferrer">https://github.com/rusqlite/rusqlite</a></li>
<li>Async: Nein, SSL/TLS: Nein (nicht notwendig, in-process), Native: Nein</li>
</ul>
</li>
</ul>
<h3 dir="auto">MySQL</h3>
<p dir="auto"><a href="https://www.mysql.com/de/" rel="noopener noreferrer">MySQL</a> ist eine sehr beliebte RDBMS Datenbank und in vielen Anwendungen immer noch die erste Wahl für die meisten Anwendungen, die eine SQL-Datenbank verwenden und Client/Server Szenarien benötigen. Zwar geht der Trend deutlich in Richtung von Postgres, aber als Single-Process DB und auch die Möglichkeit, diese In-Memory zu betreiben, macht sie für einige Szenarien attraktiver. MySQL ist stark optimiert auf Read-Access. Wird eine ausgewogene Performance für Read/Write benötigt, sollte man auf Postgres ausweichen. MySQL unterstützt aber eine hohe Anzahl von Storage Engines, die für viele Anwendungsszenarien individuell eingerichtet werden können. </p>
<ul dir="auto">
<li>MySQL (<a href="https://crates.io/crates/mysql" rel="noopener noreferrer">mysql</a>)
<ul dir="auto">
<li>Rust docs: <a href="https://docs.rs/mysql/latest/mysql/" rel="noopener noreferrer">https://docs.rs/mysql/latest/mysql/</a></li>
<li>Repository: <a href="https://github.com/blackbeam/rust-mysql-simple" rel="noopener noreferrer">https://github.com/blackbeam/rust-mysql-simple</a></li>
<li>Async: Nein, SSL/TLS: Ja (via <code>nativetls</code> oder <code>rustls</code>), Native: Ja</li>
</ul>
</li>
<li>MySQL Async (<a href="https://crates.io/crates/mysql_async/" rel="noopener noreferrer">mysql_async</a>)
<ul dir="auto">
<li>Rust docs: <a href="https://docs.rs/mysql_async/latest/mysql_async/" rel="noopener noreferrer">https://docs.rs/mysql_async/latest/mysql_async/</a></li>
<li>Repository: <a href="https://github.com/blackbeam/mysql_async" rel="noopener noreferrer">https://github.com/blackbeam/mysql_async</a></li>
<li>Async: Ja, SSL/TLS: Ja, Native: Ja</li>
</ul>
</li>
</ul>
<h3 dir="auto">MongoDB</h3>
<p dir="auto"><a href="https://www.mongodb.com/" rel="noopener noreferrer">MongoDB</a> ist eine Datenbank, die auf einem nicht relationalem Dokumentmodell basiert und somit unter die schemafreien, dokumentenorientierten No-SQL Datenbanken fällt. Die Dokumente werden als JSON strukturiert. </p>
<ul dir="auto">
<li>MongoDB (<a href="https://crates.io/crates/mongodb" rel="noopener noreferrer">mogodb</a>)
<ul dir="auto">
<li>Rust docs: <a href="https://docs.rs/mongodb/latest/mongodb/" rel="noopener noreferrer">https://docs.rs/mongodb/latest/mongodb/</a></li>
<li>Repository: <a href="https://github.com/mongodb/mongo-rust-driver" rel="noopener noreferrer">https://github.com/mongodb/mongo-rust-driver</a></li>
<li>Async: Ja (<code>tokio</code> oder <code>async-std</code>), SSL/TLS: Ja (<code>openssl</code>), Native: Ja</li>
</ul>
</li>
</ul>
<h3 dir="auto">MS SQL</h3>
<p dir="auto">Der <a href="https://www.microsoft.com/de-de/sql-server/" rel="noopener noreferrer">Microsoft SQL Server</a> ist ein relationales Datenbankmanagementsystem von Microsoft. </p>
<ul dir="auto">
<li>Tiberius TDS Client (<a href="https://crates.io/crates/tiberius" rel="noopener noreferrer">tiberius</a>)
<ul dir="auto">
<li>Rust docs: <a href="https://docs.rs/tiberius/latest/tiberius/" rel="noopener noreferrer">https://docs.rs/tiberius/latest/tiberius/</a></li>
<li>Repository: <a href="https://github.com/prisma/tiberius" rel="noopener noreferrer">https://github.com/prisma/tiberius</a></li>
<li>Async: Ja (<code>tokio</code>, <code>async-std</code>, <code>smol</code>), SSL/TLS: Ja (<code>native-tls</code>, <code>rustls</code>, <code>openssl</code>), Native: Ja</li>
</ul>
</li>
</ul>
<h3 dir="auto">Oracle</h3>
<p dir="auto"><a href="https://www.oracle.com/de/database/" rel="noopener noreferrer">Oracle Database</a> (auch Oracle Database Server, Oracle RDBMS) ist eine Datenbankmanagementsystem-Software des Unternehmens Oracle.</p>
<ul dir="auto">
<li>Sibyl (<a href="https://crates.io/crates/sibyl" rel="noopener noreferrer">sibyl</a>)
<ul dir="auto">
<li>Rust docs: <a href="https://docs.rs/sibyl/latest/sibyl/" rel="noopener noreferrer">https://docs.rs/sibyl/latest/sibyl/</a></li>
<li>Dokumentation: https://quietboil.github.io/sibyl/</li>
<li>Repository: <a href="https://github.com/quietboil/sibyl" rel="noopener noreferrer">https://github.com/quietboil/sibyl</a></li>
<li>Async: Ja (<code>tokio</code>, <code>async-std</code>, <code>actix</code>), SSL/TLS: Nein, Native: Ja</li>
</ul>
</li>
</ul>
<h3 dir="auto">Redis</h3>
<p dir="auto"><a href="https://redis.io/" rel="noopener noreferrer">Redis</a> ist eine In-Memory-Datenbank, die für die Erstellung von Caches, Worker Queues und Microservices verwendet werden kann. Das Redis-Crate bietet sowohl High-Level- als auch Low-Level-APIs. Alle Abfragen sind pipelined, was bedeutet, dass mehrere Abfragen gleichzeitig gesendet werden können.</p>
<ul dir="auto">
<li>Redis-rs (<a href="https://crates.io/crates/redis" rel="noopener noreferrer">redis</a>)
<ul dir="auto">
<li>Rust docs: <a href="https://docs.rs/redis/latest/redis/" rel="noopener noreferrer">https://docs.rs/redis/latest/redis/</a></li>
<li>Repository: <a href="https://github.com/redis-rs/redis-rs" rel="noopener noreferrer">https://github.com/redis-rs/redis-rs</a></li>
<li>Async: Ja (via <code>tokio</code> oder <code>async-std</code>, unterstützt <a href="https://docs.rs/redis/latest/redis/#pipelining" rel="noopener noreferrer">pipelining</a>), SSL/TLS: <a href="https://github.com/redis-rs/redis-rs#tls-support" rel="noopener noreferrer">Ja</a>, Native: Ja</li>
</ul>
</li>
</ul>
<h3 dir="auto">LevelDB</h3>
<p dir="auto"><a href="https://github.com/google/leveldb" rel="noopener noreferrer">LevelDB</a> wurde von Google entwickelt und ist ein reiner Single-Process Key-Value-Speicher.</p>
<ul dir="auto">
<li>Google LevelDB (<a href="https://crates.io/crates/leveldb" rel="noopener noreferrer">leveldb</a>)
<ul dir="auto">
<li>Rust docs: <a href="https://skade.github.io/leveldb/leveldb/" rel="noopener noreferrer">https://skade.github.io/leveldb/leveldb/</a></li>
<li>Repository: <a href="https://github.com/skade/leveldb" rel="noopener noreferrer">https://github.com/skade/leveldb</a></li>
<li>Async: Nein, SSL/TLS: Nein, Native: Nein</li>
</ul>
</li>
</ul>
<h3 dir="auto">MemCache</h3>
<p dir="auto"><a href="https://memcached.org/" rel="noopener noreferrer">Memcached</a> ist ein freies, quelloffenes, hochleistungsfähiges Caching-System für verteilte Speicherobjekte. <code>memcache</code> ist ein in reinem Rust geschriebener Memcached-Client. Er unterstützt mehrere Instanzen von Memcached. Einige Funktionen, einschließlich der automatischen JSON-Serialisierung und Komprimierung, sind noch nicht im Rust-Treiber verfügbar.</p>
<ul dir="auto">
<li>MemCache (<a href="https://crates.io/crates/memcache" rel="noopener noreferrer">memcache</a>)
<ul dir="auto">
<li>Rust docs: <a href="https://docs.rs/memcache/latest/memcache/" rel="noopener noreferrer">https://docs.rs/memcache/latest/memcache/</a></li>
<li>Repository: <a href="https://github.com/aisk/rust-memcache" rel="noopener noreferrer">https://github.com/aisk/rust-memcache</a></li>
<li>Async: Nein, SSL/TLS: Ja, Native: Ja</li>
</ul>
</li>
</ul>
<h3 dir="auto">Cassandra / ScyllaDB</h3>
<p dir="auto"><a href="https://cassandra.apache.org/_/index.html" rel="noopener noreferrer">Cassandra</a> ist eine verteilte, skalierbare NoSQL-Datenbank. <code>crdrs</code> ist ein Datenbanktreiber für Cassandra und <a href="https://www.scylladb.com/" rel="noopener noreferrer">ScyllaDB</a>.</p>
<ul dir="auto">
<li>CD-rs (<a href="https://crates.io/crates/cdrs" rel="noopener noreferrer">cdrs</a>)
<ul dir="auto">
<li>Rust docs: <a href="https://docs.rs/cdrs/latest/cdrs/" rel="noopener noreferrer">https://docs.rs/cdrs/latest/cdrs/</a></li>
<li>Repository: <a href="https://github.com/AlexPikalov/cdrs" rel="noopener noreferrer">https://github.com/AlexPikalov/cdrs</a></li>
<li>Async: Nein, SSL/TLS (via <code>nativetls</code>): Ja, Native: Ja</li>
</ul>
</li>
<li>CD-rs Async (<a href="https://crates.io/crates/cdrs-async" rel="noopener noreferrer">cdrs-async</a>)
<ul dir="auto">
<li>Rust docs: <a href="https://docs.rs/cdrs-async/0.1.0-alpha.0/cdrs_async/" rel="noopener noreferrer">https://docs.rs/cdrs-async/0.1.0-alpha.0/cdrs_async/</a></li>
<li>Repository: <a href="https://github.com/AlexPikalov/cdrs-async" rel="noopener noreferrer">https://github.com/AlexPikalov/cdrs-async</a></li>
<li>Async: Ja (via <code>async-std</code>), SSL/TLS: Ja (via <code>nativetls</code>), Native: Ja</li>
</ul>
</li>
</ul>
<h3 dir="auto">ElasticSearch</h3>
<p dir="auto"><a href="https://www.elastic.co/de/elasticsearch/" rel="noopener noreferrer">ElasticSearch</a> ist im Prinzip keine Datenbank, sondern ein invertierter Suchindex, basierend auf JSON Dokumenten.</p>
<ul dir="auto">
<li>Elasticsearch-rs (<a href="https://crates.io/crates/elasticsearch" rel="noopener noreferrer">elasticsearch</a>)
<ul dir="auto">
<li>Rust docs: <a href="https://docs.rs/elasticsearch/latest/elasticsearch/" rel="noopener noreferrer">https://docs.rs/elasticsearch/latest/elasticsearch/</a></li>
<li>Repository: <a href="https://github.com/elastic/elasticsearch-rs" rel="noopener noreferrer">https://github.com/elastic/elasticsearch-rs</a></li>
<li>Async: Ja (via <code>tokio</code>), SSL/TLS: Ja (via <code>nativetls</code> oder <code>rustls</code>), Native: Ja</li>
</ul>
</li>
</ul>
<h3 dir="auto">OpenSearch</h3>
<p dir="auto"><a href="https://opensearch.org/" rel="noopener noreferrer">OpenSearch</a> ist ein Fork von ElasticSearch und ist damit auch keine Datenbank, sondern ein invertierter Suchindex, basierend auf JSON Dokumenten.</p>
<ul dir="auto">
<li>Opensearch-rs (<a href="https://crates.io/crates/opensearch" rel="noopener noreferrer">opensearch</a>)
<ul dir="auto">
<li>Rust docs: <a href="https://docs.rs/opensearch/latest/opensearch/" rel="noopener noreferrer">https://docs.rs/opensearch/latest/opensearch/</a></li>
<li>Repository: <a href="https://github.com/opensearch-project/opensearch-rs" rel="noopener noreferrer">https://github.com/opensearch-project/opensearch-rs</a></li>
<li>Async: Ja (via <code>tokio</code>), SSL/TLS: Ja (via <code>nativetls</code> oder <code>rustls</code>), Native: Ja</li>
</ul>
</li>
</ul>
<h3 dir="auto">ODBC</h3>
<p dir="auto">Die ODBC-Schnittstelle (Microsoft Open Database Connectivity) ist eine C-Programmiersprachenschnittstelle, mit der Anwendungen auf Daten aus einer Vielzahl von Datenbankverwaltungssystemen (Database Management Systems, DBMS) zugreifen können. ODBC ist eine Low-Level-Schnittstelle, die speziell für RDBMS entwickelt wurde.</p>
<p dir="auto">ODBC Treiber greifen also nicht auf eine spezifische Datenbank zu, sondern bieten eine API für ODBC-fähige Datenbanken.</p>
<ul dir="auto">
<li>ODBC-sys (<a href="https://crates.io/crates/odbc-sys" rel="noopener noreferrer">odbc-sys</a>)
<ul dir="auto">
<li>Rust docs: <a href="https://docs.rs/odbc-sys/latest/odbc_sys/" rel="noopener noreferrer">https://docs.rs/odbc-sys/latest/odbc_sys/</a></li>
<li>Repository: <a href="https://github.com/pacman82/odbc-sys" rel="noopener noreferrer">https://github.com/pacman82/odbc-sys</a></li>
<li>Async: Nein, SSL/TLS: Nein, Native: Nein (FFI)</li>
</ul>
</li>
<li>ODBC-api (<a href="https://crates.io/crates/odbc-api" rel="noopener noreferrer">odbc-sys</a>)
<ul dir="auto">
<li>Info: Abstraktions-Schicht zu ODBC-sys</li>
<li>Rust docs: <a href="https://docs.rs/odbc-api/latest/odbc_api/" rel="noopener noreferrer">https://docs.rs/odbc-api/latest/odbc_api/</a></li>
<li>Repository: <a href="https://github.com/pacman82/odbc-api" rel="noopener noreferrer">https://github.com/pacman82/odbc-api</a></li>
<li>Async: Nein, SSL/TLS: Nein, Native: Nein (FFI via ODBC-sys)</li>
</ul>
</li>
</ul>
<hr>
<h2 dir="auto">SQL Abstraktionen</h2>
<h3 dir="auto">Cornucopia (<a href="https://crates.io/crates/cornucopia" rel="noopener noreferrer">cornucopia</a>)</h3>
<p dir="auto">Cornucopia ist ein kleines CLI-Programm, das auf <code>tokio-postgres</code> aufbaut, um PostgreSQL-Workflows in Rust zu erleichtern.</p>
<p dir="auto">Cornucopia wandelt PostgreSQL-Abfragen in Rust Code um. Jede Abfrage wird gegen das Schema vorbereitet, um sicherzustellen, dass die Statements gültiges SQL sind. Diese Prepared Statements werden dann verwendet, um typ-geprüften Rust-Code für die Abfragen zu erzeugen.</p>
<p dir="auto">Das Framework ist kein OR-Mapper, sondern ein Sourcecode Generator für SQL Statements.</p>
<p dir="auto">Hat man folgendes Schema:</p>
<pre><code dir="auto"><span class="source"><span class=""><span class="keyword">CREATE</span> <span class="keyword">TABLE</span> <span class="entity name function">Author</span></span> (
Id <span class="storage type">SERIAL</span> <span class="keyword">NOT NULL</span>,
Name <span class="storage type">VARCHAR</span>(<span class="constant numeric">70</span>) <span class="keyword">NOT NULL</span>,
Country <span class="storage type">VARCHAR</span>(<span class="constant numeric">100</span>) <span class="keyword">NOT NULL</span>,
<span class="storage modifier">PRIMARY KEY</span>(Id)
);
</span></code></pre>
<p dir="auto">Und diese annotierte SQL Anweisung:</p>
<pre><code dir="auto"><span class="source"><span class="comment"><span class="punctuation comment">--</span>! authors()*
</span><span class="keyword">SELECT</span> <span class="keyword operator">*</span> <span class="keyword">FROM</span> Author;
</span></code></pre>
<p dir="auto">generiert sich folgende Rust Funktion:</p>
<pre><code dir="auto"><span class="source"><span class="storage modifier">pub</span> async <span class="function"><span class="function"><span class="storage type function">fn</span> </span><span class="entity name function">authors</span></span><span class=""><span class="punctuation"><</span>T<span class="punctuation">:</span> GenericClient<span class="punctuation">></span></span><span class="function"><span class="function"><span class="punctuation">(</span><span class="variable parameter">client</span><span class="punctuation">:</span> <span class="keyword operator">&</span>T</span><span class="function"><span class="function"><span class="punctuation">)</span></span></span></span><span class="function"> <span class="function"><span class="punctuation">-></span> <span class="">Result<span class="punctuation"><</span><span class="">Vec<span class="punctuation"><</span><span class="punctuation">(</span><span class="storage type">i32</span>, String, String<span class="punctuation">)</span><span class="punctuation">></span></span>, Error<span class="punctuation">></span></span></span> </span><span class="function"><span class=""><span class="punctuation">{</span>
<span class="storage type">let</span> stmt <span class="keyword operator">=</span> client
.<span class="support function">prepare</span><span class=""><span class="punctuation">(</span>
<span class="string"><span class="punctuation string">"</span>SELECT
*
FROM
Author;
<span class="punctuation string">"</span></span><span class="punctuation">,</span>
</span><span class=""><span class="punctuation">)</span></span>
.await<span class="keyword operator">?</span><span class="punctuation">;</span>
<span class="storage type">let</span> res <span class="keyword operator">=</span> client
.<span class="support function">query_raw</span><span class=""><span class="punctuation">(</span><span class="keyword operator">&</span>stmt<span class="punctuation">,</span> <span class="">std<span class="punctuation">::</span></span><span class="">iter<span class="punctuation">::</span></span><span class="">empty<span class="punctuation">::</span></span><span class=""><span class="punctuation"><</span><span class="storage type">i32</span><span class="punctuation">></span></span><span class=""><span class="punctuation">(</span></span><span class=""><span class="punctuation">)</span></span></span><span class=""><span class="punctuation">)</span></span>
.await<span class="keyword operator">?</span>
.<span class="support function">map</span><span class=""><span class="punctuation">(</span><span class="function"><span class="function"><span class="punctuation">|</span></span></span><span class="function"><span class="function"><span class="variable parameter">res</span><span class="punctuation">|</span></span> </span><span class="function"><span class=""><span class="punctuation">{</span>
res.<span class="support function">map</span><span class=""><span class="punctuation">(</span><span class="function"><span class="function"><span class="punctuation">|</span></span></span><span class="function"><span class="function"><span class="variable parameter">res</span><span class="punctuation">|</span></span> </span><span class="function"><span class=""><span class="punctuation">{</span>
<span class="storage type">let</span> return_value_0<span class="punctuation">:</span> <span class="storage type">i32</span> <span class="keyword operator">=</span> res.<span class="support function">get</span><span class=""><span class="punctuation">(</span><span class="constant numeric">0</span></span><span class=""><span class="punctuation">)</span></span><span class="punctuation">;</span>
<span class="storage type">let</span> return_value_1<span class="punctuation">:</span> <span class="support type">String</span> <span class="keyword operator">=</span> res.<span class="support function">get</span><span class=""><span class="punctuation">(</span><span class="constant numeric">1</span></span><span class=""><span class="punctuation">)</span></span><span class="punctuation">;</span>
<span class="storage type">let</span> return_value_2<span class="punctuation">:</span> <span class="support type">String</span> <span class="keyword operator">=</span> res.<span class="support function">get</span><span class=""><span class="punctuation">(</span><span class="constant numeric">2</span></span><span class=""><span class="punctuation">)</span></span><span class="punctuation">;</span>
<span class=""><span class="punctuation">(</span>return_value_0<span class="punctuation">,</span> return_value_1<span class="punctuation">,</span> return_value_2</span><span class=""><span class="punctuation">)</span></span>
</span><span class=""><span class="punctuation">}</span></span></span></span><span class=""><span class="punctuation">)</span></span>
</span><span class=""><span class="punctuation">}</span></span></span></span><span class=""><span class="punctuation">)</span></span>
.<span class="support function">try_collect</span><span class=""><span class="punctuation">(</span></span><span class=""><span class="punctuation">)</span></span>
.await<span class="keyword operator">?</span><span class="punctuation">;</span>
<span class="support type">Ok</span><span class=""><span class="punctuation">(</span>res</span><span class=""><span class="punctuation">)</span></span>
</span><span class=""><span class="punctuation">}</span></span></span>
</span></code></pre>
<p dir="auto">Damit wird eine Menge Boilerplate Implementierung an den Code-Generator ausgelagert und in der Entwicklung muss man sich nicht mehr damit rumschlagen.</p>
<h4 dir="auto">Bewertung</h4>
<p dir="auto">Cornucopia fokussiert sich auf die Codegenerierung durch annotierte SQL Statements und macht dies ausschließlich für <code>tokio-postgres</code>. Vorteil ist, dass man keine Makros hat und der generierte Code exakt zum DB Schema passt, Async Tokio von Haus aus beherrscht und Connection Pooling via deadpool-postgress einbindet. Damit ist es nur für ganz bestimmte Lösungen geeignet, erfreut sich aber einer begeisterten Anhängerschaft. </p>
<h4 dir="auto">Fähigkeiten</h4>
<ul dir="auto">
<li>Treiber
<ul dir="auto">
<li>Tokio PostgreSQL (Rust safe)</li>
</ul>
</li>
<li>Async
<ul dir="auto">
<li>Ja (<a href="https://crates.io/crates/tokio" rel="noopener noreferrer">tokio</a>)</li>
</ul>
</li>
<li>TLS/SSL
<ul dir="auto">
<li>Über den Tokio PostgreSQL Treiber</li>
</ul>
</li>
</ul>
<h4 dir="auto">Ressourcen</h4>
<ul dir="auto">
<li>Crate: <a href="https://crates.io/crates/cornucopia" rel="noopener noreferrer">https://crates.io/crates/cornucopia</a></li>
<li>Repository: <a href="https://github.com/cornucopia-rs/cornucopia" rel="noopener noreferrer">https://github.com/cornucopia-rs/cornucopia</a></li>
<li>CLI Dokumentation: <a href="https://github.com/cornucopia-rs/cornucopia/blob/main/cli.md" rel="noopener noreferrer">https://github.com/cornucopia-rs/cornucopia/blob/main/cli.md</a></li>
</ul>
<h3 dir="auto">SQLx</h3>
<p dir="auto">SQLx ist eine SQL Abstraktions-Bibliothek, um sichere SQL Abfragen, unabhängig der darunter liegenden Datenbank zu schreiben. Typischerweise sind die Abfragen laufzeit-dynamisch, da alle Bezeichner als Strings deklariert werden. SQLx überprüft nicht die Syntax der generierten SQL Statements. D.h. es ist durchaus möglich, fehlerhaften SQL mit SQLx zu generieren. Applikationen mit SQLx benötigen deswegen eine optimale Testabdeckung. Zusätzlich hat SQLx eine Statement Verifikation zur Compiler-Zeit. </p>
<p dir="auto">Die Abstraktion ist sehr Low-Level, wie das folgende Beispiel zeigt:</p>
<pre><code dir="auto"><span class="source"><span class=""><span class="punctuation">#</span><span class="punctuation">[</span><span class="variable">derive</span><span class=""><span class=""><span class="punctuation">(</span></span></span><span class=""><span class="">sqlx::FromRow</span></span><span class=""><span class=""><span class="punctuation">)</span></span></span><span class="punctuation">]</span></span>
<span class=""><span class="storage type">struct</span> </span><span class=""><span class="entity name">User</span> </span><span class=""><span class=""><span class="punctuation">{</span> <span class="variable">name</span><span class="punctuation">:</span> String, <span class="variable">id</span><span class="punctuation">:</span> <span class="storage type">i64</span> </span><span class=""><span class="punctuation">}</span></span></span>
<span class="storage type">let</span> <span class="storage modifier">mut</span> stream <span class="keyword operator">=</span> <span class="">sqlx<span class="punctuation">::</span></span><span class="">query_as<span class="punctuation">::</span></span><span class=""><span class="punctuation"><</span><span class="keyword operator">_</span>, User<span class="punctuation">></span></span><span class=""><span class="punctuation">(</span>
<span class="string"><span class="punctuation string">"</span>SELECT * FROM users WHERE email = ? OR name = ?<span class="punctuation string">"</span></span></span><span class=""><span class="punctuation">)</span></span>
.<span class="support function">bind</span><span class=""><span class="punctuation">(</span>user_email</span><span class=""><span class="punctuation">)</span></span>
.<span class="support function">bind</span><span class=""><span class="punctuation">(</span>user_name</span><span class=""><span class="punctuation">)</span></span>
.<span class="support function">fetch</span><span class=""><span class="punctuation">(</span><span class="keyword operator">&</span><span class="storage modifier">mut</span> conn</span><span class=""><span class="punctuation">)</span></span><span class="punctuation">;</span>
</span></code></pre>
<h4 dir="auto">Bewertung</h4>
<p dir="auto">SQLx ist ein ausgezeichnetes Framework zur flexiblen Erzeugung von SQL Statements, das kaum einschränkt und dazu eine große Treiberunterstützung (sogar mit zwei Rust Treibern) bietet sowie Async und TLS nutzen kann. Jenseits der Verifikation während des Compile-Vorgangs ist man aber auf sich und die eigene Testabdeckung angewiesen. Benötigt man eine zusätzliche Abstraktion, solle man sich SeaQuery oder gar SeaORM anschauen, die beide auf SQLx basieren. </p>
<h4 dir="auto">Fähigkeiten</h4>
<ul dir="auto">
<li>Treiber
<ul dir="auto">
<li>SQlite</li>
<li>PostgreSQL (Rust safe)</li>
<li>MySQL (Rust safe)</li>
<li>MS SQL</li>
</ul>
</li>
<li>Async
<ul dir="auto">
<li>Ja (<a href="https://crates.io/crates/actix" rel="noopener noreferrer">actix</a>, <a href="https://crates.io/crates/async-std" rel="noopener noreferrer">async-std</a> und <a href="https://crates.io/crates/tokio" rel="noopener noreferrer">tokio</a>)</li>
</ul>
</li>
<li>Transport Layer Security
<ul dir="auto">
<li>Für Postgres und MySQL</li>
</ul>
</li>
</ul>
<h4 dir="auto">Ressourcen</h4>
<ul dir="auto">
<li>Repository: <a href="https://github.com/launchbadge/sqlx" rel="noopener noreferrer">https://github.com/launchbadge/sqlx</a></li>
<li>Rust Doc: <a href="https://docs.rs/sqlx/latest/sqlx/" rel="noopener noreferrer">https://docs.rs/sqlx/latest/sqlx/</a></li>
</ul>
<h3 dir="auto">SeaQL / SeaQuery</h3>
<p dir="auto">SeaQuery ist ein dynamischer Query Builder, der SQLx nutzt. Damit bietet SeaQuery eine sichere Typisierung, als es SQLx bietet. Die Bezeichner der SQL Elemente werden über Enums mit Traits implementiert, indem die Enums zu den String-Bezeichnern mit pattern matching zugewiesen werden (was über ein derive automatisiert werden kann). Der QueryBuilder arbeitet dann nicht mehr mit Strings, sondern nur mit den Enums, die Anwendern schon zur Compile-Zeit eine Fehlerprüfung auf korrekte Bezeichner bietet. </p>
<p dir="auto">Eine Abfrage sieht dann so aus:</p>
<pre><code dir="auto"><span class="source"><span class="support">assert_eq!</span><span class=""><span class="punctuation">(</span>
<span class="">Query<span class="punctuation">::</span></span>select<span class=""><span class="punctuation">(</span></span><span class=""><span class="punctuation">)</span></span>
.<span class="support function">column</span><span class=""><span class="punctuation">(</span><span class="">Glyph<span class="punctuation">::</span></span>Id</span><span class=""><span class="punctuation">)</span></span>
.<span class="support function">from</span><span class=""><span class="punctuation">(</span><span class="">Glyph<span class="punctuation">::</span></span>Table</span><span class=""><span class="punctuation">)</span></span>
.<span class="support function">cond_where</span><span class=""><span class="punctuation">(</span>
<span class="">Cond<span class="punctuation">::</span></span>any<span class=""><span class="punctuation">(</span></span><span class=""><span class="punctuation">)</span></span>
.<span class="support function">add</span><span class=""><span class="punctuation">(</span>
<span class="">Cond<span class="punctuation">::</span></span>all<span class=""><span class="punctuation">(</span></span><span class=""><span class="punctuation">)</span></span>
.<span class="support function">add</span><span class=""><span class="punctuation">(</span><span class="">Expr<span class="punctuation">::</span></span>col<span class=""><span class="punctuation">(</span><span class="">Glyph<span class="punctuation">::</span></span>Aspect</span><span class=""><span class="punctuation">)</span></span>.<span class="support function">is_null</span><span class=""><span class="punctuation">(</span></span><span class=""><span class="punctuation">)</span></span></span><span class=""><span class="punctuation">)</span></span>
.<span class="support function">add</span><span class=""><span class="punctuation">(</span><span class="">Expr<span class="punctuation">::</span></span>col<span class=""><span class="punctuation">(</span><span class="">Glyph<span class="punctuation">::</span></span>Image</span><span class=""><span class="punctuation">)</span></span>.<span class="support function">is_null</span><span class=""><span class="punctuation">(</span></span><span class=""><span class="punctuation">)</span></span></span><span class=""><span class="punctuation">)</span></span>
</span><span class=""><span class="punctuation">)</span></span>
.<span class="support function">add</span><span class=""><span class="punctuation">(</span>
<span class="">Cond<span class="punctuation">::</span></span>all<span class=""><span class="punctuation">(</span></span><span class=""><span class="punctuation">)</span></span>
.<span class="support function">add</span><span class=""><span class="punctuation">(</span><span class="">Expr<span class="punctuation">::</span></span>col<span class=""><span class="punctuation">(</span><span class="">Glyph<span class="punctuation">::</span></span>Aspect</span><span class=""><span class="punctuation">)</span></span>.<span class="support function">is_in</span><span class=""><span class="punctuation">(</span><span class="support">vec!</span><span class=""><span class="punctuation">[</span><span class="constant numeric">3</span><span class="punctuation">,</span> <span class="constant numeric">4</span><span class="punctuation">]</span></span></span><span class=""><span class="punctuation">)</span></span></span><span class=""><span class="punctuation">)</span></span>
.<span class="support function">add</span><span class=""><span class="punctuation">(</span><span class="">Expr<span class="punctuation">::</span></span>col<span class=""><span class="punctuation">(</span><span class="">Glyph<span class="punctuation">::</span></span>Image</span><span class=""><span class="punctuation">)</span></span>.<span class="support function">like</span><span class=""><span class="punctuation">(</span><span class="string"><span class="punctuation string">"</span>A%<span class="punctuation string">"</span></span></span><span class=""><span class="punctuation">)</span></span></span><span class=""><span class="punctuation">)</span></span>
</span><span class=""><span class="punctuation">)</span></span>
</span><span class=""><span class="punctuation">)</span></span>
.<span class="support function">to_string</span><span class=""><span class="punctuation">(</span>PostgresQueryBuilder</span><span class=""><span class="punctuation">)</span></span><span class="punctuation">,</span>
<span class=""><span class="punctuation">[</span>
<span class="string"><span class="storage type string">r</span><span class="punctuation string">#</span>"SELECT "id" FROM "glyph"<span class="punctuation string">"#</span></span><span class="punctuation">,</span>
<span class="string"><span class="storage type string">r</span><span class="punctuation string">#</span>"WHERE<span class="punctuation string">"#</span></span><span class="punctuation">,</span>
<span class="string"><span class="storage type string">r</span><span class="punctuation string">#</span>"("aspect" IS NULL AND "image" IS NULL)<span class="punctuation string">"#</span></span><span class="punctuation">,</span>
<span class="string"><span class="storage type string">r</span><span class="punctuation string">#</span>"OR<span class="punctuation string">"#</span></span><span class="punctuation">,</span>
<span class="string"><span class="storage type string">r</span><span class="punctuation string">#</span>"("aspect" IN (3, 4) AND "image" LIKE 'A%')<span class="punctuation string">"#</span></span><span class="punctuation">,</span>
<span class="punctuation">]</span></span>
.<span class="support function">join</span><span class=""><span class="punctuation">(</span><span class="string"><span class="punctuation string">"</span> <span class="punctuation string">"</span></span></span><span class=""><span class="punctuation">)</span></span>
</span><span class=""><span class="punctuation">)</span></span><span class="punctuation">;</span>
</span></code></pre>
<h4 dir="auto">Bewertung</h4>
<p dir="auto">Wenn man sich nicht mit Schreibfehlern an zig verschiedenen Stellen im Sourecode rumschlagen will, ist SeaQuery eine sehr gute Abstraktion für SQLx. Damit wird die Deklaration der Bezeichner an einem zentralen Punkt der Schema-Definition festgelegt und man umgeht die fehlerträchtigen String Konstanten. Mit dem <a href="https://github.com/SeaQL/sea-query#iden" rel="noopener noreferrer"><code>Iden-derive</code>-Makro</a> wird das Mapping der Bezeichner sogar automatisiert. Ansonsten kann man mit SeaQuery alles machen, was man von SQLx gewohnt ist. Die zusätzliche Indirektion über die Enums ist zwar etwas aufwendiger, aber der Gewinn an korrekten Code ist damit aufgewogen. Aber auch für SeaQuery gilt, dass keinerlei Prüfung gegen echte DB Schemata gemacht wird. D.h., was als Deklaration festgelegt wird, muss nichts mit dem DB-Schema zur Laufzeit zu tun haben. Es erlaubt eine hohe Flexibilität, fordert aber auch weiterhin eine umfangreiche Testabdeckung.</p>
<h4 dir="auto">Fähigkeiten</h4>
<ul dir="auto">
<li>Treiber
<ul dir="auto">
<li>sqlx-mysql (Rust safe)</li>
<li>sqlx-postgres (Rust safe)</li>
<li>sqlx-sqlite</li>
<li>postgres</li>
<li>postgres-*</li>
<li>rusqlite</li>
</ul>
</li>
</ul>
<h4 dir="auto">Ressourcen</h4>
<ul dir="auto">
<li>Repository: <a href="https://github.com/SeaQL/sea-query" rel="noopener noreferrer">https://github.com/SeaQL/sea-query</a></li>
<li>Dokumentation: <a href="https://www.sea-ql.org/SeaORM/docs/index/" rel="noopener noreferrer">https://www.sea-ql.org/SeaORM/docs/index/</a></li>
</ul>
<hr>
<h2 dir="auto">Object Relational Modeling (ORM)</h2>
<p dir="auto">Es gibt nicht viele Multi-DB ORM Crates, aber die wenigen lassen sich gut produktiv einsetzen und sind auch keine Konkurrenz zueinander, sondern bilden ganz eigene Paradigmen ab. </p>
<p dir="auto">Ansonsten gibt es Projekte, wie Sand am Meer, die für spezifische Datenbanken ORM-Funktionalitäten anbieten. Diese habe ich hier aber nicht betrachtet.</p>
<h3 dir="auto">Diesel</h3>
<p dir="auto">Diesel ist ein statisches ORM Builder System, das darauf abzielt, schon zur Compiler-Zeit ein vollständiges OR-Mapping durchzuführen. Die Makros sind auf typische Spaltenzahlen für Tabellen konfigurierbar. Wenn man z.B. mehr als 32 Spalten benötigt, muss man die Konfiguration anpassen, was aber auch die Compiler-Zeit verlangsamt. </p>
<p dir="auto">Spalten werden als Structs modelliert und erhalten Traits, um die Predicates zu deklarieren. </p>
<p dir="auto">Abfragen in Diesel bieten eine gute Abstraktion:</p>
<pre><code dir="auto"><span class="source"><span class="storage type">let</span> versions <span class="keyword operator">=</span> <span class="">Version<span class="punctuation">::</span></span>belonging_to<span class=""><span class="punctuation">(</span>krate</span><span class=""><span class="punctuation">)</span></span>
.<span class="support function">select</span><span class=""><span class="punctuation">(</span>id</span><span class=""><span class="punctuation">)</span></span>
.<span class="support function">order</span><span class=""><span class="punctuation">(</span>num.<span class="support function">desc</span><span class=""><span class="punctuation">(</span></span><span class=""><span class="punctuation">)</span></span></span><span class=""><span class="punctuation">)</span></span>
.<span class="support function">limit</span><span class=""><span class="punctuation">(</span><span class="constant numeric">5</span></span><span class=""><span class="punctuation">)</span></span><span class="punctuation">;</span>
<span class="storage type">let</span> downloads <span class="keyword operator">=</span> version_downloads
.<span class="support function">filter</span><span class=""><span class="punctuation">(</span>date.<span class="support function">gt</span><span class=""><span class="punctuation">(</span>now <span class="keyword operator">-</span> <span class="constant numeric">90.</span><span class="support function">days</span><span class=""><span class="punctuation">(</span></span><span class=""><span class="punctuation">)</span></span></span><span class=""><span class="punctuation">)</span></span></span><span class=""><span class="punctuation">)</span></span>
.<span class="support function">filter</span><span class=""><span class="punctuation">(</span>version_id.<span class="support function">eq</span><span class=""><span class="punctuation">(</span><span class="support function">any</span><span class=""><span class="punctuation">(</span>versions</span><span class=""><span class="punctuation">)</span></span></span><span class=""><span class="punctuation">)</span></span></span><span class=""><span class="punctuation">)</span></span>
.<span class="support function">order</span><span class=""><span class="punctuation">(</span>date</span><span class=""><span class="punctuation">)</span></span>
.<span class="">load<span class="punctuation">::</span></span><span class=""><span class="punctuation"><</span>Download<span class="punctuation">></span></span><span class=""><span class="punctuation">(</span><span class="keyword operator">&</span>conn</span><span class=""><span class="punctuation">)</span></span><span class="keyword operator">?</span><span class="punctuation">;</span>
</span></code></pre>
<h4 dir="auto">Bewertung</h4>
<p dir="auto">Man findet wohl Diesel in fast jedem Softwareprojekt, dass Persistierung in den drei meistgenutzten RDBMS Systemen durchführt. Damit gibt es eine Menge Beispielcode, eine sehr stabile Codebasis und das statische OR Mapping ist zur Laufzeit unschlagbar schnell. Das erkauft man sich mit einigen fehlenden Features (wie beispielsweise Async) und ggf. eine lange Compiler-Zeit.</p>
<p dir="auto">Diesel eignet sich besonders für überschaubare Datenmodelle, die primär mit <a href="https://de.wikipedia.org/wiki/CRUD" rel="noopener noreferrer">CRUD</a> Aktionen auskommen und es einfache, <a href="https://de.wikipedia.org/wiki/Normalisierung_(Datenbank)" rel="noopener noreferrer">normalisierte Relationen</a> gibt. </p>
<h4 dir="auto">Fähigkeiten</h4>
<ul dir="auto">
<li>Treiber
<ul dir="auto">
<li>PostgreSQL</li>
<li>MySQL</li>
<li>SQLite</li>
</ul>
</li>
<li>Async
<ul dir="auto">
<li>Nein (Experimentell via https://github.com/weiznich/diesel_async - mit tokio postgres)</li>
</ul>
</li>
<li>Transport Layer Security (TLS)
<ul dir="auto">
<li>Nein</li>
</ul>
</li>
</ul>
<h4 dir="auto">Ressourcen</h4>
<ul dir="auto">
<li>Homepage: <a href="https://diesel.rs/" rel="noopener noreferrer">https://diesel.rs/</a></li>
<li>Repository: <a href="https://github.com/diesel-rs/diesel" rel="noopener noreferrer">https://github.com/diesel-rs/diesel</a></li>
<li>Dokumentation: <a href="https://diesel.rs/guides/getting-started" rel="noopener noreferrer">https://diesel.rs/guides/getting-started</a></li>
<li>Rust-Doc: <a href="https://docs.diesel.rs/1.4.x/diesel/index.html" rel="noopener noreferrer">https://docs.diesel.rs/1.4.x/diesel/index.html</a></li>
</ul>
<h3 dir="auto">SeaORM</h3>
<p dir="auto">SeaORM ist jünger als Diesel und basiert auf SeaQuery und SQLx. Damit ist SeaORM ein dynamisches ORM System, dass zur Laufzeit Objekt-Relationen und das Mapping generiert. Wenn man mehr Flexibilität benötigt, kann man SeaQuery benutzen, welches eine SQL Abstraktion bietet, die nicht an ein OR Mapping typisiert ist.</p>
<p dir="auto">SeaORM nutzt Enums, um Spalten zu modellieren. </p>
<p dir="auto">SeaORM und SeaQuery können gemeinsam genutzt werden. </p>
<h4 dir="auto">Bewertung</h4>
<p dir="auto">Man kann SeaORM nicht wirklich mit Diesel vergleichen, da sie unterschiedliche Ansätze verfolgen. Wenn aber einen schnellen Compilerlauf für sehr große Datenmodelle benötigt und man zudem (eben aufgrund des großen Datenmodells) eine hohe Flexibilität an Abfragemöglichkeiten benötigt, wenn man nicht mit einem statischen Datenmodell arbeiten kann, dann ist SeaORM unschlagbar. Das erkauft man sich mit potenziellen Laufzeitfehlern, die man gut mit Tests abdecken sollte. Auch da bietet SeaORM eine gute Unterstützung. Überdies bietet SeaORM von Haus aus eine hervorragende Dokumentation.</p>
<p dir="auto">SeaORM ist sinnvoll für große Datenbank-Schemata, mit sehr vielen Tabellen und Attributen, die mit komplexen Abfragen verknüpft werden müssen. Ist das ORM noch zu statisch, kann mit SeaQuery jedes erdenkliche SQL Statement gebaut werden. SeaORM ist alternativlos, wenn man mit der gleichen Applikation auf unterschiedlichen Schemata arbeiten will.</p>
<h4 dir="auto">Fähigkeiten</h4>
<ul dir="auto">
<li>Treiber
<ul dir="auto">
<li>SQlite (via <code>sqlx-sqlite</code>)</li>
<li>PostgreSQL (via <code>sqlx-postgres</code>, Rust safe)</li>
<li>MySQL (via <code>sqlx-mysql</code>, Rust safe)</li>
<li>MS SQL - Unbekannt, SQLx unterstützt es, in der SeaORM Doku wird es nicht erwähnt</li>
</ul>
</li>
<li>Async
<ul dir="auto">
<li>Ja (<a href="https://crates.io/crates/actix" rel="noopener noreferrer">actix</a>, <a href="https://crates.io/crates/async-std" rel="noopener noreferrer">async-std</a> und <a href="https://crates.io/crates/tokio" rel="noopener noreferrer">tokio</a>)</li>
</ul>
</li>
<li>Transport Layer Security (TLS)
<ul dir="auto">
<li>Für Postgres und MySQL (über SQLx)</li>
</ul>
</li>
</ul>
<h4 dir="auto">Ressourcen</h4>
<ul dir="auto">
<li>Homepage: <a href="https://www.sea-ql.org/SeaORM/" rel="noopener noreferrer">https://www.sea-ql.org/SeaORM/</a></li>
<li>Repository: <a href="https://github.com/SeaQL/sea-orm" rel="noopener noreferrer">https://github.com/SeaQL/sea-orm</a></li>
<li>Dokumentation: <a href="https://www.sea-ql.org/SeaORM/docs/index/" rel="noopener noreferrer">https://www.sea-ql.org/SeaORM/docs/index/</a></li>
</ul>
<h3 dir="auto">rustorm</h3>
<p dir="auto">Das Framework hat sich auf das Data Access Object Mapping von SQL Ergebnissen spezialisiert und unterstützt damit die Übertragung der SQL Resultsets in Structs. Binding von Abfrage-Parameter besteht wohl, aber es gibt keine Beispiele.</p>
<h4 dir="auto">Bewertung</h4>
<p dir="auto">Die SQL Abfragen sind sehr Low-Level. Ein Guide oder umfangreiche Dokumentation gibt es nicht, aber die Möglichkeiten sind sehr fokussiert, sodass man sich schnell einarbeiten kann. Transaktionen werden wohl nicht unterstützt. Für größere Projekte ist rustorm eher nicht zu empfehlen. </p>
<h4 dir="auto">Fähigkeiten</h4>
<ul dir="auto">
<li>Treiber
<ul dir="auto">
<li>SQlite</li>
<li>PostgreSQL</li>
<li>MySQL</li>
</ul>
</li>
</ul>
<h4 dir="auto">Ressourcen</h4>
<ul dir="auto">
<li>Homepage: -</li>
<li>Repository: <a href="https://github.com/ivanceras/rustorm" rel="noopener noreferrer">https://github.com/ivanceras/rustorm</a></li>
<li>Rust-Doc: <a href="https://docs.rs/rustorm/0.20.0/rustorm/" rel="noopener noreferrer">https://docs.rs/rustorm/0.20.0/rustorm/</a></li>
</ul>
]]><![CDATA[Multi-Instance, Multi-Account - MastodonHelper mit einem kleinen Update]]>https://write.tchncs.de/~/BeanDevMastodon/Multi-Instance,%20Multi-Account%20-%20MastodonHelper%20mit%20einem%20kleinen%20Update/2022-06-15T08:40:41.589172+00:00Aljoscha Rittnerhttps://write.tchncs.de/@/beandev/2022-06-15T08:40:41.589172+00:00<![CDATA[<p dir="auto">Für einen Test auf der Mastodon-Instanz, auf der ich beheimatet bin, musste ich Toots über einen bestimmten Zeitraum erstellen. Das wollte ich nicht mit meinem regulären Account machen, also habe ich einen neuen Account angelegt (und den als Bot gekennzeichnet). Die Sichtbarkeit der Toots war nicht wichtig (daher habe ich die nicht gelistet) und Follower waren auch nicht wichtig.</p>
<p dir="auto">Nun war aber der MastodonHelper zwar grundsätzlich mal dafür angedacht mehr als einen Account nutzen zu können, aber es fehlten noch die passenden Implementationen.</p>
<p dir="auto">Das habe ich mit dem <a href="https://codeberg.org/beandev/mastodon-playground/commit/77cc4eeb27ff003d18a56a71fbb6c4d85701f142" rel="noopener noreferrer">letzten Commit</a> erledigt.</p>
<p dir="auto">In der <a href="https://codeberg.org/beandev/mastodon-playground/src/commit/77cc4eeb27ff003d18a56a71fbb6c4d85701f142/mastobot-config.yaml" rel="noopener noreferrer">mastobot-config.yaml</a> kann man nun unter dem Abschnitt <code>instances</code> nicht nur <code>default</code> konfigurieren, sondern mehrere Abschnitte hinterlegen.</p>
<p dir="auto">Das kann so aussehen:</p>
<pre><code dir="auto"><span class="source"><span class="string"><span class="entity name tag">instances</span></span><span class="punctuation">:</span>
<span class="string"><span class="entity name tag">default</span></span><span class="punctuation">:</span>
<span class="string"><span class="entity name tag">url</span></span><span class="punctuation">:</span> <span class="string"><span class="entity name tag">https://social.tchncs.de</span></span>
<span class="string"><span class="entity name tag">username</span></span><span class="punctuation">:</span> <span class="constant language">~</span>
<span class="string"><span class="entity name tag">login</span></span><span class="punctuation">:</span> <span class="constant language">true</span>
<span class="string"><span class="entity name tag">mybot_on_tchncs</span></span><span class="punctuation">:</span>
<span class="string"><span class="entity name tag">url</span></span><span class="punctuation">:</span> <span class="string"><span class="entity name tag">https://social.tchncs.de</span></span>
<span class="string"><span class="entity name tag">username</span></span><span class="punctuation">:</span> <span class="string"><span class="entity name tag">mybot@myprovider.de</span></span>
<span class="string"><span class="entity name tag">login</span></span><span class="punctuation">:</span> <span class="constant language">true</span>
</span></code></pre>
<p dir="auto">Die Default Konfig bleibt, wie ihr es eingerichtet habt. Dazu können beliebig weitere Instanzen mit Usern hinzugefügt werden.</p>
<p dir="auto">Das muss man aber dann auch explizit im Python Script dann so aufrufen. Dazu gibt es den neuen Parameter instance_id (der mit <code>default</code> vorbelegt ist):</p>
<pre><code dir="auto"><span class="source"><span class=""><span class="">mastodon</span></span> <span class="keyword operator">=</span> <span class=""><span class=""><span class="">mastobot</span><span class="punctuation">.</span><span class="">MastodonHelper</span><span class="punctuation">.</span></span><span class=""><span class="variable function">open_or_create_app</span></span><span class="punctuation">(</span><span class="">
<span class="variable parameter">config_yaml</span><span class="keyword operator">=</span><span class="string"><span class="string"><span class="punctuation string">'</span></span></span><span class="string"><span class="string">mastobot-config.yaml<span class="punctuation string">'</span></span></span><span class="punctuation">,</span>
<span class="variable parameter">instance_id</span><span class="keyword operator">=</span><span class="string"><span class="string"><span class="punctuation string">"</span></span></span><span class="string"><span class="string">mybot_on_tchncs<span class="punctuation string">"</span></span></span><span class="punctuation">,</span>
<span class="variable parameter">login</span><span class="keyword operator">=</span><span class="constant language">True</span>
</span><span class="punctuation">)</span></span>
</span></code></pre>
<p dir="auto">Da <code>login=True</code> gesetzt ist, wird ein Account-Login über den Username gefordert. Ihr meldet euch im Terminal also einmal an. Der Access-Token eurer Anmeldung wird dann im home-Ordner gespeichert. Das bedeutet, bei weiteren Aufrufen des Script gibt es keine Interaktion mehr: das Script meldet sich automatisch mit dem Access-Token an.</p>
<p dir="auto">Ich musste dafür auch die Dateinamen der Dateien ändern, die die <code>secrets</code> und <code>tokens</code> speichern. Das passt aber weiter zu dem bisherigen Verhalten von dem MastodonHelper.</p>
<p dir="auto">Man kann also weiterhin auch den einfachen Aufruf nutzen (was die default-Konfiguration nutzt):</p>
<pre><code dir="auto"><span class="source"><span class=""><span class="">mastodon</span></span> <span class="keyword operator">=</span> <span class=""><span class=""><span class="">mastobot</span><span class="punctuation">.</span><span class="">MastodonHelper</span><span class="punctuation">.</span></span><span class=""><span class="variable function">open_or_create_app</span></span><span class="punctuation">(</span><span class="">
<span class="variable parameter">config_yaml</span><span class="keyword operator">=</span><span class="string"><span class="string"><span class="punctuation string">'</span></span></span><span class="string"><span class="string">mastobot-config.yaml<span class="punctuation string">'</span></span></span><span class="punctuation">,</span>
<span class="variable parameter">login</span><span class="keyword operator">=</span><span class="constant language">True</span>
</span><span class="punctuation">)</span></span>
</span></code></pre>
]]><![CDATA[Migration.py - Migration mit Scripten - Dokumente im Index ändern]]>https://write.tchncs.de/~/BeanDevOpenSearch/Migration.py%20-%20Migration%20mit%20Scripten%20-%20Dokumente%20im%20Index%20ändern/2022-06-10T13:54:11.914603+00:00Aljoscha Rittnerhttps://write.tchncs.de/@/beandev/2022-06-10T13:54:11.914603+00:00<![CDATA[<h1 dir="auto">Einleitung</h1>
<p dir="auto">Manchmal hat man in der Vergangenheit eine Entscheidung getroffen, die sich für den Fortschritt eines Projektes nicht hilfreich sind. Algorithmen passen nicht, Frameworks müssen geändert werden. Implementationen erweisen sich nicht als praxistauglich. Das Beste ist, wenn man das erkennt, sollte man frühzeitig Abhilfe schaffen und diese “Technical Debts” beseitigen. Ansonsten türmen sie sich zu einem riesigen Berg auf und alles, was ein Softwareprojekt ausmacht, scheitert irgendwann daran, dass es nicht mehr zu warten ist.</p>
<p dir="auto">Es gibt verschiedene Stufen der Komplexität von Veränderungen. Wenn es nur den Sourcecode lokal betrifft, den man nicht ausgerollt hat, ist es simpel, die Probleme zu beseitigen, die man in der Phase erkannt hat. Ist die Software schon ausgerollt, muss man einen vernünftigen Update-Prozess haben, der die Dateien austauscht.</p>
<p dir="auto">Wenn man aber Software mit Daten-Persistenzen implementiert und man selber keine Hoheit über die Daten hat, bewegt man sich auf recht hohem Niveau, das meistern zu müssen. </p>
<p dir="auto">Bisher haben wir in OpenSearch nur das Mapping manipuliert. Wir haben uns entschieden, dass wir immer neue Indexe mit neuen Versionen anlegen, damit es möglichst einfach wird. Zudem haben wir immer ein Backup der Daten in dem Index. Faktisch ist es aber so, dass wir mit einem Mapping nie die Daten im “_source” Bereich des Dokumentes mit der Migration geändert hatten. Das kann aber durchaus die Anforderung sein. </p>
<p dir="auto">Betrachten wir OpenSearch als Such-Index für Quelldaten, die in ihrer Ursprungspersistenz nicht in erwartbarer Geschwindigkeit durchsucht werden können, ist der Such-Index mit seinen Dokumenten eine synchron gehaltene Kopie der Attribute, die für die Suche relevant sind. Damit öffnen sich unmittelbar mehrere Szenarien, wenn wir von Migration sprechen:</p>
<ol dir="auto">
<li>Das Mapping der Such-Attribute wird geändert, bzw. optimiert</li>
<li>Die Dokument-Such-Attribute müssen anders gespeichert werden</li>
<li>Es kommen neue Such-Attribute hinzu oder es werden welche entfernt</li>
</ol>
<p dir="auto">Punkt 1 können wir bereits. Punkt 2 ist eine neue Anforderung, die ich implementieren möchte. Der dritte Punkt ist eigentlich Punkt 1 plus einer Neuübertragung der Dokumente, auf die diese Änderung zutrifft.</p>
<p dir="auto">Zu Punkt 2 gibt es zwei Lösungen:</p>
<ol dir="auto" start="2">
<li>…
<ol dir="auto">
<li>Man fügt ein weiteres Mapping für das gleiche Such-Attribut hinzu und kopiert den Wert transformiert in ein neues Dokument-Attribut</li>
<li>Man ändert das Mapping des bestehenden Attributs und manipuliert bestehende Werte und speichert sie unter demselben Attribut-Namen</li>
</ol>
</li>
</ol>
<p dir="auto">Technisch sind beide Methoden valide und können 1:1 implementiert werden. Aber 2.1 hat den Nachteil, dass man sich ggf. bei der Implementation der Suchabfragen vertun kann und je mehr Attribute indiziert werden, desto mehr Speicherplatz und Arbeitsspeicher kostet es. </p>
<p dir="auto">Grundsätzlich, egal wie man sich entscheidet, könnte man das Ändern von Dokument-Attributen auch mit dem kompletten Neuaufbau eines Index bewerkstelligen. Also man legt eine neue Version an und überträgt alle Dokumente neu und bei der Übertragung werden die geänderten Werte so übertragen, wie es die neue Anforderung will.</p>
<p dir="auto">Es gibt zwei Sachen, die das ggf. verhindern könnten:</p>
<ol dir="auto">
<li>Die Ursprungsdaten liegen nicht mehr vor</li>
<li>Das Übertragen der Ursprungsdaten ist unverhältnismäßig aufwendig.</li>
</ol>
<p dir="auto">Deswegen möchte ich gerne für die Migration diese Methode der Datenmanipulation von Dokumenten direkt im Index implementieren. OpenSearch bietet dafür ein Scripting an, um diese Art der Änderung von Daten ohne Roundtrip zum Client durchführen zu müssen.</p>
<h1 dir="auto">Migration mit semantischen Versionen</h1>
<p dir="auto">Eine meiner Fehlentscheidungen war, dass ich die Versionen der Scripte als <code>float</code> speicherte. D.h. im <code>migration_history</code> Index ist die Version als Zahl, diese bedauerlicherweise auch untypisch notiert.</p>
<p dir="auto">Eine Version <code>01.001</code> bedeutet eigentlich <code>1.1</code> und nicht <code>1.001</code>. Jeder Abschnitt einer semantischen Version ist ein Integer und führende Nullen sind zu ignorieren. Das triviale <code>float(version)</code> erlaubt also sowieso nur zwei Versions-Ebenen und zum anderen wird die zweite Stufe auch noch falsch gespeichert.</p>
<p dir="auto">Dieses Erbstück meiner Faulheit, will ich entfernen und das bedeutet, dass der <code>migration_history</code> Index selbst migriert werden muss und ich auch keine Quelldaten habe, um die History neu aufzubauen (ja doch, die der Version 0.0 - aber nur in OpenSearch gespeichert). </p>
<p dir="auto">Ich muss also zum einen das Mapping von <code>migration_ìndex</code> ändern, zum anderen muss die Implementation angepasst werden. Abschließend müssen bestehende History-Datensätze geändert werden.</p>
<p dir="auto">Da das alles ziemlich tiefgreifende Änderungen sind, habe ich mir weitere Hilfsfunktionen gebaut. Zum Beispiel in <a href="https://codeberg.org/beandev/mastodon-playground/src/commit/a87c6e0ba53258cf39d833a499c6491bdb3e6879/oshelper/OpenSearchBackup.py" rel="noopener noreferrer">OpenSearchBackup.py</a> das Backup eines bestehenden Indexes in eine Sicherungskopie. Das Restore dazu. Außerdem in <a href="https://codeberg.org/beandev/mastodon-playground/src/commit/a87c6e0ba53258cf39d833a499c6491bdb3e6879/os-reset-migration.py" rel="noopener noreferrer">os-reset-migration.py</a> eine Funktion, die eine komplette Migration vollständig löscht (um von Vorne anfangen zu können).</p>
<p dir="auto">Das Backup/Restore und Reset bespreche ich nicht gesondert. Das sollte man sich im <a href="https://codeberg.org/beandev/mastodon-playground/commit/a87c6e0ba53258cf39d833a499c6491bdb3e6879" rel="noopener noreferrer">aktuellen Commit</a> anschauen. Die Funktionen sind nicht wirklich kompliziert, aber nehmen einem die manuelle Arbeit deutlich ab.</p>
<p dir="auto">Das <a href="https://codeberg.org/beandev/mastodon-playground/src/commit/a87c6e0ba53258cf39d833a499c6491bdb3e6879/os/migration_history/V01.000__VersionToKeyword.json" rel="noopener noreferrer">geänderte Mapping für <code>migration_history</code></a> ist einfach:</p>
<pre><code dir="auto"><span class="source">...
<span class="string"><span class="punctuation string">"</span>mappings<span class="punctuation string">"</span></span>: <span class=""><span class="punctuation">{</span>
<span class=""><span class="string"><span class="punctuation string">"</span>properties<span class="punctuation string">"</span></span></span><span class=""><span class="punctuation">:</span> <span class=""><span class="punctuation">{</span>
<span class=""><span class="string"><span class="punctuation string">"</span>installed_rank<span class="punctuation string">"</span></span></span><span class=""><span class="punctuation">:</span> <span class=""><span class="punctuation">{</span><span class=""><span class="string"><span class="punctuation string">"</span>type<span class="punctuation string">"</span></span></span><span class=""><span class="punctuation">:</span> <span class="string"><span class="punctuation string">"</span>integer<span class="punctuation string">"</span></span></span><span class="punctuation">}</span></span><span class="punctuation">,</span></span>
<span class=""><span class="string"><span class="punctuation string">"</span>version<span class="punctuation string">"</span></span></span><span class=""><span class="punctuation">:</span> <span class=""><span class="punctuation">{</span> <span class=""><span class="string"><span class="punctuation string">"</span>type<span class="punctuation string">"</span></span></span><span class=""><span class="punctuation">:</span> <span class="string"><span class="punctuation string">"</span>keyword<span class="punctuation string">"</span></span> </span><span class="punctuation">}</span></span><span class="punctuation">,</span></span>
<span class="invalid">.</span><span class="invalid">.</span><span class="invalid">.</span>
</span></span></span></span></code></pre>
<p dir="auto">Die <code>version</code> wird nun als <code>keyword</code> behandelt. Aber das ändert ja die Dokumente nicht. Bisherige Dokumente mit ihren float-Versions werden bei einem ReIndex 1:1 wieder mit float kopiert. Das <code>keyword</code> als Mapping ändert daran gar nichts.</p>
<p dir="auto">Man könnte das Kopieren so gestalten, dass man nun alle History-Datensätze liest, den Wert von float auf den transformierten String wandeln (also 1.001 -> “1.1”, 1.002 -> “1.2”, usw) und das Dokument im Zielindex (neue Version) speichern. </p>
<p dir="auto">Aber das ist eine sehr teure Angelegenheit. Erstens müsste man das statisch implementieren. Dann müssen alle Dokumente geladen und wieder gespeichert werden (eine Menge Roundtrips zwischen Cluster und Client) und es skaliert nicht, wenn wir viele Nodes und Replikations-Knoten haben. </p>
<p dir="auto">Die <a href="https://opensearch.org/docs/latest/opensearch/reindex-data/#transform-documents-during-reindexing" rel="noopener noreferrer">ReIndex-API</a> unterstützt deswegen das Scripting während des Kopierens von Index-Dokumenten in einen anderen Index, ganz ohne den Client damit zu involvieren. Mehrere Script-Sprachen stehen da zur Verfügung und <em>Painless</em> wird meistens dafür empfohlen.</p>
<p dir="auto">Wir erweitern in <a href="https://codeberg.org/beandev/mastodon-playground/src/commit/a87c6e0ba53258cf39d833a499c6491bdb3e6879/oshelper/OpenSearchHelper.py" rel="noopener noreferrer">OpenSearchHelper.py</a> also das Kopieren von Dokumenten um einen weieren Parameter:</p>
<pre><code dir="auto"><span class="source"><span class="function"><span class="storage type function">def</span> <span class="entity name function"><span class="">os_copy_documents</span></span></span><span class="function"><span class="punctuation">(</span></span><span class="function"><span class="variable parameter">client</span></span><span class="function"><span class="punctuation parameter">:</span> <span class=""><span class="">OpenSearch</span></span></span><span class="function"><span class="punctuation">,</span> <span class="variable parameter">index_from</span></span><span class="function"><span class="punctuation parameter">:</span> <span class=""><span class="support type">str</span></span></span><span class="function"><span class="punctuation">,</span> <span class="variable parameter">index_to</span></span><span class="function"><span class="punctuation parameter">:</span> <span class=""><span class="support type">str</span></span></span><span class="function"><span class="punctuation">,</span> <span class="variable parameter">script</span> </span><span class="function"><span class="keyword operator">=</span> <span class="constant language">None</span></span><span class="function"><span class="punctuation">)</span></span><span class="function"><span class="punctuation function">:</span></span>
<span class=""><span class="">reindex</span></span> <span class="keyword operator">=</span> <span class=""><span class="punctuation">{</span>
<span class="string"><span class="string"><span class="punctuation string">"</span></span></span><span class="string"><span class="string">source<span class="punctuation string">"</span></span></span><span class="punctuation">:</span> <span class=""><span class="punctuation">{</span>
<span class="string"><span class="string"><span class="punctuation string">"</span></span></span><span class="string"><span class="string">index<span class="punctuation string">"</span></span></span><span class="punctuation">:</span> <span class=""><span class="">index_from</span></span>
<span class="punctuation">}</span></span><span class="punctuation">,</span>
<span class="string"><span class="string"><span class="punctuation string">"</span></span></span><span class="string"><span class="string">dest<span class="punctuation string">"</span></span></span><span class="punctuation">:</span> <span class=""><span class="punctuation">{</span>
<span class="string"><span class="string"><span class="punctuation string">"</span></span></span><span class="string"><span class="string">index<span class="punctuation string">"</span></span></span><span class="punctuation">:</span> <span class=""><span class="">index_to</span></span><span class="punctuation">,</span>
<span class="string"><span class="string"><span class="punctuation string">"</span></span></span><span class="string"><span class="string">op_type<span class="punctuation string">"</span></span></span><span class="punctuation">:</span> <span class="string"><span class="string"><span class="punctuation string">"</span></span></span><span class="string"><span class="string">create<span class="punctuation string">"</span></span></span>
<span class="punctuation">}</span></span>
<span class="punctuation">}</span></span>
<span class="comment"><span class="punctuation comment">#</span> Add the script object to the reindex body
</span> <span class=""><span class="keyword control">if</span> <span class=""><span class="">script</span></span><span class="punctuation">:</span></span>
<span class=""><span class="keyword control">if</span> <span class="string"><span class="string"><span class="punctuation string">'</span></span></span><span class="string"><span class="string">script<span class="punctuation string">'</span></span></span> <span class="keyword operator">in</span> <span class=""><span class="">script</span></span><span class="punctuation">:</span></span>
<span class=""><span class=""><span class="">reindex</span></span></span><span class=""><span class="punctuation">[</span></span><span class=""><span class="string"><span class="string"><span class="punctuation string">'</span></span></span><span class="string"><span class="string">script<span class="punctuation string">'</span></span></span></span><span class=""><span class="punctuation">]</span></span> <span class="keyword operator">=</span> <span class=""><span class=""><span class="">script</span></span></span><span class=""><span class="punctuation">[</span></span><span class=""><span class="string"><span class="string"><span class="punctuation string">'</span></span></span><span class="string"><span class="string">script<span class="punctuation string">'</span></span></span></span><span class=""><span class="punctuation">]</span></span>
<span class=""><span class="keyword control">else</span><span class="punctuation">:</span></span>
<span class=""><span class=""><span class="">reindex</span></span></span><span class=""><span class="punctuation">[</span></span><span class=""><span class="string"><span class="string"><span class="punctuation string">'</span></span></span><span class="string"><span class="string">script<span class="punctuation string">'</span></span></span></span><span class=""><span class="punctuation">]</span></span> <span class="keyword operator">=</span> <span class=""><span class="">script</span></span>
<span class=""><span class="">response</span></span> <span class="keyword operator">=</span> <span class=""><span class=""><span class="">client</span><span class="punctuation">.</span></span><span class=""><span class="variable function">reindex</span></span><span class="punctuation">(</span><span class=""><span class="variable parameter">body</span><span class="keyword operator">=</span><span class=""><span class="">reindex</span></span><span class="punctuation">,</span> <span class="variable parameter">requests_per_second</span><span class="keyword operator">=</span><span class="constant numeric">10_000</span><span class="punctuation">,</span> <span class="variable parameter">refresh</span><span class="keyword operator">=</span><span class="constant language">True</span></span><span class="punctuation">)</span></span>
<span class=""><span class=""><span class="support function">print</span></span><span class="punctuation">(</span><span class=""><span class="storage type string">f</span><span class="string"><span class="string"><span class="punctuation string">"</span></span></span><span class="string"><span class="string">Copied </span><span class=""><span class="punctuation">{</span><span class="source"><span class=""><span class=""><span class="">response</span></span></span><span class=""><span class="punctuation">[</span></span><span class=""><span class="string"><span class="string"><span class="punctuation string">'</span></span></span><span class="string"><span class="string">created<span class="punctuation string">'</span></span></span></span><span class=""><span class="punctuation">]</span></span></span></span><span class=""><span class="punctuation">}</span></span><span class="string"> documents from </span><span class=""><span class="punctuation">{</span><span class="source"><span class=""><span class="">index_from</span></span></span></span><span class=""><span class="punctuation">}</span></span><span class="string"> to </span><span class=""><span class="punctuation">{</span><span class="source"><span class=""><span class="">index_to</span></span></span></span><span class=""><span class="punctuation">}</span></span><span class="string"><span class="punctuation string">"</span></span></span></span><span class="punctuation">)</span></span>
<span class=""><span class="keyword control">if</span> <span class=""><span class=""><span class="">response</span></span></span><span class=""><span class="punctuation">[</span></span><span class=""><span class="string"><span class="string"><span class="punctuation string">'</span></span></span><span class="string"><span class="string">failures<span class="punctuation string">'</span></span></span></span><span class=""><span class="punctuation">]</span></span><span class="punctuation">:</span></span>
<span class=""><span class=""><span class="support function">print</span></span><span class="punctuation">(</span><span class=""><span class="storage type string">f</span><span class="string"><span class="string"><span class="punctuation string">"</span></span></span><span class="string"><span class="string">Failures on copy: </span><span class=""><span class="punctuation">{</span><span class="source"><span class=""><span class=""><span class="">response</span></span></span><span class=""><span class="punctuation">[</span></span><span class=""><span class="string"><span class="string"><span class="punctuation string">'</span></span></span><span class="string"><span class="string">failures<span class="punctuation string">'</span></span></span></span><span class=""><span class="punctuation">]</span></span></span></span><span class=""><span class="punctuation">}</span></span><span class="string"><span class="punctuation string">"</span></span></span></span><span class="punctuation">)</span></span>
<span class=""><span class="keyword control">raise</span> <span class=""><span class=""><span class="support type">RuntimeError</span></span><span class="punctuation">(</span><span class=""><span class="storage type string">f</span><span class="string"><span class="string"><span class="punctuation string">"</span></span></span><span class="string"><span class="string">Failure on copying document from </span><span class=""><span class="punctuation">{</span><span class="source"><span class=""><span class="">index_from</span></span></span></span><span class=""><span class="punctuation">}</span></span><span class="string"> to </span><span class=""><span class="punctuation">{</span><span class="source"><span class=""><span class="">index_to</span></span></span></span><span class=""><span class="punctuation">}</span></span><span class="string"><span class="punctuation string">"</span></span></span></span><span class="punctuation">)</span></span></span>
<span class="keyword control">return</span> <span class=""><span class="">response</span></span>
</span></code></pre>
<p dir="auto">Wird ein <code>script</code> mit übergeben, fügen wir es dem Request-Body hinzu und lassen OpenSearch den Rest machen. Für jedes zu kopierende Dokument ruft OpenSearch in jedem Node des Clusters in allen verfügbaren Lucene-Prozessen (Shards) eine compilierte Version des Scripts auf. Das Script bekommt das Dokument in einem Kontext Dictionary geliefert (<code>ctx</code>).</p>
<h2 dir="auto">Painless</h2>
<p dir="auto">Aber wie schreibt man Scripte in <em>Painless</em>? Mit einer Menge <em>Pain</em>. Leider. </p>
<p dir="auto">An sich ist <em>Painless</em> ein <em>Groovy</em> Derivat mit einigen Änderungen. OpenSearch erlaubt nicht den Zugriff auf alle Klassen der Java-Runtime der Nodes. Bei den Klassen, die verfügbar sind, sind auch nicht alle Methoden aufrufbar. OpenSearch benutzt dafür eine <a href="https://www.elastic.co/guide/en/elasticsearch/painless/current/painless-api-reference.html" rel="noopener noreferrer">Whitelist</a>. Die zwei wichtigsten Gründe sind: Performance-Probleme bei einigen Methoden (wie die String-Methoden, die reguläre Ausdrücke verwenden, wie <code>split</code>) und Sicherheitsprobleme (zum Beispiel ist ein Zugriff auf das Filesystem natürlich unterbunden).</p>
<p dir="auto">Aber in der Syntax gibt es weitere Unterschiede. <em>Painless</em> erwartet “;” als Zeilenabschluss, was in <em>Groovy</em> nicht notwendig ist. Reguläre Ausdrücke über Pattern.compile sind in <em>Painless</em> statisch vorkompiliert. Das bedeutet, dass es nicht möglich ist, dynamisch reguläre Ausdrücke zu erzeugen. Es müssen Konstanten sein. Dafür hat <em>Painless</em> sogar <a href="https://www.elastic.co/guide/en/elasticsearch/painless/current/painless-regexes.html" rel="noopener noreferrer">eine eigene Syntax</a>, die in <em>Groovy</em> nicht existiert. </p>
<p dir="auto">Dann hat OpenSearch keinen Debugger und die Fehlermeldungen sind manchmal krude und Testen ist auch nicht einfach. </p>
<p dir="auto">Wie geht man also voran? Man kann trotzdem <em>Groovy</em> verwenden, um ein Script vorab zu testen. Man darf ja auch in <em>Groovy</em> trotzdem <code>;</code> ans Ende von Zeilen schreiben. Mit etwas Ausprobieren findet man raus, welche Klassen und Methoden erlaubt sind (und es gibt bei <a href="https://www.elastic.co/guide/en/elasticsearch/reference/7.10/modules-scripting-painless.html" rel="noopener noreferrer">ElasticSearch eine API Referenz</a>).</p>
<p dir="auto">Dann gibt es aber noch ein anderes Problem. Das Script in das JSON Objekt unserer Migrations-Datei einfügen ist leider kaum lesbar. Denn JSON erlaubt keine Zeilenumbrüche in Zeichenketten (nur als Escapes). Ich habe also die Migration so umgebaut, dass man “Dateizeiger” in das Script aufnehmen kann. Das sieht so aus:</p>
<pre><code dir="auto"><span class="source"><span class=""><span class="punctuation">{</span>
<span class=""><span class="string"><span class="punctuation string">"</span>run<span class="punctuation string">"</span></span></span><span class=""><span class="punctuation">:</span> <span class=""><span class="punctuation">[</span>
<span class=""><span class="punctuation">{</span>
<span class=""><span class="string"><span class="punctuation string">"</span>call<span class="punctuation string">"</span></span></span><span class=""><span class="punctuation">:</span> <span class="string"><span class="punctuation string">"</span>create index<span class="punctuation string">"</span></span><span class="punctuation">,</span></span>
<span class=""><span class="string"><span class="punctuation string">"</span>index_name<span class="punctuation string">"</span></span></span><span class=""><span class="punctuation">:</span> <span class="string"><span class="punctuation string">"</span>migration_history<span class="punctuation string">"</span></span><span class="punctuation">,</span></span>
<span class=""><span class="string"><span class="punctuation string">"</span>body<span class="punctuation string">"</span></span></span><span class=""><span class="punctuation">:</span> <span class="string"><span class="punctuation string">"</span>migration_history<span class="punctuation string">"</span></span><span class="punctuation">,</span></span>
<span class=""><span class="string"><span class="punctuation string">"</span>reindex_body<span class="punctuation string">"</span></span></span><span class=""><span class="punctuation">:</span> <span class=""><span class="punctuation">{</span>
<span class=""><span class="string"><span class="punctuation string">"</span>script<span class="punctuation string">"</span></span></span><span class=""><span class="punctuation">:</span> <span class=""><span class="punctuation">{</span>
<span class=""><span class="string"><span class="punctuation string">"</span>lang<span class="punctuation string">"</span></span></span><span class=""><span class="punctuation">:</span> <span class="string"><span class="punctuation string">"</span>painless<span class="punctuation string">"</span></span><span class="punctuation">,</span></span>
<span class=""><span class="string"><span class="punctuation string">"</span>inline<span class="punctuation string">"</span></span></span><span class=""><span class="punctuation">:</span> <span class="string"><span class="punctuation string">"</span>script://S01.000__Reindex_Version.groovy<span class="punctuation string">"</span></span>
</span><span class="punctuation">}</span></span>
</span><span class="punctuation">}</span></span>
</span><span class="punctuation">}</span></span>
<span class="punctuation">]</span></span><span class="punctuation">,</span></span>
<span class="invalid">.</span><span class="invalid">.</span><span class="invalid">.</span>
</span></span></code></pre>
<p dir="auto">Es gibt also den neuen Parameter <code>reindex_body</code> (den wir später an die Kopierfunktion übergeben) und diese besondere Notation <code>script://</code>. Immer wenn <a href="https://codeberg.org/beandev/mastodon-playground/src/commit/a87c6e0ba53258cf39d833a499c6491bdb3e6879/oshelper/Migration.py#L154" rel="noopener noreferrer">Migration.py so etwas sieht</a>, versucht es dazu eine Datei zu laden und das genau an die Stelle als Zeichenkette einzufügen.</p>
<p dir="auto">Im Prinzip schon so, wie wir das mit dem <code>body</code> Parameter gemacht haben. Nur laden wir den Inhalt aus einer weiteren Datei. Diese Datei ist für unser Script nicht wieder ein JSON, sondern eben <em>Groovy</em>.</p>
<p dir="auto">Damit haben wir nun zwei Dinge erledigt: Wir können ganz normale Zeilenumbrüche verwenden und wir könenn das Script einfach mit einer IDE bearbeiten und durch einen Compiler jagen oder sogar ausführen.</p>
<p dir="auto">Jetzt die 1 Millionen Dollar Frage: Wie sieht unser Script aus?</p>
<p dir="auto">Die lauffähige Version in <em>Groovy</em> geschrieben, sieht so aus:</p>
<pre><code dir="auto"><span class="source"><span class=""><span class="storage type">def</span> <span class="entity name function">normalizeVer</span> <span class="punctuation">(</span><span class=""><span class="parameter"><span class="storage type parameter">String</span> <span class="variable parameter">ver</span></span></span><span class="punctuation">)</span> {</span>
<span class="storage type">def</span> pZeros <span class="keyword operator">=</span> ~<span class="string"><span class="punctuation string">'</span>^0+(?!$)<span class="punctuation string">'</span></span>
<span class="storage type">def</span> pDotSplit <span class="keyword operator">=</span> ~<span class="string"><span class="punctuation string">'</span><span class="constant">\\</span>.<span class="punctuation string">'</span></span>
<span class="keyword control">if</span> (ver <span class="keyword operator">==</span> <span class="constant language">null</span> <span class="keyword operator">||</span> ver<span class="keyword operator">.</span><span class=""><span class="">length</span><span class="punctuation">(</span><span class="punctuation">)</span></span> <span class="keyword operator">==</span> <span class="constant numeric">0</span>) {
<span class="keyword control">return</span> <span class="string"><span class="punctuation string">"</span>0<span class="punctuation string">"</span></span>
}
<span class="keyword control">if</span> (ver<span class="keyword operator">.</span><span class=""><span class="">startsWith</span><span class="punctuation">(</span><span class="string"><span class="punctuation string">"</span>.<span class="punctuation string">"</span></span><span class="punctuation">)</span></span>) {
ver <span class="keyword operator">=</span> <span class="string"><span class="punctuation string">"</span>0<span class="punctuation string">"</span></span> <span class="keyword operator">+</span> ver
}
<span class="storage type">String</span> norm <span class="keyword operator">=</span> <span class="string"><span class="punctuation string">"</span><span class="punctuation string">"</span></span>
<span class="keyword control">for</span> (<span class="storage type">String</span> <span class="constant">v</span> <span class="punctuation">:</span> pDotSplit<span class="keyword operator">.</span><span class=""><span class="">split</span><span class="punctuation">(</span>ver<span class="punctuation">)</span></span>) {
<span class="keyword control">if</span> (norm<span class="keyword operator">.</span><span class=""><span class="">length</span><span class="punctuation">(</span><span class="punctuation">)</span></span> <span class="keyword operator">></span> <span class="constant numeric">0</span>) {
norm <span class="keyword operator">=</span> norm <span class="keyword operator">+</span> <span class="string"><span class="punctuation string">"</span>.<span class="punctuation string">"</span></span>
}
<span class="keyword control">if</span> (v<span class="keyword operator">==</span><span class="string"><span class="punctuation string">'</span><span class="punctuation string">'</span></span>) {
norm <span class="keyword operator">=</span> norm <span class="keyword operator">+</span> <span class="string"><span class="punctuation string">"</span>0<span class="punctuation string">"</span></span>
} <span class="keyword control">else</span> {
norm <span class="keyword operator">=</span> norm <span class="keyword operator">+</span> pZeros<span class="keyword operator">.</span><span class=""><span class="">matcher</span><span class="punctuation">(</span>v<span class="punctuation">)</span></span><span class="keyword operator">.</span><span class=""><span class="">replaceFirst</span><span class="punctuation">(</span><span class="string"><span class="punctuation string">'</span><span class="punctuation string">'</span></span><span class="punctuation">)</span></span>
}
}
<span class="keyword control">return</span> norm;
}
ctx<span class=""><span class="punctuation">[</span><span class="string"><span class="punctuation string">'</span>_source<span class="punctuation string">'</span></span><span class="punctuation">]</span></span><span class=""><span class="punctuation">[</span><span class="string"><span class="punctuation string">'</span>version<span class="punctuation string">'</span></span><span class="punctuation">]</span></span> <span class="keyword operator">=</span> <span class=""><span class="">normalizeVer</span><span class="punctuation">(</span>ctx<span class=""><span class="punctuation">[</span><span class="string"><span class="punctuation string">'</span>_source<span class="punctuation string">'</span></span><span class="punctuation">]</span></span><span class=""><span class="punctuation">[</span><span class="string"><span class="punctuation string">'</span>version<span class="punctuation string">'</span></span><span class="punctuation">]</span></span><span class="keyword operator">.</span><span class=""><span class="">toString</span><span class="punctuation">(</span><span class="punctuation">)</span></span><span class="punctuation">)</span></span>
</span></code></pre>
<p dir="auto">Man könnte es fast in <em>Groovy</em> ausführen, wenn uns nicht <code>ctx</code> fehlen würde, dass nur in der Laufzeitumgebung von OpenSearch magisch zur Verfügung steht.</p>
<p dir="auto">Also <a href="https://de.wikipedia.org/wiki/Mock-Objekt" rel="noopener noreferrer">Mocken</a> wir das, als würden wir einen Test schreiben:</p>
<pre><code dir="auto"><span class="source"><span class="storage type">def</span> ctx <span class="keyword operator">=</span> <span class=""><span class="punctuation">[</span><span class="string"><span class="punctuation string">"</span>_source<span class="punctuation string">"</span></span>: <span class=""><span class="punctuation">[</span><span class="string"><span class="punctuation string">"</span>version<span class="punctuation string">"</span></span>: <span class="constant numeric">1.001</span><span class="punctuation">]</span></span><span class="punctuation">]</span></span>
</span></code></pre>
<p dir="auto">Hm, aber das dürfen wir OpenSearch nicht so mitgeben (Die Syntax ist sowieso noch zu <em>Groovy</em>, es fehlen <code>;</code> am Ende der Zeilen), weil wir das <code>ctx</code> Dictionary nicht so in OpenSearch defnieren können (dort existiert es ja).</p>
<p dir="auto">Also erfinde ich zwei spezielle Marker, um den Test-Code vom OpenSearch-Script zu trennen:</p>
<pre><code dir="auto"><span class="source"><span class="storage type">def</span> ctx <span class="keyword operator">=</span> <span class=""><span class="punctuation">[</span><span class="string"><span class="punctuation string">"</span>_source<span class="punctuation string">"</span></span>: <span class=""><span class="punctuation">[</span><span class="string"><span class="punctuation string">"</span>version<span class="punctuation string">"</span></span>: <span class="constant numeric">1.001</span><span class="punctuation">]</span></span><span class="punctuation">]</span></span>
<span class="comment"><span class="punctuation comment">//</span>!script
</span><span class=""><span class="storage type">def</span> <span class="entity name function">normalizeVer</span> <span class="punctuation">(</span><span class=""><span class="parameter"><span class="storage type parameter">String</span> <span class="variable parameter">ver</span></span></span><span class="punctuation">)</span> {</span>
<span class="storage type">def</span> pZeros <span class="keyword operator">=</span> ~<span class="string"><span class="punctuation string">'</span>^0+(?!$)<span class="punctuation string">'</span></span>
<span class="storage type">def</span> pDotSplit <span class="keyword operator">=</span> ~<span class="string"><span class="punctuation string">'</span><span class="constant">\\</span>.<span class="punctuation string">'</span></span>
<span class="keyword control">if</span> (ver <span class="keyword operator">==</span> <span class="constant language">null</span> <span class="keyword operator">||</span> ver<span class="keyword operator">.</span><span class=""><span class="">length</span><span class="punctuation">(</span><span class="punctuation">)</span></span> <span class="keyword operator">==</span> <span class="constant numeric">0</span>) {
<span class="keyword control">return</span> <span class="string"><span class="punctuation string">"</span>0<span class="punctuation string">"</span></span>
}
<span class="keyword control">if</span> (ver<span class="keyword operator">.</span><span class=""><span class="">startsWith</span><span class="punctuation">(</span><span class="string"><span class="punctuation string">"</span>.<span class="punctuation string">"</span></span><span class="punctuation">)</span></span>) {
ver <span class="keyword operator">=</span> <span class="string"><span class="punctuation string">"</span>0<span class="punctuation string">"</span></span> <span class="keyword operator">+</span> ver
}
<span class="storage type">String</span> norm <span class="keyword operator">=</span> <span class="string"><span class="punctuation string">"</span><span class="punctuation string">"</span></span>
<span class="keyword control">for</span> (<span class="storage type">String</span> <span class="constant">v</span> <span class="punctuation">:</span> pDotSplit<span class="keyword operator">.</span><span class=""><span class="">split</span><span class="punctuation">(</span>ver<span class="punctuation">)</span></span>) {
<span class="keyword control">if</span> (norm<span class="keyword operator">.</span><span class=""><span class="">length</span><span class="punctuation">(</span><span class="punctuation">)</span></span> <span class="keyword operator">></span> <span class="constant numeric">0</span>) {
norm <span class="keyword operator">=</span> norm <span class="keyword operator">+</span> <span class="string"><span class="punctuation string">"</span>.<span class="punctuation string">"</span></span>
}
<span class="keyword control">if</span> (v<span class="keyword operator">==</span><span class="string"><span class="punctuation string">'</span><span class="punctuation string">'</span></span>) {
norm <span class="keyword operator">=</span> norm <span class="keyword operator">+</span> <span class="string"><span class="punctuation string">"</span>0<span class="punctuation string">"</span></span>
} <span class="keyword control">else</span> {
norm <span class="keyword operator">=</span> norm <span class="keyword operator">+</span> pZeros<span class="keyword operator">.</span><span class=""><span class="">matcher</span><span class="punctuation">(</span>v<span class="punctuation">)</span></span><span class="keyword operator">.</span><span class=""><span class="">replaceFirst</span><span class="punctuation">(</span><span class="string"><span class="punctuation string">'</span><span class="punctuation string">'</span></span><span class="punctuation">)</span></span>
}
}
<span class="keyword control">return</span> norm;
}
ctx<span class=""><span class="punctuation">[</span><span class="string"><span class="punctuation string">'</span>_source<span class="punctuation string">'</span></span><span class="punctuation">]</span></span><span class=""><span class="punctuation">[</span><span class="string"><span class="punctuation string">'</span>version<span class="punctuation string">'</span></span><span class="punctuation">]</span></span> <span class="keyword operator">=</span> <span class=""><span class="">normalizeVer</span><span class="punctuation">(</span>ctx<span class=""><span class="punctuation">[</span><span class="string"><span class="punctuation string">'</span>_source<span class="punctuation string">'</span></span><span class="punctuation">]</span></span><span class=""><span class="punctuation">[</span><span class="string"><span class="punctuation string">'</span>version<span class="punctuation string">'</span></span><span class="punctuation">]</span></span><span class="keyword operator">.</span><span class=""><span class="">toString</span><span class="punctuation">(</span><span class="punctuation">)</span></span><span class="punctuation">)</span></span>;
<span class="comment"><span class="punctuation comment">//</span>!endscript
</span>
<span class="declaration"><span class="keyword control">assert</span> <span class="string"><span class="punctuation string">"</span>1.1<span class="punctuation string">"</span></span> <span class="keyword operator">==</span> ctx<span class=""><span class="punctuation">[</span><span class="string"><span class="punctuation string">'</span>_source<span class="punctuation string">'</span></span><span class="punctuation">]</span></span><span class=""><span class="punctuation">[</span><span class="string"><span class="punctuation string">'</span>version<span class="punctuation string">'</span></span><span class="punctuation">]</span></span></span>
</span></code></pre>
<p dir="auto">Alles was zwischen <code>//!script</code> und <code>//!endscript</code> liegt, wird als Script eingefügt, der Rest verworfen.</p>
<p dir="auto">Ok, das würde immer noch nicht funktionieren. Denn <code>;</code> fehlt und das Definieren der regulären Ausdrücke funktioniert nur so in <em>Groovy</em> aber nicht in <em>Painless</em>.</p>
<p dir="auto">Also habe ich einen ganz einfachen Code-Wandler geschrieben, der die Zeilen von <em>Groovy</em> nach <em>Painless</em> konvertiert.</p>
<p dir="auto">Die <a href="https://www.elastic.co/guide/en/elasticsearch/painless/current/painless-regexes.html" rel="noopener noreferrer"><em>Painless</em> Variante von Regulären Ausdrücken</a> ist übrigens:</p>
<pre><code dir="auto"><span class=""> def pZeros = /^0+(?!$)/
def pDotSplit = /\\./
</span></code></pre>
<p dir="auto">Der Konverter wird nur ausgeführt, wenn die Endung der Datei <code>.groovy</code> ist. Wenn die Endung <code>.painless</code> ist, kopiert Migration.py das Script 1:1 in die Kopier-Funktion.</p>
<p dir="auto">Das waren jetzt wirklich eine Menge Details und insgesamt habe ich schon ein paar Tage dafür gebraucht, bis es so funktioniert.</p>
<p dir="auto">Dazu habe ich Migration.py so umgebaut, dass es nicht mehr mit float, sondern nur noch mit <a href="https://packaging.pypa.io/en/latest/version.html" rel="noopener noreferrer">packaging.Version</a> arbeitet, damit wir semantische Versionen unterstützen (das auch mit mehr Stufen als nur <code>1.2</code>).</p>
<h1 dir="auto">Prolog</h1>
<p dir="auto">Das war eine wirklich große Umstellung, aber das Migration.py hat dabei eine Menge gewonnen. Nicht nur Komplexität, sondern auch die Flexibilität Dokumente umzuwandeln und mit Scripten zu arbeiten. Zugleich haben wir ein kleines Werkzeug, wo wir die <em>Painless</em>-Scripte in <em>Groovy</em>-IDEs testen zu können. </p>
<p dir="auto">Die neuen Module zum Erstellen von Index-Kopien (als eine Art Backup) mit Restore-Funktion und das komplette Löschen schon Indexen, die über eine Migration erstellt wurden, runden das Toolset noch ab. So ist es viel einfacher eigene Migrationen für eigene Softwareprojekte in Python zusammen mit OpenSearch zu entwickeln.</p>
]]><![CDATA[Refactorings in OpenSearchHelper, Migration und MigrationDev]]>https://write.tchncs.de/~/BeanDevOpenSearch/Refactorings%20in%20OpenSearchHelper,%20Migration%20und%20MigrationDev/2022-06-07T17:37:50.953167+00:00Aljoscha Rittnerhttps://write.tchncs.de/@/beandev/2022-06-07T17:37:50.953167+00:00<![CDATA[<h1 dir="auto">Einleitung</h1>
<p dir="auto">Die Ursprungsidee war die implementation einer Undo-Funktion, um Migrationsscripte wieder zurückzunehmen. Das ist überhaupt möglich, weil wir bei einer Migration eines Index-Mappings immer eine Kopie des bisherigen Index bestehen lassen, einen neuen Index (mit neuer Versionsnummer) erstellen und dann den Alias-Namen auf den neuen Index legen.</p>
<p dir="auto">Das lässt sich natürlich auch rückgängig machen. </p>
<h1 dir="auto">Undo</h1>
<p dir="auto">Das Undo ist aber nicht sehr trivial. Man muss beachten, ob es schon ein clean_up gab (also die alten Indexe nicht mehr existieren). Zudem müssen wir schauen, dass die nicht mehr benötigten Indexe weggeräumt werden (löschen oder archivieren), damit bei der nächsten Migration es nicht zu Fehlern kommt (weil die Indexe der Versionen ja nicht überschrieben werden).</p>
<p dir="auto">Da die Undo-Funktion sehr komplex geworden ist, habe ich sie in <a href="https://codeberg.org/beandev/mastodon-playground/src/commit/399a70441c3e329905ba52de04cb57c0b2bb6888/oshelper/MigrationDev.py" rel="noopener noreferrer">MigrationDev.py</a> ausgelagert. Diese Art der Undo-Funktion sollte auch nur recht selten benötigt werden (zum Beispiel, wenn man sich bei einem Script vertan hat und das rückgängig machen muss).</p>
<h1 dir="auto">Refactorings in Migration.py und OpenSearchHelper.py</h1>
<p dir="auto">Da <a href="https://codeberg.org/beandev/mastodon-playground/src/commit/399a70441c3e329905ba52de04cb57c0b2bb6888/oshelper/MigrationDev.py" rel="noopener noreferrer">MigrationDev.py</a> auf Methoden in <a href="https://codeberg.org/beandev/mastodon-playground/blame/commit/399a70441c3e329905ba52de04cb57c0b2bb6888/oshelper/Migration.py" rel="noopener noreferrer">Migration.py</a> zurückgreift, habe ich einige Methoden von protected auf public umbenannt. Zudem habe ich die in <a href="https://codeberg.org/beandev/mastodon-playground/src/commit/399a70441c3e329905ba52de04cb57c0b2bb6888/oshelper/OpenSearchHelper.py" rel="noopener noreferrer">OpenSearchHelper.py</a> ein paar High-Level Methoden hinzugefügt (Kopieren von Dokumenten, Kopieren eines kompletten Index, Umbenennen eines Index). </p>
]]><![CDATA[Python-Module in Sub-Packages und wie man sich von der IDE veralbern lassen kann]]>https://write.tchncs.de/~/BeanDevMastodon/Python-Module%20in%20Sub-Packages%20und%20wie%20man%20sich%20von%20der%20IDE%20veralbern%20lassen%20kann/2022-06-05T14:00:29.452153+00:00Aljoscha Rittnerhttps://write.tchncs.de/@/beandev/2022-06-05T14:00:29.452153+00:00<![CDATA[<p dir="auto">Es gibt ein paar gute Tutorials (<a href="https://www.delftstack.com/de/howto/python/__init__.py-in-python/" rel="noopener noreferrer">hier</a>), die Module und Pakete erklären. </p>
<p dir="auto">Auch die Referenz-Dokumentation zeigt, wie es aufgebaut werden muss: <a href="https://docs.python.org/3/reference/import.html#regular-packages" rel="noopener noreferrer">https://docs.python.org/3/reference/import.html#regular-packages</a></p>
<pre><code dir="auto"><span class="source"><span class=""><span class="">parent</span></span><span class="keyword operator">/</span>
<span class=""><span class="support function">__init__</span><span class="punctuation">.</span><span class="">py</span></span>
<span class=""><span class="">one</span></span><span class="keyword operator">/</span>
<span class=""><span class="support function">__init__</span><span class="punctuation">.</span><span class="">py</span></span>
<span class=""><span class="">two</span></span><span class="keyword operator">/</span>
<span class=""><span class="support function">__init__</span><span class="punctuation">.</span><span class="">py</span></span>
<span class=""><span class="">three</span></span><span class="keyword operator">/</span>
<span class=""><span class="support function">__init__</span><span class="punctuation">.</span><span class="">py</span></span>
</span></code></pre>
<p dir="auto">Arbeitet man mit einer IDE (wie zum Beispiel IntelliJ), kann man direkt aus der IDE ein Regular-Module-Package erzeugen, dass die IDE auch direkt als Modul lädt. Und dann ist die <code>__init__.py</code> Datei nicht notwendig. </p>
<p dir="auto">Wenn man das nutzt (und vergisst), wird bei einer Übergabe des Codes an andere es zu Problemen kommen.</p>
<p dir="auto">Ruft man im Ordner des ausgecheckten Codeberg Repository <code>python</code> auf, um in die Python Shell zu kommen, kann man mit</p>
<pre><code dir="auto"><span class="source"><span class="keyword operator">></span><span class="keyword operator">></span><span class="keyword operator">></span><span class=""><span class=""><span class="support function">help</span></span><span class="punctuation">(</span><span class=""><span class="string"><span class="string"><span class="punctuation string">'</span></span></span><span class="string"><span class="string">modules<span class="punctuation string">'</span></span></span></span><span class="punctuation">)</span></span>
</span></code></pre>
<p dir="auto">…eine Liste der Module bekommen. <code>mastobot</code> wird, wegen der fehlenden <code>__init__.py</code> nicht dabei sein.</p>
<p dir="auto">Legen wir <code>__init__.py</code> in <code>mastobot</code> (und dann auch in <code>oshelper</code>), haben wir in der 4-spaltigen Liste dann auch <code>mastobot</code> und <code>oshelper</code>. Unsere playground <code>.py</code> Dateien sind ja selbst Module, also lege ich auch dort eine <code>__init__.py</code> an.</p>
<p dir="auto">Wir brauchen also ungefähr diese Struktur:</p>
<pre><code dir="auto"><span class="source"><span class=""><span class="">my</span></span><span class="keyword operator">-</span><span class=""><span class="">playground</span></span><span class="keyword operator">/</span>
<span class=""><span class="">mastobot</span></span><span class="keyword operator">/</span>
<span class=""><span class="support function">__init__</span><span class="punctuation">.</span><span class="">py</span></span>
<span class=""><span class="">oshelper</span></span><span class="keyword operator">/</span>
<span class=""><span class="support function">__init__</span><span class="punctuation">.</span><span class="">py</span></span>
<span class=""><span class="support function">__init__</span><span class="punctuation">.</span><span class="">py</span></span>
<span class=""><span class="">me</span><span class="punctuation">.</span><span class="">py</span></span>
</span></code></pre>
<p dir="auto">Ich committe mal die Dateien ins <a href="https://codeberg.org/beandev/mastodon-playground" rel="noopener noreferrer">Codeberg Repo</a>, damit es nicht weiter zu Problemen kommt.</p>
]]><![CDATA[Migration.py - Cleanup Methode und Schließen von ungenutzten Indexen]]>https://write.tchncs.de/~/BeanDevOpenSearch/Migration.py%20-%20Cleanup%20Methode%20und%20Schließen%20von%20ungenutztten%20Indexen/2022-06-03T10:01:14.051960+00:00Aljoscha Rittnerhttps://write.tchncs.de/@/beandev/2022-06-03T10:01:14.051960+00:00<![CDATA[<h1 dir="auto">Einleitung</h1>
<p dir="auto">Es gibt ein paar Verbesserungen für das <code>Migration.py</code> Script und ich werde ein paar kleine Blog-Artikel zu den Verbesserungen schreiben.</p>
<h1 dir="auto">Index close</h1>
<p dir="auto">Eine winzige Änderung ist das Schließen eines Index. Es ist zu empfehlen Indexe, die man nicht mehr zur Verfügung stellen will, zu schließen (siehe <a href="https://opensearch.org/docs/1.1/opensearch/rest-api/index-apis/close-index/" rel="noopener noreferrer">Close Index API</a>). Das Gegenteil bietet die <a href="https://opensearch.org/docs/1.3/opensearch/rest-api/index-apis/open-index/" rel="noopener noreferrer">Index Open API</a>.</p>
<p dir="auto">Ich habe mal zwei Methoden im Migration-Script dafür angelegt:</p>
<pre><code dir="auto"><span class="source"><span class="function"><span class="storage type function">def</span> <span class="entity name function"><span class="">_close_index</span></span> </span><span class="function"><span class="punctuation">(</span></span><span class="function"><span class="variable parameter">client</span></span><span class="function"><span class="punctuation parameter">:</span> <span class=""><span class="">OpenSearch</span></span></span><span class="function"><span class="punctuation">,</span> <span class="variable parameter">index_name</span></span><span class="function"><span class="punctuation parameter">:</span> <span class=""><span class="support type">str</span></span></span><span class="function"><span class="punctuation">)</span></span><span class="function"><span class="punctuation function">:</span></span>
<span class=""><span class=""><span class="">client</span><span class="punctuation">.</span><span class="">indices</span><span class="punctuation">.</span></span><span class=""><span class="variable function">close</span></span><span class="punctuation">(</span><span class=""><span class="variable parameter">index</span><span class="keyword operator">=</span><span class=""><span class="">index_name</span></span><span class="punctuation">,</span> <span class="variable parameter">ignore_unavailable</span><span class="keyword operator">=</span><span class="constant language">True</span></span><span class="punctuation">)</span></span>
<span class="function">
<span class="storage type function">def</span> <span class="entity name function"><span class="">_open_index</span></span> </span><span class="function"><span class="punctuation">(</span></span><span class="function"><span class="variable parameter">client</span></span><span class="function"><span class="punctuation parameter">:</span> <span class=""><span class="">OpenSearch</span></span></span><span class="function"><span class="punctuation">,</span> <span class="variable parameter">index_name</span></span><span class="function"><span class="punctuation parameter">:</span> <span class=""><span class="support type">str</span></span></span><span class="function"><span class="punctuation">)</span></span><span class="function"><span class="punctuation function">:</span></span>
<span class=""><span class=""><span class="">client</span><span class="punctuation">.</span><span class="">indices</span><span class="punctuation">.</span></span><span class=""><span class="variable function">open</span></span><span class="punctuation">(</span><span class=""><span class="variable parameter">index</span><span class="keyword operator">=</span><span class=""><span class="">index_name</span></span></span><span class="punctuation">)</span></span>
</span></code></pre>
<p dir="auto">Die <code>switch_version</code> hat einen neuen optionalen Parameter erhalten, mit dem Indexnamen, den man schließen will. Das rufen wir von <code>_run_create_index</code> auf.</p>
<p dir="auto">Ein Versuch auf einen geschlossenen Index zuzugreifen verursacht ein Bad Request (Exception in Python) und diese Response:</p>
<pre><code dir="auto"><span class="source"><span class=""><span class="punctuation">{</span>
<span class=""><span class="string"><span class="punctuation string">"</span>error<span class="punctuation string">"</span></span></span><span class=""><span class="punctuation">:</span> <span class=""><span class="punctuation">{</span>
<span class=""><span class="string"><span class="punctuation string">"</span>root_cause<span class="punctuation string">"</span></span></span><span class=""><span class="punctuation">:</span> <span class=""><span class="punctuation">[</span>
<span class=""><span class="punctuation">{</span>
<span class=""><span class="string"><span class="punctuation string">"</span>type<span class="punctuation string">"</span></span></span><span class=""><span class="punctuation">:</span> <span class="string"><span class="punctuation string">"</span>index_closed_exception<span class="punctuation string">"</span></span><span class="punctuation">,</span></span>
<span class=""><span class="string"><span class="punctuation string">"</span>reason<span class="punctuation string">"</span></span></span><span class=""><span class="punctuation">:</span> <span class="string"><span class="punctuation string">"</span>closed<span class="punctuation string">"</span></span><span class="punctuation">,</span></span>
<span class=""><span class="string"><span class="punctuation string">"</span>index<span class="punctuation string">"</span></span></span><span class=""><span class="punctuation">:</span> <span class="string"><span class="punctuation string">"</span>toots_v1_002<span class="punctuation string">"</span></span><span class="punctuation">,</span></span>
<span class=""><span class="string"><span class="punctuation string">"</span>index_uuid<span class="punctuation string">"</span></span></span><span class=""><span class="punctuation">:</span> <span class="string"><span class="punctuation string">"</span>o7OXHpaZQoebIqxhRdkz4g<span class="punctuation string">"</span></span>
</span><span class="punctuation">}</span></span>
<span class="punctuation">]</span></span><span class="punctuation">,</span></span>
<span class=""><span class="string"><span class="punctuation string">"</span>type<span class="punctuation string">"</span></span></span><span class=""><span class="punctuation">:</span> <span class="string"><span class="punctuation string">"</span>index_closed_exception<span class="punctuation string">"</span></span><span class="punctuation">,</span></span>
<span class=""><span class="string"><span class="punctuation string">"</span>reason<span class="punctuation string">"</span></span></span><span class=""><span class="punctuation">:</span> <span class="string"><span class="punctuation string">"</span>closed<span class="punctuation string">"</span></span><span class="punctuation">,</span></span>
<span class=""><span class="string"><span class="punctuation string">"</span>index<span class="punctuation string">"</span></span></span><span class=""><span class="punctuation">:</span> <span class="string"><span class="punctuation string">"</span>toots_v1_002<span class="punctuation string">"</span></span><span class="punctuation">,</span></span>
<span class=""><span class="string"><span class="punctuation string">"</span>index_uuid<span class="punctuation string">"</span></span></span><span class=""><span class="punctuation">:</span> <span class="string"><span class="punctuation string">"</span>o7OXHpaZQoebIqxhRdkz4g<span class="punctuation string">"</span></span>
</span><span class="punctuation">}</span></span><span class="punctuation">,</span></span>
<span class=""><span class="string"><span class="punctuation string">"</span>status<span class="punctuation string">"</span></span></span><span class=""><span class="punctuation">:</span> <span class="constant numeric">400</span>
</span><span class="punctuation">}</span></span>
</span></code></pre>
<p dir="auto">Das ist die der Link zur: <a href="https://codeberg.org/beandev/mastodon-playground/commit/2c7ffb25785f4778073f4f082cba3c0ef54f2487" rel="noopener noreferrer">Commit-Version</a> von <a href="https://codeberg.org/beandev/mastodon-playground/src/commit/2c7ffb25785f4778073f4f082cba3c0ef54f2487/oshelper/Migration.py" rel="noopener noreferrer"><code>Migration.py</code></a>.</p>
<h1 dir="auto">Clean-Up</h1>
<p dir="auto">Jeder Migrations-Schritt kann zu einem neuen Index führen, weil wir grundsätzlich bei “create index” eine neue Version anlegen und die Dokumente kopieren. </p>
<p dir="auto">Wenn man die alten Daten nicht mehr nutzen will, wäre eine Hilfemethode sinnvoll, diese zu löschen.</p>
<p dir="auto">Die Hilfsmethode muss etwas suchen, da wir keine wirkliche strukturierte Information haben, welche Indexe die Vorversionen eines Index sein soll. Wir haben nur ein Namensschema, das aussagt, dass ein Index mit <code>_v#_#</code> endet. Wir müssen uns also auf diese Namenskonvention verlassen. Um sich mit sowas sicherer zu fühlen, werden, per Default, nur geschlossene Index-Versionen gelöscht. Das kann man aber über einen Parameter abschalten.</p>
<p dir="auto">Meine Anforderungen sehen als Signatur so aus:</p>
<pre><code dir="auto"><span class="source"><span class="function"><span class="storage type function">def</span> <span class="entity name function"><span class="">clean_up</span></span> </span><span class="function"><span class="punctuation">(</span></span><span class="function"><span class="variable parameter">client</span></span><span class="function"><span class="punctuation parameter">:</span> <span class=""><span class="">OpenSearch</span></span></span><span class="function"><span class="punctuation">,</span> <span class="variable parameter">index_alias</span></span><span class="function"><span class="punctuation parameter">:</span> <span class=""><span class="support type">str</span></span></span><span class="function"><span class="punctuation">,</span> <span class="variable parameter">keep_version</span></span><span class="function"><span class="punctuation parameter">:</span> <span class=""><span class="support type">float</span></span> </span><span class="function"><span class="keyword operator">=</span> <span class="constant language">None</span></span><span class="function"><span class="punctuation">,</span> <span class="variable parameter">only_closed</span></span><span class="function"><span class="keyword operator">=</span><span class="constant language">True</span></span><span class="function"><span class="punctuation">)</span></span><span class="function"><span class="punctuation function">:</span></span>
<span class="comment"><span class="punctuation comment">"""</span>
Removes older indexes.
:param client: OpenSearch client
:param index_alias: the Alias name
:param keep_version: the version to keep (and all higher). None, if we keep the latest
:param only_closed: delete/hide only the closed indexes
:return:
<span class="punctuation comment">"""</span></span>
<span class="keyword control">pass</span>
</span></code></pre>
<p dir="auto">Es müssen erstmal ein paar Dinge in Erfahrung gebracht werden.</p>
<ol dir="auto">
<li>Gibt es den Alias und ist dahinter eine Version?</li>
<li>Welche Indexe gibt es mit Versionen, die zum Index-Alias-Prefix passen?</li>
<li>Wenn eine keep_version mitgegeben wurde, gibt es diese? Wenn nicht, nehmen wir die aus Punkt 1</li>
</ol>
<p dir="auto">Sowas wie die aktuelle Version zu ermitteln, hatten wir schon in <code>_run_create_index</code>. Ich extrahiere das mal in eine Hilfsmethode:</p>
<pre><code dir="auto"><span class="source"><span class="function"><span class="storage type function">def</span> <span class="entity name function"><span class="">_get_current_version_index</span></span> </span><span class="function"><span class="punctuation">(</span></span><span class="function"><span class="variable parameter">client</span></span><span class="function"><span class="punctuation parameter">:</span> <span class=""><span class="">OpenSearch</span></span></span><span class="function"><span class="punctuation">,</span> <span class="variable parameter">index_alias</span></span><span class="function"><span class="punctuation parameter">:</span> <span class=""><span class="support type">str</span></span></span><span class="function"><span class="punctuation">)</span></span><span class="function"> </span><span class="function"><span class="punctuation">-></span> <span class=""><span class="support type">str</span></span></span><span class="function"><span class="punctuation function">:</span></span>
<span class=""><span class="">alias_response</span></span> <span class="keyword operator">=</span> <span class=""><span class=""><span class="">client</span><span class="punctuation">.</span><span class="">indices</span><span class="punctuation">.</span></span><span class=""><span class="variable function">get_alias</span></span><span class="punctuation">(</span><span class=""><span class="variable parameter">name</span><span class="keyword operator">=</span><span class=""><span class="">index_alias</span></span></span><span class="punctuation">)</span></span>
<span class="comment"><span class="punctuation comment">#</span> We hope, we have not a spanning alias, this means only a single result:
</span> <span class=""><span class="keyword control">if</span> <span class=""><span class=""><span class="support function">len</span></span><span class="punctuation">(</span><span class=""><span class=""><span class="">alias_response</span></span></span><span class="punctuation">)</span></span> <span class="keyword operator">!=</span> <span class="constant numeric">1</span><span class="punctuation">:</span></span>
<span class=""><span class="keyword control">raise</span> <span class=""><span class=""><span class="support type">RuntimeError</span></span><span class="punctuation">(</span><span class=""><span class="storage type string">f</span><span class="string"><span class="string"><span class="punctuation string">"</span></span></span><span class="string"><span class="string">The alias </span><span class=""><span class="punctuation">{</span><span class="source"><span class=""><span class="">index_alias</span></span></span></span><span class=""><span class="punctuation">}</span></span><span class="string"> is a spanning alias over more than one index<span class="punctuation string">"</span></span></span></span><span class="punctuation">)</span></span></span>
<span class="keyword control">return</span> <span class=""><span class=""><span class="support function">next</span></span><span class="punctuation">(</span><span class=""><span class=""><span class=""><span class="support function">iter</span></span><span class="punctuation">(</span><span class=""><span class=""><span class=""><span class="">alias_response</span><span class="punctuation">.</span></span><span class=""><span class="variable function">keys</span></span><span class="punctuation">(</span><span class="punctuation">)</span></span></span><span class="punctuation">)</span></span></span><span class="punctuation">)</span></span>
</span></code></pre>
<p dir="auto">Um aus dem Namen die Version zu extrahieren, wieder eine Hilfsmethode:</p>
<pre><code dir="auto"><span class="source"><span class="function"><span class="storage type function">def</span> <span class="entity name function"><span class="">_parse_version</span></span> </span><span class="function"><span class="punctuation">(</span></span><span class="function"><span class="variable parameter">index_alias</span></span><span class="function"><span class="punctuation parameter">:</span> <span class=""><span class="support type">str</span></span></span><span class="function"><span class="punctuation">,</span> <span class="variable parameter">index_name</span></span><span class="function"><span class="punctuation parameter">:</span> <span class=""><span class="support type">str</span></span></span><span class="function"><span class="punctuation">)</span></span><span class="function"><span class="punctuation function">:</span></span>
<span class=""><span class="">match</span></span> <span class="keyword operator">=</span> <span class=""><span class=""><span class="">re</span><span class="punctuation">.</span></span><span class=""><span class="variable function">match</span></span><span class="punctuation">(</span><span class=""><span class=""><span class="">index_alias</span></span> <span class="keyword operator">+</span> <span class="string"><span class="string"><span class="punctuation string">"</span></span></span><span class="string"><span class="string">_v([0-9_]+)<span class="punctuation string">"</span></span></span><span class="punctuation">,</span> <span class=""><span class="">index_name</span></span></span><span class="punctuation">)</span></span>
<span class=""><span class="keyword control">if</span> <span class=""><span class="">match</span></span><span class="punctuation">:</span></span>
<span class="keyword control">return</span> <span class=""><span class=""><span class="">match</span><span class="punctuation">.</span></span><span class=""><span class="variable function">group</span></span><span class="punctuation">(</span><span class=""><span class="constant numeric">1</span></span><span class="punctuation">)</span></span><span class=""><span class=""><span class="punctuation">.</span></span><span class=""><span class="variable function">replace</span></span><span class="punctuation">(</span><span class=""><span class="string"><span class="string"><span class="punctuation string">'</span></span></span><span class="string"><span class="string">_<span class="punctuation string">'</span></span></span><span class="punctuation">,</span> <span class="string"><span class="string"><span class="punctuation string">'</span></span></span><span class="string"><span class="string">.<span class="punctuation string">'</span></span></span></span><span class="punctuation">)</span></span>
<span class="keyword control">return</span> <span class="constant language">None</span>
</span></code></pre>
<p dir="auto">Also Punkt 1 ist dann das hier:</p>
<pre><code dir="auto"><span class="source"> <span class=""><span class="keyword control">if</span> <span class="keyword operator">not</span> <span class=""><span class=""><span class="">client</span><span class="punctuation">.</span><span class="">indices</span><span class="punctuation">.</span></span><span class=""><span class="variable function">exists_alias</span></span><span class="punctuation">(</span><span class=""><span class="variable parameter">name</span><span class="keyword operator">=</span><span class=""><span class="">index_alias</span></span></span><span class="punctuation">)</span></span><span class="punctuation">:</span></span>
<span class=""><span class="keyword control">raise</span> <span class=""><span class=""><span class="support type">RuntimeError</span> </span><span class="punctuation">(</span><span class=""><span class="storage type string">f</span><span class="string"><span class="string"><span class="punctuation string">"</span></span></span><span class="string"><span class="string">Alias </span><span class=""><span class="punctuation">{</span><span class="source"><span class=""><span class="">index_alias</span></span></span></span><span class=""><span class="punctuation">}</span></span><span class="string"> doesn't exists, cannot proceed with clean_up<span class="punctuation string">"</span></span></span></span><span class="punctuation">)</span></span></span>
<span class=""><span class="">index_current</span></span> <span class="keyword operator">=</span> <span class=""><span class=""><span class="variable function">_get_current_version_index</span></span><span class="punctuation">(</span><span class=""><span class=""><span class="">client</span></span><span class="punctuation">,</span> <span class=""><span class="">index_alias</span></span></span><span class="punctuation">)</span></span>
<span class=""><span class="">version_str</span></span> <span class="keyword operator">=</span> <span class=""><span class=""><span class="variable function">_parse_version</span></span><span class="punctuation">(</span><span class=""><span class=""><span class="">index_alias</span></span><span class="punctuation">,</span> <span class=""><span class="">index_current</span></span></span><span class="punctuation">)</span></span>
<span class=""><span class="keyword control">if</span> <span class="keyword operator">not</span> <span class=""><span class="">version_str</span></span><span class="punctuation">:</span></span>
<span class=""><span class="keyword control">raise</span> <span class=""><span class=""><span class="support type">RuntimeError</span> </span><span class="punctuation">(</span><span class=""><span class="storage type string">f</span><span class="string"><span class="string"><span class="punctuation string">"</span></span></span><span class="string"><span class="string">Alias </span><span class=""><span class="punctuation">{</span><span class="source"><span class=""><span class="">index_alias</span></span></span></span><span class=""><span class="punctuation">}</span></span><span class="string"> is not assigned to a version index </span><span class=""><span class="punctuation">{</span><span class="source"><span class=""><span class="">index_current</span></span></span></span><span class=""><span class="punctuation">}</span></span><span class="string"><span class="punctuation string">"</span></span></span></span><span class="punctuation">)</span></span></span>
</span></code></pre>
<p dir="auto">Für den zweiten Punkt suchen wir einfach im Cluster nach allen Indexen, die mit dem Namen des Alias anfangen. Die Methoden dazu sind vielfältig, aber eine ist ganz nett: <a href="http://localhost:9200/_cluster/state/metadata/toots_v*?expand_wildcards=all&filter_path=metadata.indices.*.state" rel="noopener noreferrer">http://localhost:9200/_cluster/state/metadata/toots_v*?expand_wildcards=all&filter_path=metadata.indices.*.state</a>, was in Python so aussieht: </p>
<pre><code dir="auto"><span class="source"><span class=""><span class=""><span class="">client</span><span class="punctuation">.</span><span class="">cluster</span><span class="punctuation">.</span></span><span class=""><span class="variable function">state</span></span><span class="punctuation">(</span><span class=""><span class="variable parameter">metric</span><span class="keyword operator">=</span><span class="string"><span class="string"><span class="punctuation string">'</span></span></span><span class="string"><span class="string">metadata<span class="punctuation string">'</span></span></span><span class="punctuation">,</span> <span class="variable parameter">index</span><span class="keyword operator">=</span><span class=""><span class="">index_alias</span></span> <span class="keyword operator">+</span> <span class="string"><span class="string"><span class="punctuation string">'</span></span></span><span class="string"><span class="string">_v*<span class="punctuation string">'</span></span></span><span class="punctuation">,</span> <span class="variable parameter">expand_wildcards</span><span class="keyword operator">=</span><span class="string"><span class="string"><span class="punctuation string">'</span></span></span><span class="string"><span class="string">all<span class="punctuation string">'</span></span></span><span class="punctuation">,</span>
<span class="variable parameter">filter_path</span><span class="keyword operator">=</span><span class="string"><span class="string"><span class="punctuation string">'</span></span></span><span class="string"><span class="string">metadata.indices.*.state<span class="punctuation string">'</span></span></span></span><span class="punctuation">)</span></span>
</span></code></pre>
<p dir="auto">Das liefert alle Indexe, ungefiltert nach unserem Alias-Prefix. </p>
<p dir="auto">Zum Vergleich der Versionen nutze ich <code>package.Version</code>. Zusammengebaut sieht das dann so aus:</p>
<pre><code dir="auto"><span class="source"> <span class=""><span class="">found_indexes</span></span><span class="keyword operator">=</span><span class=""><span class="punctuation">{</span><span class=""><span class="punctuation">}</span></span></span>
<span class=""><span class="">meta</span></span> <span class="keyword operator">=</span> <span class=""><span class=""><span class="">client</span><span class="punctuation">.</span><span class="">cluster</span><span class="punctuation">.</span></span><span class=""><span class="variable function">state</span></span><span class="punctuation">(</span><span class=""><span class="variable parameter">metric</span><span class="keyword operator">=</span><span class="string"><span class="string"><span class="punctuation string">'</span></span></span><span class="string"><span class="string">metadata<span class="punctuation string">'</span></span></span><span class="punctuation">,</span> <span class="variable parameter">index</span><span class="keyword operator">=</span><span class=""><span class="">index_alias</span></span> <span class="keyword operator">+</span> <span class="string"><span class="string"><span class="punctuation string">'</span></span></span><span class="string"><span class="string">_v*<span class="punctuation string">'</span></span></span><span class="punctuation">,</span> <span class="variable parameter">expand_wildcards</span><span class="keyword operator">=</span><span class="string"><span class="string"><span class="punctuation string">'</span></span></span><span class="string"><span class="string">all<span class="punctuation string">'</span></span></span><span class="punctuation">,</span>
<span class="variable parameter">filter_path</span><span class="keyword operator">=</span><span class="string"><span class="string"><span class="punctuation string">'</span></span></span><span class="string"><span class="string">metadata.indices.*.state<span class="punctuation string">'</span></span></span></span><span class="punctuation">)</span></span>
<span class=""><span class="keyword control">for</span> <span class="">index_name</span> <span class=""><span class="keyword control">in</span></span></span><span class=""> <span class=""><span class=""><span class="">meta</span></span></span><span class=""><span class="punctuation">[</span></span><span class=""><span class="string"><span class="string"><span class="punctuation string">'</span></span></span><span class="string"><span class="string">metadata<span class="punctuation string">'</span></span></span></span><span class=""><span class="punctuation">]</span></span><span class=""><span class="punctuation">[</span></span><span class=""><span class="string"><span class="string"><span class="punctuation string">'</span></span></span><span class="string"><span class="string">indices<span class="punctuation string">'</span></span></span></span><span class=""><span class="punctuation">]</span></span><span class=""><span class=""><span class="punctuation">.</span></span><span class=""><span class="variable function">keys</span></span><span class="punctuation">(</span><span class="punctuation">)</span></span><span class="punctuation">:</span></span>
<span class=""><span class="">v</span></span> <span class="keyword operator">=</span> <span class=""><span class=""><span class="variable function">_parse_version</span></span><span class="punctuation">(</span><span class=""><span class=""><span class="">index_alias</span></span><span class="punctuation">,</span> <span class=""><span class="">index_name</span></span></span><span class="punctuation">)</span></span>
<span class=""><span class="keyword control">if</span> <span class=""><span class="">v</span></span> <span class="keyword operator">and</span> <span class=""><span class="punctuation">(</span><span class=""><span class=""><span class="variable function">Version</span></span><span class="punctuation">(</span><span class=""><span class=""><span class="">v</span></span></span><span class="punctuation">)</span></span> <span class="keyword operator"><</span> <span class=""><span class="">keep</span></span><span class="punctuation">)</span></span><span class="punctuation">:</span></span>
<span class=""><span class="keyword control">if</span> <span class=""><span class="">only_closed</span></span> <span class="keyword operator">and</span> <span class=""><span class=""><span class="">meta</span></span></span><span class=""><span class="punctuation">[</span></span><span class=""><span class="string"><span class="string"><span class="punctuation string">'</span></span></span><span class="string"><span class="string">metadata<span class="punctuation string">'</span></span></span></span><span class=""><span class="punctuation">]</span></span><span class=""><span class="punctuation">[</span></span><span class=""><span class="string"><span class="string"><span class="punctuation string">'</span></span></span><span class="string"><span class="string">indices<span class="punctuation string">'</span></span></span></span><span class=""><span class="punctuation">]</span></span><span class=""><span class="punctuation">[</span></span><span class=""><span class=""><span class="">index_name</span></span></span><span class=""><span class="punctuation">]</span></span><span class=""><span class="punctuation">[</span></span><span class=""><span class="string"><span class="string"><span class="punctuation string">'</span></span></span><span class="string"><span class="string">state<span class="punctuation string">'</span></span></span></span><span class=""><span class="punctuation">]</span></span> <span class="keyword operator">==</span> <span class="string"><span class="string"><span class="punctuation string">'</span></span></span><span class="string"><span class="string">open<span class="punctuation string">'</span></span></span><span class="punctuation">:</span></span>
<span class="keyword control">continue</span>
<span class=""><span class=""><span class="">found_indexes</span></span></span><span class=""><span class="punctuation">[</span></span><span class=""><span class=""><span class=""><span class="variable function">Version</span></span><span class="punctuation">(</span><span class=""><span class=""><span class="">v</span></span></span><span class="punctuation">)</span></span></span><span class=""><span class="punctuation">]</span></span><span class="keyword operator">=</span><span class=""><span class="">index_name</span></span>
</span></code></pre>
<p dir="auto">Bei einer Migration bis Version 1.004 bekommt man entweder nix (weil <code>toots_v1_002</code> nicht geschlossen ist) oder <code><Version('1.2')>: 'toots_v1_002'</code> in <code>found_indexes</code>.</p>
<p dir="auto">Das ist schon mal sehr schön, denn Punkt 3 haben wir damit nebenbei auch erledigt.</p>
<p dir="auto">Jetzt können wir zur Tat schreiten. Alle Informationen, was wir zu tun haben, liegen vor. </p>
<pre><code dir="auto"><span class="source"> <span class=""><span class="keyword control">for</span> <span class="">version</span><span class="punctuation">,</span> <span class="">index_name</span> <span class=""><span class="keyword control">in</span></span></span><span class=""> <span class=""><span class=""><span class="">found_indexes</span><span class="punctuation">.</span></span><span class=""><span class="variable function">items</span></span><span class="punctuation">(</span><span class="punctuation">)</span></span><span class="punctuation">:</span></span>
<span class=""><span class=""><span class="">client</span><span class="punctuation">.</span><span class="">indices</span><span class="punctuation">.</span></span><span class=""><span class="variable function">delete</span></span><span class="punctuation">(</span><span class=""><span class="variable parameter">index</span><span class="keyword operator">=</span><span class=""><span class="">index_name</span></span></span><span class="punctuation">)</span></span>
</span></code></pre>
<p dir="auto">Man sieht, dass die eigentliche Funktion beeindruckend trivial ist und der Hauptfokus immer auf den Randbedingungen und Parametern liegt. </p>
<p dir="auto">Der Aufruf der Methode <code>Migration.clean_up (...)</code> ist meistens manuell, am besten über eine Konsole. Man könnte auch ein CLI Tools dazu basteln. Die <code>clean_up</code> Funktion baue ich aber später in <code>migration_history_prepare</code> ein. D.h. für diesen Index unserer selbstverwalteten History räumen wir automatisch die Reste auf. Das ist aber ein extra Artikel, da wir das <code>migration_history_prepare</code> umbauen müssen, um ebenfalls eine vollständige Migration zu unterstützen.</p>
<p dir="auto">Die aktuelle Version des <a href="https://codeberg.org/beandev/mastodon-playground/src/commit/515259b8222e4b2b739fdeba2e2b1219877e88e6/oshelper/Migration.py" rel="noopener noreferrer">Migration.py</a> und der <a href="https://codeberg.org/beandev/mastodon-playground/commit/515259b8222e4b2b739fdeba2e2b1219877e88e6" rel="noopener noreferrer">Commit</a></p>
<p dir="auto"><em>Update</em>:
Leider habe ich vergessen zu prüfen, ob die keep_version (wenn sie als Parameter übergeben wird), existiert. Das ist mit dem <a href="https://codeberg.org/beandev/mastodon-playground/commit/879c606663963fdabeb10aaf5977f01c057344af" rel="noopener noreferrer">Commit 879c606663963fdabeb10aaf5977f01c057344af</a> gefixed.</p>
<p dir="auto">Mal wieder Danke, dass ihr so weit gefolgt seid. </p>
]]><![CDATA[Migration.py mit echter Index-Migration bei Mapping-Änderungen]]>https://write.tchncs.de/~/BeanDevOpenSearch/Migration.py%20mit%20echter%20Index-Migration%20bei%20Mapping-Änderungen/2022-06-02T17:01:57.908693+00:00Aljoscha Rittnerhttps://write.tchncs.de/@/beandev/2022-06-02T17:01:57.908693+00:00<![CDATA[<h1 dir="auto">Einleitung</h1>
<p dir="auto">Wie schon im <a href="https://write.tchncs.de/%7E/BeanDevOpenSearch/Index%20Alias%20-%20Warum%20man%20in%20OpenSearch%20mehrere%20Namen%20f%C3%BCr%20Indexe%20ben%C3%B6tigt" rel="noopener noreferrer">Blog-Artikel über Index-Aliase</a> erwähnt, gibt es die Einschränkung in OpenSearch, dass man einen Index in seiner Mapping-Konfiguration kaum verändern kann. Tatsächlich kann man nur Mappings hinzufügen. Man ist also gezwungen, einen ganz neuen Index anzulegen und die Dokumente aus dem alten Index in den neuen Index zu kopieren. Der neue Index hat aber zwingend einen anderen Namen. Also benötigen wir immer einen Alias, damit nicht alle Clients ständig den Sourcecode ändern müssen, wenn wir nur was am Mapping ändern.</p>
<p dir="auto">Es gibt also ein paar Schritte durchzuführen:</p>
<ul dir="auto">
<li>Neuen Index mit neuem Namen anlegen (da nehmen wir einfach die Version zu dem Namen)</li>
<li>Dokumente vom alten Index in den neuen Index kopieren</li>
<li>Den neuen Index mit einem eindeutigen Alias versehen, damit alle Clients immer den gleichen Namen verwenden können.</li>
</ul>
<p dir="auto">Beispiel:</p>
<ul dir="auto">
<li><code>toots_v1_001</code> anlegen</li>
<li>Dokumente vom alten <code>toots</code> Index nach <code>toots_v1_001</code> kopieren</li>
<li>Alias <code>toots</code> für <code>toots_v1_001</code> setzen und gleichzeitig vom alten Index löschen (wenn er dort war)</li>
</ul>
<p dir="auto">Sieht simpel aus. Aber man bekommt gleich Bauchschmerzen. Wir haben ja schon den Index <code>toots</code> der dann ja so heißt wie unser Alias.</p>
<p dir="auto">Ok, damit haben wir den Sonderfall, wenn wir nicht-versionierte Indexe haben.</p>
<p dir="auto">Dafür müssen wir wie folgt arbeiten:</p>
<ul dir="auto">
<li>Überprüfen, ob wir nur einen unversionierten <code>toots</code> Index ohne Alias haben. Wenn ja:
<ul dir="auto">
<li><code>toots_v1_001</code> anlegen</li>
<li>Dokumente vom alten <code>toots</code> Index nach <code>toots_v1_001</code> kopieren</li>
<li>Index <code>toots</code> löschen (ja, sorry - das ist hart)</li>
<li>Alias <code>toots</code> für <code>toots_v1_001</code> setzen</li>
</ul>
</li>
</ul>
<p dir="auto">Aber irgendjemand könnte auch von 0 anfangen. Es gibt keinen Index oder Alias. Dieser Fall ist am einfachsten:</p>
<ul dir="auto">
<li>Neuen Index <code>toots_v1_001</code> erstellen</li>
<li>Alias <code>toots</code> auf <code>toots_v1_001</code> anlegen</li>
</ul>
<p dir="auto">Das deckt unsere Fälle ab, die uns begegnen könnten.</p>
<p dir="auto">Das Grundprinzip der Versionierung mit Alias-Namen habe ich mal visualisiert:</p>
<p dir="auto"><img src="https://codeberg.org/beandev/mastodon-playground/raw/branch/main/documentation/os/index-alias/versioning-alias.png" alt="versioning"></p>
<h1 dir="auto">Migration.py anpassen</h1>
<p dir="auto">Wir müssen zwei neue Funktionen anlegen und <code>_run_create_index</code> anpassen.</p>
<h2 dir="auto">Copy documents</h2>
<p dir="auto">OpenSearch bietet eine ReIndex API an, damit man Dokumente von einem Index in einen neuen Index kopieren und neu indizieren kann.</p>
<p dir="auto">Die Python Methode sieht so aus:</p>
<pre><code dir="auto"><span class="source"><span class="function"><span class="storage type function">def</span> <span class="entity name function"><span class="">copy_documents</span></span></span><span class="function"><span class="punctuation">(</span></span><span class="function"><span class="variable parameter">client</span></span><span class="function"><span class="punctuation parameter">:</span> <span class=""><span class="">OpenSearch</span></span></span><span class="function"><span class="punctuation">,</span> <span class="variable parameter">index_from</span></span><span class="function"><span class="punctuation parameter">:</span> <span class=""><span class="support type">str</span></span></span><span class="function"><span class="punctuation">,</span> <span class="variable parameter">index_to</span></span><span class="function"><span class="punctuation parameter">:</span> <span class=""><span class="support type">str</span></span></span><span class="function"><span class="punctuation">)</span></span><span class="function"><span class="punctuation function">:</span></span>
<span class=""><span class="">reindex</span></span> <span class="keyword operator">=</span> <span class=""><span class="punctuation">{</span>
<span class="string"><span class="string"><span class="punctuation string">"</span></span></span><span class="string"><span class="string">source<span class="punctuation string">"</span></span></span><span class="punctuation">:</span> <span class=""><span class="punctuation">{</span>
<span class="string"><span class="string"><span class="punctuation string">"</span></span></span><span class="string"><span class="string">index<span class="punctuation string">"</span></span></span><span class="punctuation">:</span> <span class=""><span class="">index_from</span></span>
<span class="punctuation">}</span></span><span class="punctuation">,</span>
<span class="string"><span class="string"><span class="punctuation string">"</span></span></span><span class="string"><span class="string">dest<span class="punctuation string">"</span></span></span><span class="punctuation">:</span> <span class=""><span class="punctuation">{</span>
<span class="string"><span class="string"><span class="punctuation string">"</span></span></span><span class="string"><span class="string">index<span class="punctuation string">"</span></span></span><span class="punctuation">:</span> <span class=""><span class="">index_to</span></span><span class="punctuation">,</span>
<span class="string"><span class="string"><span class="punctuation string">"</span></span></span><span class="string"><span class="string">op_type<span class="punctuation string">"</span></span></span><span class="punctuation">:</span> <span class="string"><span class="string"><span class="punctuation string">"</span></span></span><span class="string"><span class="string">create<span class="punctuation string">"</span></span></span>
<span class="punctuation">}</span></span>
<span class="punctuation">}</span></span>
<span class=""><span class="">response</span></span> <span class="keyword operator">=</span> <span class=""><span class=""><span class="">client</span><span class="punctuation">.</span></span><span class=""><span class="variable function">reindex</span></span><span class="punctuation">(</span><span class=""><span class="variable parameter">body</span><span class="keyword operator">=</span><span class=""><span class="">reindex</span></span><span class="punctuation">,</span> <span class="variable parameter">requests_per_second</span><span class="keyword operator">=</span><span class="constant numeric">10_000</span><span class="punctuation">,</span> <span class="variable parameter">refresh</span><span class="keyword operator">=</span><span class="constant language">True</span></span><span class="punctuation">)</span></span>
<span class=""><span class="keyword control">if</span> <span class=""><span class=""><span class="">response</span></span></span><span class=""><span class="punctuation">[</span></span><span class=""><span class="string"><span class="string"><span class="punctuation string">'</span></span></span><span class="string"><span class="string">failures<span class="punctuation string">'</span></span></span></span><span class=""><span class="punctuation">]</span></span><span class="punctuation">:</span></span>
<span class=""><span class="keyword control">raise</span> <span class=""><span class=""><span class="support type">RuntimeError</span></span><span class="punctuation">(</span><span class=""><span class="storage type string">f</span><span class="string"><span class="string"><span class="punctuation string">"</span></span></span><span class="string"><span class="string">Failure on copying document from </span><span class=""><span class="punctuation">{</span><span class="source"><span class=""><span class="">index_from</span></span></span></span><span class=""><span class="punctuation">}</span></span><span class="string"> to </span><span class=""><span class="punctuation">{</span><span class="source"><span class=""><span class="">index_to</span></span></span></span><span class=""><span class="punctuation">}</span></span><span class="string"><span class="punctuation string">"</span></span></span></span><span class="punctuation">)</span></span></span>
<span class="keyword control">return</span> <span class=""><span class="">response</span></span>
</span></code></pre>
<p dir="auto">Da der Zielindex immer leer ist, reicht ein <code>create</code> als operation type. Die restlichen Parameter von <code>reindex</code> sind einmal ein Throttling (um nach 10.000 inserts etwas Pause zu haben) und der obligatorische Refresh.</p>
<p dir="auto">Wenn Fehler auftreten, müssen wir abbrechen.</p>
<h2 dir="auto">Switch Version</h2>
<p dir="auto">Die Index-Alias API bietet einen komfortablen Weg in einem Rutsch ein Alias von anderen Indexen wegzunehmen und ihn dann einem bestimmten Index zuzuweisen.</p>
<p dir="auto">In Python sieht das so aus:</p>
<pre><code dir="auto"><span class="source"><span class="function"><span class="storage type function">def</span> <span class="entity name function"><span class="">switch_version</span></span></span><span class="function"><span class="punctuation">(</span></span><span class="function"><span class="variable parameter">client</span></span><span class="function"><span class="punctuation parameter">:</span> <span class=""><span class="">OpenSearch</span></span></span><span class="function"><span class="punctuation">,</span> <span class="variable parameter">index_alias</span></span><span class="function"><span class="punctuation parameter">:</span> <span class=""><span class="support type">str</span></span></span><span class="function"><span class="punctuation">,</span> <span class="variable parameter">version</span><span class="punctuation">)</span></span><span class="function"><span class="punctuation function">:</span></span>
<span class=""><span class="">index_to</span></span> <span class="keyword operator">=</span> <span class=""><span class="">index_alias</span></span> <span class="keyword operator">+</span> <span class="string"><span class="string"><span class="punctuation string">"</span></span></span><span class="string"><span class="string">_v<span class="punctuation string">"</span></span></span> <span class="keyword operator">+</span> <span class=""><span class=""><span class="support type">str</span></span><span class="punctuation">(</span><span class=""><span class=""><span class="">version</span></span></span><span class="punctuation">)</span></span><span class=""><span class=""><span class="punctuation">.</span></span><span class=""><span class="variable function">replace</span></span><span class="punctuation">(</span><span class=""><span class="string"><span class="string"><span class="punctuation string">'</span></span></span><span class="string"><span class="string">.<span class="punctuation string">'</span></span></span><span class="punctuation">,</span> <span class="string"><span class="string"><span class="punctuation string">'</span></span></span><span class="string"><span class="string">_<span class="punctuation string">'</span></span></span></span><span class="punctuation">)</span></span>
<span class="comment"><span class="punctuation comment">#</span> Move or creates the alias. The remove works also in cases of non-existing alias.
</span> <span class=""><span class="">move_alias</span></span> <span class="keyword operator">=</span> <span class=""><span class="punctuation">{</span>
<span class="string"><span class="string"><span class="punctuation string">"</span></span></span><span class="string"><span class="string">actions<span class="punctuation string">"</span></span></span><span class="punctuation">:</span> <span class=""><span class="punctuation">[</span>
<span class=""><span class="punctuation">{</span>
<span class="string"><span class="string"><span class="punctuation string">"</span></span></span><span class="string"><span class="string">remove<span class="punctuation string">"</span></span></span><span class="punctuation">:</span> <span class=""><span class="punctuation">{</span>
<span class="string"><span class="string"><span class="punctuation string">"</span></span></span><span class="string"><span class="string">index<span class="punctuation string">"</span></span></span><span class="punctuation">:</span> <span class="string"><span class="string"><span class="punctuation string">"</span></span></span><span class="string"><span class="string">*<span class="punctuation string">"</span></span></span><span class="punctuation">,</span>
<span class="string"><span class="string"><span class="punctuation string">"</span></span></span><span class="string"><span class="string">alias<span class="punctuation string">"</span></span></span><span class="punctuation">:</span> <span class=""><span class="">index_alias</span></span>
<span class="punctuation">}</span></span>
<span class="punctuation">}</span></span><span class="punctuation">,</span>
<span class=""><span class="punctuation">{</span>
<span class="string"><span class="string"><span class="punctuation string">"</span></span></span><span class="string"><span class="string">add<span class="punctuation string">"</span></span></span><span class="punctuation">:</span> <span class=""><span class="punctuation">{</span>
<span class="string"><span class="string"><span class="punctuation string">"</span></span></span><span class="string"><span class="string">index<span class="punctuation string">"</span></span></span><span class="punctuation">:</span> <span class=""><span class="">index_to</span></span><span class="punctuation">,</span>
<span class="string"><span class="string"><span class="punctuation string">"</span></span></span><span class="string"><span class="string">alias<span class="punctuation string">"</span></span></span><span class="punctuation">:</span> <span class=""><span class="">index_alias</span></span>
<span class="punctuation">}</span></span>
<span class="punctuation">}</span></span>
<span class="punctuation">]</span></span>
<span class="punctuation">}</span></span>
<span class="keyword control">return</span> <span class=""><span class=""><span class="">client</span><span class="punctuation">.</span><span class="">indices</span><span class="punctuation">.</span></span><span class=""><span class="variable function">update_aliases</span></span><span class="punctuation">(</span><span class=""><span class="variable parameter">body</span><span class="keyword operator">=</span><span class=""><span class="">move_alias</span></span></span><span class="punctuation">)</span></span>
</span></code></pre>
<p dir="auto">Egal wo der Index-Alias hinzugefügt wurde, wir löschen ihn überall. Zudem soll der Alias auf den neuen Index hinzugefügt werden. </p>
<h2 dir="auto"><code>_run_create_index</code> anpassen</h2>
<p dir="auto">Leider haben wir nicht mehr eine Zeile, sondern nun etwas mehr. Wir haben nun drei Fälle zu prüfen:</p>
<ol dir="auto">
<li>Wir haben echte Indexe mit Namen wie <code>toots</code>, <code>following</code>, usw. Alle ohne Version im Namen. Kein Alias vergeben</li>
<li>Wir haben überhaupt keinen Index und fangen frisch von der grünen Wiese an</li>
<li>Wir haben einen Migrationsschritt von Version a zu Version b. D.h. es gibt einen versionierten Index und einen Alias dazu.</li>
</ol>
<p dir="auto">So sieht das in python aus:</p>
<pre><code dir="auto"><span class="source"><span class="function"><span class="storage type function">def</span> <span class="entity name function"><span class="">_run_create_index</span></span></span><span class="function"><span class="punctuation">(</span></span><span class="function"><span class="variable parameter">client</span></span><span class="function"><span class="punctuation parameter">:</span> <span class=""><span class="">OpenSearch</span></span></span><span class="function"><span class="punctuation">,</span> <span class="variable parameter">runner</span><span class="punctuation">)</span></span><span class="function"><span class="punctuation function">:</span></span>
<span class=""><span class="">index_alias</span></span> <span class="keyword operator">=</span> <span class=""><span class=""><span class="">runner</span></span></span><span class=""><span class="punctuation">[</span></span><span class=""><span class="string"><span class="string"><span class="punctuation string">'</span></span></span><span class="string"><span class="string">index_name<span class="punctuation string">'</span></span></span></span><span class=""><span class="punctuation">]</span></span>
<span class=""><span class="">version</span></span> <span class="keyword operator">=</span> <span class=""><span class=""><span class="">runner</span></span></span><span class=""><span class="punctuation">[</span></span><span class=""><span class="string"><span class="string"><span class="punctuation string">'</span></span></span><span class="string"><span class="string">__self<span class="punctuation string">'</span></span></span></span><span class=""><span class="punctuation">]</span></span><span class=""><span class="punctuation">[</span></span><span class=""><span class="string"><span class="string"><span class="punctuation string">'</span></span></span><span class="string"><span class="string">version<span class="punctuation string">'</span></span></span></span><span class=""><span class="punctuation">]</span></span>
<span class=""><span class="">index_to</span></span> <span class="keyword operator">=</span> <span class=""><span class="">index_alias</span></span> <span class="keyword operator">+</span> <span class="string"><span class="string"><span class="punctuation string">"</span></span></span><span class="string"><span class="string">_v<span class="punctuation string">"</span></span></span> <span class="keyword operator">+</span> <span class=""><span class=""><span class="support type">str</span></span><span class="punctuation">(</span><span class=""><span class=""><span class="">version</span></span></span><span class="punctuation">)</span></span><span class=""><span class=""><span class="punctuation">.</span></span><span class=""><span class="variable function">replace</span></span><span class="punctuation">(</span><span class=""><span class="string"><span class="string"><span class="punctuation string">'</span></span></span><span class="string"><span class="string">.<span class="punctuation string">'</span></span></span><span class="punctuation">,</span> <span class="string"><span class="string"><span class="punctuation string">'</span></span></span><span class="string"><span class="string">_<span class="punctuation string">'</span></span></span></span><span class="punctuation">)</span></span>
<span class=""><span class="">index_or_alias_exists</span></span><span class="punctuation variable">:</span> <span class=""><span class="support type">bool</span></span> <span class="keyword operator">=</span> <span class=""><span class=""><span class="">client</span><span class="punctuation">.</span><span class="">indices</span><span class="punctuation">.</span></span><span class=""><span class="variable function">exists</span></span><span class="punctuation">(</span><span class=""><span class="variable parameter">index</span><span class="keyword operator">=</span><span class=""><span class="">index_alias</span></span><span class="punctuation">,</span> <span class="variable parameter">allow_no_indices</span><span class="keyword operator">=</span><span class="constant language">False</span></span><span class="punctuation">)</span></span>
<span class=""><span class="">alias_exists</span></span><span class="punctuation variable">:</span> <span class=""><span class="support type">bool</span></span> <span class="keyword operator">=</span> <span class=""><span class=""><span class="">client</span><span class="punctuation">.</span><span class="">indices</span><span class="punctuation">.</span></span><span class=""><span class="variable function">exists_alias</span></span><span class="punctuation">(</span><span class=""><span class="variable parameter">name</span><span class="keyword operator">=</span><span class=""><span class="">index_alias</span></span></span><span class="punctuation">)</span></span>
<span class=""><span class="keyword control">if</span> <span class=""><span class="">index_or_alias_exists</span></span> <span class="keyword operator">and</span> <span class="keyword operator">not</span> <span class=""><span class="">alias_exists</span></span><span class="punctuation">:</span></span>
<span class="comment"><span class="punctuation comment">#</span> Case 1
</span> <span class=""><span class="">response</span></span> <span class="keyword operator">=</span> <span class=""><span class=""><span class="">client</span><span class="punctuation">.</span><span class="">indices</span><span class="punctuation">.</span></span><span class=""><span class="variable function">create</span></span><span class="punctuation">(</span><span class=""><span class=""><span class="">index_to</span></span><span class="punctuation">,</span> <span class="variable parameter">body</span><span class="keyword operator">=</span><span class=""><span class=""><span class="">runner</span></span></span><span class=""><span class="punctuation">[</span></span><span class=""><span class="string"><span class="string"><span class="punctuation string">'</span></span></span><span class="string"><span class="string">body<span class="punctuation string">'</span></span></span></span><span class=""><span class="punctuation">]</span></span></span><span class="punctuation">)</span></span>
<span class="comment"><span class="punctuation comment">#</span> Now we cannot switch the version, because the old index with the name equal to alias exists
</span> <span class="comment"><span class="punctuation comment">#</span> We must copy first the documents
</span> <span class=""><span class=""><span class="variable function">copy_documents</span></span><span class="punctuation">(</span><span class=""><span class=""><span class="">client</span></span><span class="punctuation">,</span> <span class="variable parameter">index_from</span><span class="keyword operator">=</span><span class=""><span class="">index_alias</span></span><span class="punctuation">,</span> <span class="variable parameter">index_to</span><span class="keyword operator">=</span><span class=""><span class="">index_to</span></span></span><span class="punctuation">)</span></span>
<span class="comment"><span class="punctuation comment">#</span> No failures, so we can remove the old index (yes, it's hard)
</span> <span class=""><span class=""><span class="">client</span><span class="punctuation">.</span><span class="">indices</span><span class="punctuation">.</span></span><span class=""><span class="variable function">delete</span></span><span class="punctuation">(</span><span class=""><span class="variable parameter">index</span><span class="keyword operator">=</span><span class=""><span class="">index_alias</span></span></span><span class="punctuation">)</span></span>
<span class="comment"><span class="punctuation comment">#</span> Now we can switch the version (this means, we create an alias):
</span> <span class=""><span class=""><span class="variable function">switch_version</span></span><span class="punctuation">(</span><span class=""><span class=""><span class="">client</span></span><span class="punctuation">,</span> <span class="variable parameter">index_alias</span><span class="keyword operator">=</span><span class=""><span class="">index_alias</span></span><span class="punctuation">,</span> <span class="variable parameter">version</span><span class="keyword operator">=</span><span class=""><span class="">version</span></span></span><span class="punctuation">)</span></span>
<span class=""><span class="keyword control">elif</span></span> <span class="keyword operator">not</span> <span class=""><span class="">index_or_alias_exists</span></span> <span class="keyword operator">and</span> <span class="keyword operator">not</span> <span class=""><span class="">alias_exists</span></span><span class="punctuation variable">:</span>
<span class="comment"><span class="punctuation comment">#</span> Case 2
</span> <span class=""><span class="">response</span></span> <span class="keyword operator">=</span> <span class=""><span class=""><span class="">client</span><span class="punctuation">.</span><span class="">indices</span><span class="punctuation">.</span></span><span class=""><span class="variable function">create</span></span><span class="punctuation">(</span><span class=""><span class=""><span class="">index_to</span></span><span class="punctuation">,</span> <span class="variable parameter">body</span><span class="keyword operator">=</span><span class=""><span class=""><span class="">runner</span></span></span><span class=""><span class="punctuation">[</span></span><span class=""><span class="string"><span class="string"><span class="punctuation string">'</span></span></span><span class="string"><span class="string">body<span class="punctuation string">'</span></span></span></span><span class=""><span class="punctuation">]</span></span></span><span class="punctuation">)</span></span>
<span class=""><span class=""><span class="variable function">switch_version</span></span><span class="punctuation">(</span><span class=""><span class=""><span class="">client</span></span><span class="punctuation">,</span> <span class="variable parameter">index_alias</span><span class="keyword operator">=</span><span class=""><span class="">index_alias</span></span><span class="punctuation">,</span> <span class="variable parameter">version</span><span class="keyword operator">=</span><span class=""><span class="">version</span></span></span><span class="punctuation">)</span></span>
<span class=""><span class="keyword control">elif</span></span> <span class=""><span class="">index_or_alias_exists</span></span> <span class="keyword operator">and</span> <span class=""><span class="">alias_exists</span></span><span class="punctuation variable">:</span>
<span class="comment"><span class="punctuation comment">#</span> Case 3
</span> <span class="comment"><span class="punctuation comment">#</span> We need the index behind the alias name to migrate the documents
</span> <span class=""><span class="">alias_response</span></span> <span class="keyword operator">=</span> <span class=""><span class=""><span class="">client</span><span class="punctuation">.</span><span class="">indices</span><span class="punctuation">.</span></span><span class=""><span class="variable function">get_alias</span></span><span class="punctuation">(</span><span class=""><span class="variable parameter">name</span><span class="keyword operator">=</span><span class=""><span class="">index_alias</span></span></span><span class="punctuation">)</span></span>
<span class="comment"><span class="punctuation comment">#</span> We hope, we have not a spanning alias, this means only a single result:
</span> <span class=""><span class="keyword control">if</span> <span class=""><span class=""><span class="support function">len</span></span><span class="punctuation">(</span><span class=""><span class=""><span class="">alias_response</span></span></span><span class="punctuation">)</span></span> <span class="keyword operator">!=</span> <span class="constant numeric">1</span><span class="punctuation">:</span></span>
<span class=""><span class="keyword control">raise</span> <span class=""><span class=""><span class="support type">RuntimeError</span></span><span class="punctuation">(</span><span class=""><span class="storage type string">f</span><span class="string"><span class="string"><span class="punctuation string">"</span></span></span><span class="string"><span class="string">The alias </span><span class=""><span class="punctuation">{</span><span class="source"><span class=""><span class="">index_alias</span></span></span></span><span class=""><span class="punctuation">}</span></span><span class="string"> is a spanning alias over more than one index<span class="punctuation string">"</span></span></span></span><span class="punctuation">)</span></span></span>
<span class=""><span class="">index_from</span></span> <span class="keyword operator">=</span> <span class=""><span class=""><span class="support function">next</span></span><span class="punctuation">(</span><span class=""><span class=""><span class=""><span class="support function">iter</span></span><span class="punctuation">(</span><span class=""><span class=""><span class=""><span class="">alias_response</span><span class="punctuation">.</span></span><span class=""><span class="variable function">keys</span></span><span class="punctuation">(</span><span class="punctuation">)</span></span></span><span class="punctuation">)</span></span></span><span class="punctuation">)</span></span>
<span class=""><span class="keyword control">if</span> <span class=""><span class="">index_from</span></span> <span class="keyword operator">!=</span> <span class=""><span class="">index_to</span></span><span class="punctuation">:</span></span>
<span class=""><span class="">response</span></span> <span class="keyword operator">=</span> <span class=""><span class=""><span class="">client</span><span class="punctuation">.</span><span class="">indices</span><span class="punctuation">.</span></span><span class=""><span class="variable function">create</span></span><span class="punctuation">(</span><span class=""><span class=""><span class="">index_to</span></span><span class="punctuation">,</span> <span class="variable parameter">body</span><span class="keyword operator">=</span><span class=""><span class=""><span class="">runner</span></span></span><span class=""><span class="punctuation">[</span></span><span class=""><span class="string"><span class="string"><span class="punctuation string">'</span></span></span><span class="string"><span class="string">body<span class="punctuation string">'</span></span></span></span><span class=""><span class="punctuation">]</span></span></span><span class="punctuation">)</span></span>
<span class=""><span class=""><span class="variable function">copy_documents</span></span><span class="punctuation">(</span><span class=""><span class=""><span class="">client</span></span><span class="punctuation">,</span> <span class="variable parameter">index_from</span><span class="keyword operator">=</span><span class=""><span class="">index_from</span></span><span class="punctuation">,</span> <span class="variable parameter">index_to</span><span class="keyword operator">=</span><span class=""><span class="">index_to</span></span></span><span class="punctuation">)</span></span>
<span class=""><span class=""><span class="variable function">switch_version</span></span><span class="punctuation">(</span><span class=""><span class=""><span class="">client</span></span><span class="punctuation">,</span> <span class="variable parameter">index_alias</span><span class="keyword operator">=</span><span class=""><span class="">index_alias</span></span><span class="punctuation">,</span> <span class="variable parameter">version</span><span class="keyword operator">=</span><span class=""><span class="">version</span></span></span><span class="punctuation">)</span></span>
<span class=""><span class="keyword control">else</span><span class="punctuation">:</span></span>
<span class=""><span class=""><span class="support function">print</span></span><span class="punctuation">(</span><span class=""><span class="storage type string">f</span><span class="string"><span class="string"><span class="punctuation string">"</span></span></span><span class="string"><span class="string">Warning: Existing index </span><span class=""><span class="punctuation">{</span><span class="source"><span class=""><span class="">index_from</span></span></span></span><span class=""><span class="punctuation">}</span></span><span class="string"> is equal to the <span class="punctuation string">"</span></span></span>
<span class="storage type string">f</span><span class="string"><span class="string"><span class="punctuation string">"</span></span></span><span class="string"><span class="string">migration version. Maybe the history was deleted?<span class="punctuation string">"</span></span></span></span><span class="punctuation">)</span></span>
<span class=""><span class="keyword control">else</span><span class="punctuation">:</span></span>
<span class=""><span class="keyword control">raise</span> <span class=""><span class=""><span class="support type">RuntimeError</span></span><span class="punctuation">(</span><span class=""><span class="string"><span class="string"><span class="punctuation string">"</span></span></span><span class="string"><span class="string">No idea, what to do<span class="punctuation string">"</span></span></span></span><span class="punctuation">)</span></span></span>
<span class="keyword control">return</span> <span class=""><span class="">response</span></span>
</span></code></pre>
<p dir="auto">Am Anfang der Methode bereiten wir alle Parameter vor und suchen auf dem OpenSearch Cluster, ob der Index existiert und ob es einen Alias gibt. Die verschiedenen Kombinationen ermöglichen uns, die drei Fälle zu unterscheiden.</p>
<p dir="auto">Ihr seht, Fall 2 ist der einfachste. Fall 3 ist das, was wir in Zukunft immer als Migrationen erwarten und Fall 1 müssen wir implementieren, weil unser altes Migrationsscript keine Versionierung beherrschte.</p>
<p dir="auto">Das war es eigentlich schon.</p>
<p dir="auto">Wenn das neue <a href="https://codeberg.org/beandev/mastodon-playground/src/commit/a4b3cad446ab89421b591d4b3e5570ef71f59496/oshelper/Migration.py" rel="noopener noreferrer"><code>Migration.py</code></a> Script ausgeführt wird (mit dem Aufruf von <a href="https://codeberg.org/beandev/mastodon-playground/src/commit/c7b65c9d5c657ce80a028153449556d197dc15aa/os-migration.py" rel="noopener noreferrer"><code>os-migration.py</code></a>), werden (wenn ihr nicht schon das letzte Mal bei 0 angefangen habt), die Versionen 1.002 und 1.003 (da wo wir toots und accounts anlegen) plötzlich durchlaufen.</p>
<p dir="auto">Wenn ihr dann die Indexe mal im Katalog anschaut, habt ihr ggf. ähnliche Ergebnisse:</p>
<pre><code dir="auto">health status index uuid pri rep docs.count docs.deleted store.size pri.store.size
green open un-followers_v1_003 zcRQJo8RRfuC5d_O_uApkA 2 0 1 0 33.3kb 33.3kb
green open un-following_v1_003 RrgaScgCSIWxZH8wM5wjVg 2 0 0 0 416b 416b
green open following_v1_003 j_S1G_nvQ5-_tP1_stftzw 2 0 142 0 343.9kb 343.9kb
green open migration_history E2Z421iORciEFDRL0RE7xA 2 0 4 0 42.2kb 42.2kb
green open toots_v1_004 5EDW1jbpTCmxQZ2s97QcYQ 2 0 6323 0 5.1mb 5.1mb
green open followers_v1_003 5aFps3PzRCaFUbMWtkFWpw 2 0 212 0 413.4kb 413.4kb
green open .kibana_1 oPhh4WkXTha54EXU4SdJ7A 1 0 1 0 5kb 5kb
green open toots_v1_002 o7OXHpaZQoebIqxhRdkz4g 2 0 6000 0 5.1mb 5.1mb
</code></pre>
<p dir="auto">Die Version v1_004 für die Toots kommt mit dem neuen <a href="https://codeberg.org/beandev/mastodon-playground/src/commit/a4b3cad446ab89421b591d4b3e5570ef71f59496/os/migration/V01.004__MigrateTootsIndex.json" rel="noopener noreferrer">Migrations JSON</a> aus dem git-Repo (ich brauchte ja was zum Testen).</p>
<h1 dir="auto">Prolog</h1>
<p dir="auto">Es gibt immer noch Szenarien, die das Script nicht 100% abfangen kann. Aber mit etwas mehr als 400 Programmzeilen haben wir ein Migrationsscript, was den Aufbau einer Index-Collection erlaubt und sogar Veränderungen durchführt, ohne die Dokumente aus den alten Indexen zu verlieren. Kleinigkeiten fehlen noch: Die alten Indexe sollten unmittelbar geschlossen werden (damit niemand reinschreibt). Alte Indexe müssten auch mal irgendwann gelöscht werden. Und da wir aber in der Zwischenzeit die alten Indexe uns merken, wäre ein Undo einer Migration toll (ok, dann dürfen wir aber auch nicht hart einen Index löschen).</p>
<p dir="auto">Das heben wir uns für einen anderen Artikel auf.</p>
<p dir="auto">Wie immer würde es mich freuen, Feedback zu bekommen und vielleicht auch ein Boost des Artikels. </p>
]]><![CDATA[Index Alias - Warum man in OpenSearch mehrere Namen für Indexe benötigt]]>https://write.tchncs.de/~/BeanDevOpenSearch/Index%20Alias%20-%20Warum%20man%20in%20OpenSearch%20mehrere%20Namen%20für%20Indexe%20benötigt/2022-06-02T13:18:08.689605+00:00Aljoscha Rittnerhttps://write.tchncs.de/@/beandev/2022-06-02T13:18:08.689605+00:00<![CDATA[<h1 dir="auto">Einleitung</h1>
<p dir="auto">Zunächst erscheint es seltsam, dass man mehrere Namen für einen Index benötigt. In der Softwareentwicklung mag man es lieber exakt, da gehören eindeutige Namen dazu.</p>
<p dir="auto">Wenn man aber unterschiedliche Systeme hat, die miteinander kommunizieren und diese Systeme entkoppelt sind, werden Alias-Namen zu einem fundamentalen Werkzeug. Mit OpenSearch haben wir zum Beispiel ein System und unser Python-Script ist als Client ein anderes System. Jetzt kann es vorkommen, dass wir in OpenSearch Operationen durchführen, die sich in Umstrukturierungen der Indexe auswirken. Vielleicht wollen wir das Mapping verändern. Oder wir wollen Dokumente aus dem Index herausnehmen und archivieren. Eventuell wollen wir einen Index aufteilen und die Daten auf zwei verschiedenen Cluster verteilen. Es kann auch sein, dass wir eine große Anzahl an Dokumenten neu hinzufügen wollen oder bestehende aktualisieren müssen. </p>
<p dir="auto">All diese Aktionen können für Clients “verwirrend” sein, weil die durchgeführten Änderungen zu nicht mehr nachvollziehbaren Suchergebnissen führen. Zudem sind einige Index-Manipulationen gar nicht möglich. Mappings kann man hinzufügen, aber bestehende nicht mehr ändern. Das geht nur, in dem man einen neuen Index anlegt und die Dokumente überträgt. Aber neuer Index bedeutet auch ein neuer Name. Da müssten wir dann alle Clients ändern, damit die den neuen Index-Namen wissen?</p>
<p dir="auto">Man sieht, es gibt einiges an Gründen, einen Index unter einem mehreren Namen zu erreichen. Üblicherweise hat man einen stabilen “public API” Namen, der für die Clients eine verlässliche Adresse ist. Hinter diesem Alias verstecken wir den Index mit einem internen technischen Namen. Haben wir uns entschieden, dass dieser bisherige Index nicht mehr unseren Ansprüchen genügt, legen wir einen neuen an, ggf. mit besserem Mapping, transferieren die Dokumente von dem aktuellen Index in den neuen und übertragen dann den Alias-Namen abschließend auf den neuen Index. Für alle Clients sieht es so aus, als würden mit einem Schlag sich alle Inhalte des Index geändert haben.</p>
<h1 dir="auto">Index Alias Beispiele</h1>
<p dir="auto">Beispiel:</p>
<ul dir="auto">
<li>Wir haben den Index <code>playground-toots</code> mit einem einigermaßen guten Mapping.</li>
<li>Zu diesem Index vergeben wir einen guten Alias-Namen, der einfach zu nutzen ist: <code>toots</code></li>
<li>Die Clients (und unser Python Script) nutzen immer <code>toots</code></li>
<li>Nun stellen wir fest, dass wir die Accounts gerne anders mappen wollen. Auch der HTML Tokenizer sollte besser laufen. Das können wir auf dem bestehenden <code>playground-toots</code> nicht machen. Mappings sind sehr statisch und eine Änderung ist nicht möglich.</li>
<li>Wir ändern das <code>standard</code> Index Template und legen einen verbesserten HTML Analyzer hinzu.</li>
<li>Wir legen einen neuen Index an: <code>playouground-toots-v2</code>
<ul dir="auto">
<li>Hier definieren wir das neue Mapping und der Analyzer wird aus dem <code>standard</code> Template genommen</li>
<li>An dem bisherigen <code>playground-toots</code> hat sich nichts geändert, alle Clients lesen den bisherigen Stand über <code>toots</code> aus</li>
</ul>
</li>
<li>Jetzt übertragen wir alle Dokumente aus dem alten Index <code>playground-toots</code> in den neuen <code>playouground-toots-v2</code>. Das wird ein paar Sekunden, bis Minuten dauern. Je nachdem, wie viele Dokumente wir haben.
<ul dir="auto">
<li>Immer noch bemerkt niemand was von unserer Aktion</li>
</ul>
</li>
<li>Jetzt kommt der entscheidende Moment. Wir löschen den Alias <code>toots</code> von <code>playground-toots</code> und fügen <code>playouground-toots-v2</code> den Alias <code>toots</code> hinzu. OpenSearch kann das sogar in einem Schritt machen.
<ul dir="auto">
<li>Die Clients, die nun weitere Abfragen machen, arbeiten unmittelbar auf dem neuen technischen Index, aber weiterhin über den Namen <code>toots</code></li>
</ul>
</li>
</ul>
<p dir="auto">Die grafische Darstellung:</p>
<p dir="auto"><img src="https://codeberg.org/beandev/mastodon-playground/raw/branch/main/documentation/os/index-alias/versioning-alias.png" alt="versioning"></p>
<p dir="auto">Beispiel:</p>
<ul dir="auto">
<li>Wir haben einen schnell wachsenden Index (aus Logdaten zum Beispiel) und müssen die Indexe nach Monaten aufteilen</li>
<li>Jeden Monat wird ein neuer Index angelegt (mit _YYYY_MM als Suffix) und der Alias des aktuellen Index <code>log</code> wird auf den letzten neu angelegten Index gesetzt.</li>
<li>Da man aber alle Indexe gemeinsam lesen will, glegt man noch einen Alias an, der über mehrere Indexe geht. Zum Beispiel über die letzten 3 Monate.</li>
</ul>
<p dir="auto">Die grafische Darstellung:</p>
<p dir="auto"><img src="https://codeberg.org/beandev/mastodon-playground/raw/branch/main/documentation/os/index-alias/spanning_index_alias.png" alt="spanning"></p>
<p dir="auto">Man sieht, es gibt einiges an Lösungen. Ein Index kann mehrere Aliase bekommen, oder ein Alias kann mehrere Indexe umfassen. Im letzteren Fall muss man aber festlegen, in welchen Index real geschrieben werden soll, wenn ein Dokument über den “überspannenden” Alias gespeichert wird.</p>
<p dir="auto">Es ist sogar möglich, jeden Index über den Alias einem Filter zuzuordnen. Greift man direkt auf den echten Namen zu, kann man alle Dokumente erhalten. Greift man über den Alias zu, werden Dokumente ausgefiltert, wenn man es definiert hat.</p>
<h1 dir="auto">Ausblick</h1>
<p dir="auto">Im nächsten Blog-Artikel werden wir das Beispiel der Versionierung in unser <code>Migration.py</code> Modul einbauen. Denn aktuell können wir nur Indexe erstellen und keine Mappings manipulieren. Da das technisch sowieso nicht geht (bzw., nur sehr eingeschränkt), müssen wir die obigen Schritte implementieren. </p>
]]><![CDATA[OpenSearch Scripte gehen ins Codeberg Repository]]>https://write.tchncs.de/~/BeanDevOpenSearch/OpenSearch%20Scripte%20gehen%20ins%20Codeberg%20Repository/2022-06-01T16:38:27.971617+00:00Aljoscha Rittnerhttps://write.tchncs.de/@/beandev/2022-06-01T16:38:27.971617+00:00<![CDATA[<h1 dir="auto">Einleitung</h1>
<p dir="auto">Wie schon in meinem anderen <a href="https://write.tchncs.de/%7E/BeanDevMastodon/Blog-Artikel%20Scripte%20migrieren%20nach%20%23Codeberg" rel="noopener noreferrer">Blog-Artikel</a> werde ich nun auch die OpenSearch-Scripte für das Erstellen der Indexe für Toots und Accounts (inklusive Index-Template) in mein <a href="https://codeberg.org/beandev/mastodon-playground" rel="noopener noreferrer">Codeberg Repository</a> integrieren. </p>
<p dir="auto">Da werde ich zudem ein grundlegendes Refactoring vornehmen und hier beschreiben, um eine Index-Collection immer wieder von Grund auf neu zu erstellen und in einer späteren Iteration bestehende Indexe zu updaten.</p>
<p dir="auto">Das erfordert aber noch ein bis zwei weitere Artikel mit entsprechenden Grundlagen.</p>
<h1 dir="auto">Das Refactoring</h1>
<h2 dir="auto">Login Konfiguration</h2>
<p dir="auto">Ähnlich wie das <a href="https://codeberg.org/beandev/mastodon-playground/src/branch/main/mastobot/MastodonHelper.py" rel="noopener noreferrer">MastodonHelper.py</a> Script benötigen wir ein <code>OpenSearchHelper</code>, um aus einer Konfiguration die Zugriffsinformationen zu laden. Damit machen wir die Scripte im Codeberg-git Repository unabhängig lokaler Testumgebungen. </p>
<p dir="auto">Da es eine massiv reduzierte Variante des MastodonHelper.py Scripts ist, werde ich das hier nicht weiter ausführen. Nur zur Erinnerung: Das Repository enthält eine <a href="https://codeberg.org/beandev/mastodon-playground/src/commit/b89e886dfe6476426d3f941c84fa8ab6f13f5649/oscluster-config.yaml" rel="noopener noreferrer">oscluster-config.yaml</a> Datei im Basisordner und hat nur die Grundkonfiguration eines Dev-Systems:</p>
<pre><code dir="auto"><span class="source"><span class="comment"><span class="punctuation comment">#</span> Nodes to access.
</span><span class="string"><span class="entity name tag">nodes</span></span><span class="punctuation">:</span>
<span class="string"><span class="entity name tag">default</span></span><span class="punctuation">:</span>
<span class="string"><span class="entity name tag">host</span></span><span class="punctuation">:</span> <span class="constant numeric">127.0.0.1</span> <span class="comment"><span class="punctuation comment">#</span> your master node
</span> <span class="string"><span class="entity name tag">port</span></span><span class="punctuation">:</span> <span class="constant numeric">9200</span> <span class="comment"><span class="punctuation comment">#</span> port for the node
</span></span></code></pre>
<p dir="auto">Wenn das so passt, alles gut. Wenn ihr einen anderen Host oder Port habt, dann kopiert die Datei in euer Home-Verzeichnis und ändert die Parameter. Das <a href="https://codeberg.org/beandev/mastodon-playground/src/commit/b89e886dfe6476426d3f941c84fa8ab6f13f5649/oshelper/OpenSearchHelper.py" rel="noopener noreferrer">OpenSearchHelper.py</a> Modul sucht dort zuerst und nimmt von dort die Parameter:</p>
<pre><code dir="auto"><span class="source"> <span class=""><span class="keyword control">if</span> <span class="keyword operator">not</span> <span class=""><span class=""><span class="">os</span><span class="punctuation">.</span><span class="">path</span><span class="punctuation">.</span></span><span class=""><span class="variable function">isabs</span></span><span class="punctuation">(</span><span class=""><span class=""><span class="">config_yaml</span></span></span><span class="punctuation">)</span></span><span class="punctuation">:</span></span>
<span class="comment"><span class="punctuation comment">#</span> First we check the home folder (will override the standard config from repository)
</span> <span class=""><span class="">config_home</span></span><span class="keyword operator">=</span><span class=""><span class=""><span class="">os</span><span class="punctuation">.</span><span class="">path</span><span class="punctuation">.</span></span><span class=""><span class="variable function">expanduser</span></span><span class="punctuation">(</span><span class=""><span class=""><span class=""><span class="">os</span><span class="punctuation">.</span><span class="">path</span><span class="punctuation">.</span></span><span class=""><span class="variable function">join</span></span><span class="punctuation">(</span><span class=""><span class="string"><span class="string"><span class="punctuation string">"</span></span></span><span class="string"><span class="string">~<span class="punctuation string">"</span></span></span><span class="punctuation">,</span> <span class=""><span class="">config_yaml</span></span></span><span class="punctuation">)</span></span></span><span class="punctuation">)</span></span>
<span class=""><span class="keyword control">if</span> <span class=""><span class=""><span class="">os</span><span class="punctuation">.</span><span class="">path</span><span class="punctuation">.</span></span><span class=""><span class="variable function">exists</span></span><span class="punctuation">(</span><span class=""><span class=""><span class="">config_home</span></span></span><span class="punctuation">)</span></span><span class="punctuation">:</span></span>
<span class=""><span class="">config_yaml</span></span> <span class="keyword operator">=</span> <span class=""><span class="">config_home</span></span>
</span></code></pre>
<p dir="auto">Mit der Zeit werden wir das Modul erweitern, aber so reicht uns das zunächst.</p>
<p dir="auto">So kann man es testen (siehe <a href="https://codeberg.org/beandev/mastodon-playground/src/commit/b89e886dfe6476426d3f941c84fa8ab6f13f5649/os-status.py" rel="noopener noreferrer"><code>os-status.py</code></a>):</p>
<pre><code dir="auto"><span class="source"><span class=""><span class="keyword control">import</span> <span class=""><span class="">json</span></span></span>
<span class=""><span class="keyword control">import</span> <span class=""><span class="">oshelper</span><span class="punctuation">.</span><span class="">OpenSearchHelper</span></span></span>
<span class=""><span class="keyword control">from</span></span><span class=""><span class=""> <span class=""><span class="">opensearchpy</span></span> <span class=""><span class="keyword control">import</span></span></span></span><span class=""></span><span class=""> <span class="">OpenSearch</span></span>
<span class=""><span class="">client</span></span><span class="punctuation variable">:</span> <span class=""><span class="">OpenSearch</span></span> <span class="keyword operator">=</span> <span class=""><span class=""><span class="">oshelper</span><span class="punctuation">.</span><span class="">OpenSearchHelper</span><span class="punctuation">.</span></span><span class=""><span class="variable function">os_open</span></span><span class="punctuation">(</span><span class=""><span class="variable parameter">config_yaml</span><span class="keyword operator">=</span><span class="string"><span class="string"><span class="punctuation string">"</span></span></span><span class="string"><span class="string">oscluster-config.yaml<span class="punctuation string">"</span></span></span></span><span class="punctuation">)</span></span>
<span class=""><span class="">indexes</span></span> <span class="keyword operator">=</span> <span class=""><span class=""><span class="">client</span><span class="punctuation">.</span><span class="">indices</span><span class="punctuation">.</span></span><span class=""><span class="variable function">get</span></span><span class="punctuation">(</span><span class=""><span class="variable parameter">index</span><span class="keyword operator">=</span><span class="string"><span class="string"><span class="punctuation string">"</span></span></span><span class="string"><span class="string">*<span class="punctuation string">"</span></span></span></span><span class="punctuation">)</span></span>
<span class=""><span class=""><span class="support function">print</span></span><span class="punctuation">(</span><span class=""><span class=""><span class=""><span class="">json</span><span class="punctuation">.</span></span><span class=""><span class="variable function">dumps</span></span><span class="punctuation">(</span><span class=""><span class=""><span class="">indexes</span></span><span class="punctuation">,</span> <span class="variable parameter">indent</span><span class="keyword operator">=</span><span class="constant numeric">4</span><span class="punctuation">,</span> <span class="variable parameter">default</span><span class="keyword operator">=</span><span class=""><span class="support type">str</span></span></span><span class="punctuation">)</span></span></span><span class="punctuation">)</span></span>
</span></code></pre>
<h2 dir="auto">Index Erstellung</h2>
<p dir="auto">Die bisherigen Scripte zum Erstellen der Indexe für die unterschiedlichen Dokumente (unsere Toots und die Accounts der Followings und Followers) waren alles einzelne Sammlungen, die wir immer in einer bestimmten Reihenfolge aufrufen mussten. Das ist ein fehlerträchtiger Vorgang und erfordert Wissen über die Vorgänge und ist weit entfernt von einer Automatisierung, die man sich von Scripten eigentlich erhofft. Es war aber auch nicht die Intention der letzten Artikel. Da ich aber weiter fortschreitende Techniken für OpenSearch vorstellen möchte, macht es inzwischen Sinn einige Sachen nicht immer manuell durchzuführen.</p>
<p dir="auto">OpenSearch ist sehr befehlsorientiert, das über Schnittstellen für Entwickler. Es gibt wenige UI Tools, die dabei unterstützen und in unserem Python-Umfeld ist uns das auch egal. Wir brauchen also ein Tool, dass uns ein generelles Setup unserer Indexe erstellt, Schritt für Schritt und möglichst ohne den Sourcecode deutlich verändern zu müssen. Mir schwebt da eine Trennung zwischen OpenSearch API Aufrufen und Request-Objekten vor.</p>
<p dir="auto">Was haben wir bisher gehabt?</p>
<ol dir="auto">
<li>Index Templates erzeugen</li>
<li>Index erstellen
<ul dir="auto">
<li>Index Konfiguration</li>
<li>Index Mapping</li>
</ul>
</li>
</ol>
<p dir="auto">Dazu noch</p>
<ul dir="auto">
<li>Index löschen</li>
</ul>
<p dir="auto">Das sind alles unterschiedliche API Endpunkte unterschiedlicher APIs (<a href="https://opensearch.org/docs/latest/opensearch/index-templates/" rel="noopener noreferrer">Index Template API</a>, <a href="https://opensearch.org/docs/latest/opensearch/rest-api/index-apis/index/" rel="noopener noreferrer">Index API</a>). Die müssen wir unter einen Hut bringen, möglichst in korrekte Reihenfolge.</p>
<p dir="auto">Wenn wir unsere Request-Objekte (Payload) der API Endpunkte also vom Code trennen (und gesondert abspeichern), müssen wir später wissen, zu was für einem Aufruf die gehören. Als Speicherformat nehme ich natürlich JSON, das bietet sich an. Ich kann damit auch gleich in dem JSON Objekt abspeichern, wohin der Request gehört. Diese “Metainformation” sollte aber nicht mit dem Payload des Requests interferieren. Also trenne ich die Information ebenfalls in dem Speicher-Objekt.</p>
<p dir="auto">Beispiel:</p>
<pre><code dir="auto"><span class="source"><span class=""><span class="punctuation">{</span>
<span class=""><span class="string"><span class="punctuation string">"</span>run<span class="punctuation string">"</span></span></span><span class=""><span class="punctuation">:</span> <span class=""><span class="punctuation">[</span>
<span class=""><span class="punctuation">{</span><span class=""><span class="string"><span class="punctuation string">"</span>call<span class="punctuation string">"</span></span></span><span class=""><span class="punctuation">:</span> <span class="string"><span class="punctuation string">"</span>create template<span class="punctuation string">"</span></span><span class="punctuation">,</span></span> <span class=""><span class="string"><span class="punctuation string">"</span>template_name<span class="punctuation string">"</span></span></span><span class=""><span class="punctuation">:</span> <span class="string"><span class="punctuation string">"</span>standard<span class="punctuation string">"</span></span><span class="punctuation">,</span></span> <span class=""><span class="string"><span class="punctuation string">"</span>body<span class="punctuation string">"</span></span></span><span class=""><span class="punctuation">:</span> <span class="string"><span class="punctuation string">"</span>create_standard<span class="punctuation string">"</span></span></span><span class="punctuation">}</span></span>
<span class="punctuation">]</span></span><span class="punctuation">,</span></span>
<span class=""><span class="string"><span class="punctuation string">"</span>payloads<span class="punctuation string">"</span></span></span><span class=""><span class="punctuation">:</span> <span class=""><span class="punctuation">{</span>
<span class=""><span class="string"><span class="punctuation string">"</span>create_standard<span class="punctuation string">"</span></span></span><span class=""><span class="punctuation">:</span> <span class=""><span class="punctuation">{</span>
<span class=""><span class="string"><span class="punctuation string">"</span>index_patterns<span class="punctuation string">"</span></span></span> <span class=""><span class="punctuation">:</span> <span class=""><span class="punctuation">[</span><span class="string"><span class="punctuation string">"</span>*<span class="punctuation string">"</span></span><span class="punctuation">]</span></span><span class="punctuation">,</span></span>
<span class=""><span class="string"><span class="punctuation string">"</span>priority<span class="punctuation string">"</span></span></span> <span class=""><span class="punctuation">:</span> <span class="constant numeric">1</span><span class="punctuation">,</span></span>
<span class=""><span class="string"><span class="punctuation string">"</span>template<span class="punctuation string">"</span></span></span><span class=""><span class="punctuation">:</span> <span class=""><span class="punctuation">{</span>
<span class=""><span class="string"><span class="punctuation string">"</span>settings<span class="punctuation string">"</span></span></span> <span class=""><span class="punctuation">:</span> <span class=""><span class="punctuation">{</span>
<span class="invalid">#</span><span class="invalid">.</span><span class="invalid">.</span><span class="invalid">.</span>
<span class="punctuation">}</span></span>
</span><span class="punctuation">}</span></span>
</span><span class="punctuation">}</span></span>
</span><span class="punctuation">}</span></span>
</span><span class="punctuation">}</span></span>
</span></code></pre>
<p dir="auto">Das <code>run</code> Attribut ist unsere Liste an Befehlen. Mit <code>call</code> definieren wir unseren API-Aufruf und weitere Attribute sind “well-known” Parameter für die Funktion, die unser Payload bekommt. Das Payload ist erstmal nur das pure Request Body Objekt, das wir 1:1 an die OpenSearch Python-Library übergeben.</p>
<p dir="auto">Wir benötigen also ein Script, dass dieses JSON Objekt übergeben bekommt, <code>run</code> ausliest und je nach Inhalt von <code>call</code> eine Funktion mit Parametern und Body aufruft. Wenn wir davon ausgehen, dass wir mehrere API Endpunkt-Aufrufe haben, gibt es mehrere JSON Dateien. Dazu eine definierte Reihenfolge.</p>
<p dir="auto">Die obige Struktur wirkt erstmal unnötig komplex. Ich erkläre das später.</p>
<p dir="auto">Am einfachsten ist, wir speichern die JSON Objekte mit Dateinamen, die man sortieren kann und zugleich noch direkt am Namen erkennen kann, was sie tun. Beispiele (und irgendjemand wird nun sehen, woher her ich die “Idee” habe):</p>
<pre><code dir="auto">V01.000__CreateIndexTemplate.json
V01.001__CreateIndexToots.json
V01.002__CreateIndexFollowers.json
V01.003__CreateIndexFollowings.json
V01.004__CreateIndexUnFollowers.json
V01.005__CreateIndexUnFollowings.json
</code></pre>
<p dir="auto">Das sieht schwer nach <a href="https://flywaydb.org" rel="noopener noreferrer">Flyway</a> aus - und ja - das können wir von der Idee genau so übernehmen. Allerdings trivialer und für unsere Zwecke angemessen.</p>
<p dir="auto">Wenn aber oben im JSON sieht, dass wir mit Listen von Befehlen arbeiten, kann man das Erstellen der Accounts schon zusammenfassen:</p>
<pre><code dir="auto">V01.000__CreateIndexTemplate.json
V01.001__CreateIndexToots.json
V01.002__CreateIndexAccounts.json
</code></pre>
<p dir="auto">…wir sind ja faul.</p>
<p dir="auto">Es gibt da noch Punkte, die wir jetzt noch ausklammern werden (aber später hinzufügen):</p>
<ol dir="auto">
<li>Updates von Indexen mit Daten</li>
<li>Erstellen von beliebigen Index-Collections in eigenem Namensraum
<ul dir="auto">
<li>Damit wir mit verschiedenen Versionen und Testständen arbeiten können</li>
</ul>
</li>
</ol>
<p dir="auto">Ihr seht, wir haben einiges vor.</p>
<h2 dir="auto">Migration.py</h2>
<p dir="auto">Das neue Modul-Script <a href="https://codeberg.org/beandev/mastodon-playground/src/commit/5b0f0376ae255002fd4ee80ed96df9443394e61c/documentation/os/Migration.puml" rel="noopener noreferrer">Migration.py</a> soll die technische Automatisierung übernehmen, was wir bisher immer als einzelne Python Scripts aufgerufen haben. Die API Endpunkt-Aufrufe werden in einem Ordner gesammelt. </p>
<p dir="auto">Das Script umfasst weniger als 300 Zeilen und viel ist davon Dokumentation und Validierungen, ob alle Daten einigermaßen passen.</p>
<p dir="auto">Ein sehr grobes Aktivitätsdiagramm einer Migration:</p>
<p dir="auto"><img src="https://codeberg.org/beandev/mastodon-playground/raw/branch/main/documentation/os/Migration_migration.png" alt="Migration activity-flow"></p>
<p dir="auto">Es gibt eine Menge Hilfsfunktionen in dem Script, auf die ich nur mal als Hinweis darauf eingehen kann:</p>
<ul dir="auto">
<li><code>list_migration_files</code>
<ul dir="auto">
<li>Gibt eine sortierte Liste an Dateien eines Ordners zurück. Es werden nur Dateien mit einem bestimmten Muster gelistet und die Sortierung erfolgt nach der Versionsnummer. Es sind nur Versionnummern mit Haupt und Unterversionen zulässig.</li>
</ul>
</li>
<li><code>_load_migration</code>
<ul dir="auto">
<li>Lädt ein Migration File als JSON in ein <code>dict</code> und reichert das Dictionary per <code>_inject_migration_meta</code> mit Zusatzinformationen an. Das wird später genutzt, um auch etwas in den <code>migration_history</code> Index zu schreiben.</li>
</ul>
</li>
<li><code>_resolve_migration_runners</code>
<ul dir="auto">
<li>Sieht sehr kompliziert aus. Aber es ordnet nur dem <code>body</code> Attribut einer Befehlszeile den passenden <code>payload</code> zu. Damit können wir mehrere Befehle mit immer dem selben Payload durchführen. Schaut dazu das <a href="https://codeberg.org/beandev/mastodon-playground/src/commit/c7b65c9d5c657ce80a028153449556d197dc15aa/os/migration/V01.003__CreateAccountsIndexes.json" rel="noopener noreferrer">V01.003__CreateAccountsIndexes.json</a> an.</li>
</ul>
</li>
<li><code>_run_create_index</code> und <code>_run_create_index_template</code>
<ul dir="auto">
<li>Hier passiert der Aufruf der OpenSearch-API Endpunkte. Diese beiden Methoden werden als Callback Functions in der Map <code>_CALLBACK_RUNNERS</code> gespeichert und <code>_run_migration_runner</code> ist die Methode, die anhand der <code>call</code>-Attribute in den JSON Dateien entscheidet, welcher API Aufruf erfolgt. Also eine reine Übersetzung von Befehl im Script zur Function im Python-Script.</li>
</ul>
</li>
<li><code>migration_history_prepare</code>
<ul dir="auto">
<li>Das ist die simple Version einer Migration, die nur den <code>migration_history</code> Index anlegt, damit wir später verfolgen können, welche Scripte bereits migriert wurden (und in Zukunft ignoriert werden können)</li>
</ul>
</li>
<li><code>migration_history_delete</code>
<ul dir="auto">
<li>Löscht den <code>migration_history</code> Index (zum Testen)</li>
</ul>
</li>
<li><code>_check_migrated</code>
<ul dir="auto">
<li>Wird von der <code>migration</code> Funktion benötigt, um zu schauen, ob eine Version schon migriert wurde. Das ist ein schönes Beispiel einer exakten Filter-Suche auf einen Index. </li>
</ul>
</li>
<li><code>_create_history</code>
<ul dir="auto">
<li>Bereitet das History Dokument mit allen Metadaten vor. Danach wird erst versucht das Script auszuführen.</li>
</ul>
</li>
<li><code>_update_history_success</code>
<ul dir="auto">
<li>Wenn ein Script einer Version erfolgreich war wird das History Dokument mit den Daten versorgt, damit das Migrationsscript in Zukunft nicht mehr ausgeführt wird (<code>success=True</code> und ein paar weitere Werte)</li>
</ul>
</li>
<li><code>_update_history_error</code>
<ul dir="auto">
<li>Gab es ein Fehler bei der Script-Ausführung, fügen wir hier den Fehler an das <code>message</code> Attribut an. Als klassisches “get + alter + index”, also Dokument lesen, dann die Attribute ändern, dann das geänderte Dokument mit der selben <code>_id</code> wieder speichern.</li>
</ul>
</li>
<li><code>migration</code>
<ul dir="auto">
<li>Last, but not least, der Befehl zur vollständigen Migration. </li>
</ul>
</li>
</ul>
<p dir="auto">Der Aufruf ist einfach (<a href="https://codeberg.org/beandev/mastodon-playground/src/commit/c7b65c9d5c657ce80a028153449556d197dc15aa/os-migration.py" rel="noopener noreferrer">os-migration.py</a>):</p>
<pre><code dir="auto"><span class="source"><span class=""><span class="keyword control">import</span> <span class=""><span class="">os</span><span class="punctuation">.</span><span class="">path</span></span></span>
<span class=""><span class="keyword control">import</span> <span class=""><span class="">oshelper</span><span class="punctuation">.</span><span class="">OpenSearchHelper</span></span></span>
<span class=""><span class="keyword control">from</span></span><span class=""><span class=""> <span class=""><span class="">opensearchpy</span></span> <span class=""><span class="keyword control">import</span></span></span></span><span class=""></span><span class=""> <span class="">OpenSearch</span></span>
<span class=""><span class="keyword control">from</span></span><span class=""><span class=""> <span class=""><span class="">oshelper</span></span> <span class=""><span class="keyword control">import</span></span></span></span><span class=""></span><span class=""> <span class="">Migration</span></span>
<span class=""><span class="">client</span></span><span class="punctuation variable">:</span> <span class=""><span class="">OpenSearch</span></span> <span class="keyword operator">=</span> <span class=""><span class=""><span class="">oshelper</span><span class="punctuation">.</span><span class="">OpenSearchHelper</span><span class="punctuation">.</span></span><span class=""><span class="variable function">os_open</span></span><span class="punctuation">(</span><span class=""><span class="variable parameter">config_yaml</span><span class="keyword operator">=</span><span class="string"><span class="string"><span class="punctuation string">"</span></span></span><span class="string"><span class="string">oscluster-config.yaml<span class="punctuation string">"</span></span></span></span><span class="punctuation">)</span></span>
<span class=""><span class="">path</span></span> <span class="keyword operator">=</span> <span class=""><span class=""><span class="">os</span><span class="punctuation">.</span><span class="">path</span><span class="punctuation">.</span></span><span class=""><span class="variable function">abspath</span></span><span class="punctuation">(</span><span class=""><span class="string"><span class="string"><span class="punctuation string">"</span></span></span><span class="string"><span class="string">./os/migration/<span class="punctuation string">"</span></span></span></span><span class="punctuation">)</span></span>
<span class=""><span class=""><span class="">Migration</span><span class="punctuation">.</span></span><span class=""><span class="variable function">migration</span></span><span class="punctuation">(</span><span class=""><span class=""><span class="">client</span></span><span class="punctuation">,</span> <span class=""><span class="">path</span></span></span><span class="punctuation">)</span></span>
</span></code></pre>
<p dir="auto">Über den <code>oshelper.OpenSearchHelper</code> verbinden wir uns an OpenSearch. Im <code>path</code> finden sich die Migrationsscripte. Das zusammen übergeben wir an <code>Migration.migration</code></p>
<p dir="auto">Wenn ihr mal alle eure Indexe löscht, um eine volle Migration zu testen (ja, da gehen die alten Daten verloren), dann bekommt man diese Ausgabe:</p>
<pre><code dir="auto">Run V01.001__CreateTemplate.json
success
Run V01.002__CreateTootsIndex.json
success
Run V01.003__CreateAccountsIndexes.json
success
</code></pre>
<p dir="auto">Wenn ihr den toots-Index und die vier Indexe für die Account nicht wegwerfen wollt, dann gibt es folgende Ausgabe:</p>
<pre><code dir="auto">Run V01.001__CreateTemplate.json
success
Run V01.002__CreateTootsIndex.json
error
</code></pre>
<p dir="auto">Das <code> V01.001__CreateTemplate.json</code> ist erfolgreich, weil die API create oder update macht. Das Template wird also neu angelegt oder aktualisiert.</p>
<p dir="auto">Das Erstellen des <code>toots</code> Index wird aber mit Fehler quittiert. Danach bricht die Migration ab. Alle Migrationsschritte sind strikt aufeinanderfolgend. </p>
<p dir="auto">Ruft ihr jetzt <em>nochmal</em> das Script auf, gibt es nur noch diese Ausgabe:</p>
<pre><code dir="auto">Run V01.002__CreateTootsIndex.json
error
</code></pre>
<p dir="auto">Die Version <code>01.001</code> wurde nicht mehr aufgrufen, weil in der Historie vermerkt wurde, dass das schon mal erfolgreich war. Stoisch versuchte aber die <code>migration</code>-Funktion es nochmal mit V01.002__CreateTootsIndex.json zu probieren - vergeblich, damit ein sofortiger Abbruch.</p>
<p dir="auto">Die message_history wird so abgefragt:</p>
<p dir="auto"><a href="http://localhost:9200/migration_history/_search" rel="noopener noreferrer">http://localhost:9200/migration_history/_search</a></p>
<p dir="auto">Achtung, das Ergebnis ist nicht sortiert! Je nachdem, wie häufig man Fehler auf einer Version bekommen hat, desto mehr steht in dem <code>message</code> Attribut.</p>
]]><![CDATA[Blog-Artikel Scripte migrieren nach #Codeberg]]>https://write.tchncs.de/~/BeanDevMastodon/Blog-Artikel%20Scripte%20migrieren%20nach%20%23Codeberg/2022-05-25T19:21:12.625018+00:00Aljoscha Rittnerhttps://write.tchncs.de/@/beandev/2022-05-25T19:21:12.625018+00:00<![CDATA[<h1 dir="auto">Einleitung</h1>
<p dir="auto"><a href="https://codeberg.org/" rel="noopener noreferrer">Codeberg</a> basiert auf <a href="https://gitea.io/en-us/" rel="noopener noreferrer">gitea</a> und wird von einem Berliner Verein zur Verfügung gestellt und es ist schnell meine erste Wahl geworden. Die Funktionen sind umfangreich, ausgereift und es arbeitet, wie Plume, mit <a href="https://commonmark.org/" rel="noopener noreferrer">Common Markdown</a>. </p>
<p dir="auto">Ich werde aber nicht die bisherigen Scripte 1:1 so übernehmen, wie ich sie in den letzten Artikeln beschrieben hatte. Ich habe ein paar Refactorings vorgenommen, insbesondere in dem Bereich, wo es um den Zugriff auf die Mastodon API und OpenSearch geht. Die Konfiguration der Instanzen und Nodes, Client-IDs, Secrets und Login-Daten werden jetzt natürlich nicht mehr in den Scripten sein. Gerade Mastodon.py unterstützt sehr gut dabei, dass man von Anfang an die Konfiguration von dem Script-Code trennt. Ich hatte das nur bei den Beispielen erstmal gelassen, weil sie so schneller auszuprobieren waren. </p>
<p dir="auto">Da jetzt die Snippets auch aus dem git zu laden sind (und damit auch mal komplexer sein können), ist es klar, dass solche “Komfortfunktionen” integriert werden können, ohne auf den einfachen Charakter des Blogs verzichten zu müssen. D.h. hier bleiben die Beispiele einfach und skizzieren, wie man Lösungen anstrebt und im Repository finden sich dann besser ausformulierte Lösungen. </p>
<h1 dir="auto">Modul MastodonHelper</h1>
<p dir="auto">Damit man komplett unabhängig von den Scripten aus dem git-Repository eine eigene Konfiguration speichern kann, zudem weder im Mastodon-Web UI die App registrieren muss, noch User/Password lokal speichern muss, um in Zukunft die Scripte automatisiert ausführen zu können, kommt hier ein kleines Helper Modul, dass mit einem Methoden-Aufruf alles erledigt.</p>
<p dir="auto">Die wichtigste Funktion in dem Helper ist <code>open_or_create_app(...)</code>. Das ist ein Multitalent und ist in der Lage, die Scripte als App zu registrieren, sich entweder nur als Client-App oder User-Account anzumelden und dann die Registrierungs-Daten und den Zugriffstoken des Accounts lokal zu speichern, damit beim weiteren Aufruf weder die App erneut registriert wird noch man die Login-Daten wieder eingeben muss.</p>
<p dir="auto">Da das kaum in Worte zu fassen ist, ein Aktivitätsdiagramm (erstellt mit <plantuml.org>):</p>
<p dir="auto"><img src="https://codeberg.org/beandev/mastodon-playground/raw/branch/main/documentation/mastobot/MastodonHelper_open_or_create_app.png" alt="Activity"></p>
<p dir="auto">Man kann die Funktion in drei große Abschnitte aufteilen:</p>
<h2 dir="auto">Prepare Configuration</h2>
<p dir="auto">Der Teil der Methode übernimmt entweder die notwendigen Parameter von dem Funktions-Aufruf oder liest diese aus einer Config-Datei. Die Config-Datei wird erst im Home-Ordner gesucht, dann im Script-Ordner. Man kann auch einen absoluten Pfad angeben. Legt man aber in dem eigenen Home-Ordner die Datei `mastobot-config.yaml’ an, kann man die Beispielscripte ohne Modifikation sofort ausführen (gesetzt den Fall, man hat die Konfiguration angepasst).</p>
<p dir="auto">Alle Konfigurationsparameter werden auch grob geprüft und man bekommt ggf. Fehler um die Ohren gehauen.</p>
<h2 dir="auto">Register Application</h2>
<p dir="auto">Der Abschnitt sucht erstmal, ob wir unter dem konfigurierten Application-Name schon Client-App-Daten in eine <code>.secret</code> Datei gespeichert haben. Wenn nicht, wird eine App unter dem Namen und der gewünschten Default-Instanz registriert (siehe `mastobot-config.yaml’).</p>
<p dir="auto">Mit der erfolgreichen Registrierung, werden die <code>Client-ID</code> und das <code>Secret</code> in eine Datei gespeichert und, wie oben beschrieben, in Zukunft immer wieder verwendet. Das verhindert, dass eine App immer neu registriert wird (was Podmins der Mastodon-Instanzen zu schätzen wissen).</p>
<h2 dir="auto">Connect to Mastodon</h2>
<p dir="auto">Der dritte Abschnitt in der Methode ist das Kernstück.</p>
<p dir="auto">Erst wird geschaut, ob ein Account-Login gewünscht wird und der Access-Token des Users schon mal gespeichert wurde (das ist der linke Pfad im Aktivitätsdiagramm). Wenn ja, werden die Daten aus dem HOME-Ordner geladen und Mastodon.py meldet sich mit den Daten an.</p>
<p dir="auto">Der längere Weg ist im rechten Pfad beschrieben. Zunächst sagen wir Mastodon.py welche Client-ID und Secret genommen wird (das haben wir früher mal, oben bei <em>Register Application</em>, gespeichert). Danach prüfen wir, ob wir einen User-Account-Login wollten (über den Parameter <code>login = True</code>). Wenn ja, dann fragen wir den Benutzernamen und Password im Terminal ab (Yeah, ein UI!), loggen uns damit bei unserer Mastodon-Instanz ein, bekommen dann einen Access-Token und speichern diesen auch im Home-Ordner ab. Puh. Warum? Damit wir bei allen zukünftigen Aufrufen nicht mehr ein interaktives Login haben!</p>
<p dir="auto">Anders gesagt: Wir müssen uns nur ein einziges Mal anmelden und danach laufen alle Scripte automatisiert, ohne Interaktion, durch.</p>
<p dir="auto">Schaut euch auf Codeberg das Modul mal an: <a href="https://codeberg.org/beandev/mastodon-playground/src/commit/6797cf2bdf45018cf60219f4b69f1de4fb7fa542/mastobot/MastodonHelper.py" rel="noopener noreferrer">MastodonHelper.py</a>.</p>
<p dir="auto">Da sind noch ein paar weitere Hilfsmethoden drin. Die sollen aber das alles nur etwas strukturieren.</p>
<h1 dir="auto">me.py</h1>
<p dir="auto">Wie wird das nun verwendet? Dazu habe ich ein winziges Script geschrieben: <a href="https://codeberg.org/beandev/mastodon-playground/src/branch/main/me.py" rel="noopener noreferrer"><code>me.py</code></a>:</p>
<pre><code dir="auto"><span class="source"><span class=""><span class="keyword control">import</span> <span class=""><span class="">json</span></span></span>
<span class=""><span class="keyword control">import</span> <span class=""><span class="">mastobot</span><span class="punctuation">.</span><span class="">MastodonHelper</span></span></span>
<span class=""><span class="">mastodon</span></span> <span class="keyword operator">=</span> <span class=""><span class=""><span class="">mastobot</span><span class="punctuation">.</span><span class="">MastodonHelper</span><span class="punctuation">.</span></span><span class=""><span class="variable function">open_or_create_app</span></span><span class="punctuation">(</span><span class="">
<span class="variable parameter">config_yaml</span><span class="keyword operator">=</span><span class="string"><span class="string"><span class="punctuation string">'</span></span></span><span class="string"><span class="string">mastobot-config.yaml<span class="punctuation string">'</span></span></span><span class="punctuation">,</span>
<span class="variable parameter">login</span><span class="keyword operator">=</span><span class="constant language">True</span>
</span><span class="punctuation">)</span></span>
<span class="comment"><span class="punctuation comment">#</span> Request my account data:
</span><span class=""><span class="">me</span></span> <span class="keyword operator">=</span> <span class=""><span class=""><span class="">mastodon</span><span class="punctuation">.</span></span><span class=""><span class="variable function">me</span></span><span class="punctuation">(</span><span class="punctuation">)</span></span>
<span class="comment"><span class="punctuation comment">#</span> Print out basic stuff:
</span><span class=""><span class=""><span class="support function">print</span></span><span class="punctuation">(</span><span class=""><span class="string"><span class="string"><span class="punctuation string">"</span></span></span><span class="string"><span class="string"><span class="constant">\n</span><span class="constant">\n</span><span class="punctuation string">"</span></span></span></span><span class="punctuation">)</span></span>
<span class=""><span class=""><span class="support function">print</span></span><span class="punctuation">(</span><span class=""><span class="storage type string">f</span><span class="string"><span class="string"><span class="punctuation string">"</span></span></span><span class="string"><span class="string">Me: </span><span class=""><span class="punctuation">{</span><span class="source"><span class=""><span class=""><span class="">me</span></span></span><span class=""><span class="punctuation">[</span></span><span class=""><span class="string"><span class="string"><span class="punctuation string">'</span></span></span><span class="string"><span class="string">id<span class="punctuation string">'</span></span></span></span><span class=""><span class="punctuation">]</span></span></span></span><span class=""><span class="punctuation">}</span></span><span class="string"> = </span><span class=""><span class="punctuation">{</span><span class="source"><span class=""><span class=""><span class="">me</span></span></span><span class=""><span class="punctuation">[</span></span><span class=""><span class="string"><span class="string"><span class="punctuation string">'</span></span></span><span class="string"><span class="string">username<span class="punctuation string">'</span></span></span></span><span class=""><span class="punctuation">]</span></span></span></span><span class=""><span class="punctuation">}</span></span><span class="string"><span class="punctuation string">"</span></span></span></span><span class="punctuation">)</span></span>
<span class="comment"><span class="punctuation comment">#</span> Print out everything:
</span><span class=""><span class=""><span class="support function">print</span></span><span class="punctuation">(</span><span class=""><span class=""><span class=""><span class="">json</span><span class="punctuation">.</span></span><span class=""><span class="variable function">dumps</span></span><span class="punctuation">(</span><span class=""><span class=""><span class="">me</span></span><span class="punctuation">,</span> <span class="variable parameter">indent</span><span class="keyword operator">=</span><span class="constant numeric">4</span><span class="punctuation">,</span> <span class="variable parameter">default</span><span class="keyword operator">=</span><span class=""><span class="support type">str</span></span></span><span class="punctuation">)</span></span></span><span class="punctuation">)</span></span>
</span></code></pre>
<p dir="auto">Der obige Aufruf von <code>open_or_create_app</code> wird, wenn ihr nichts an der <code>mastobot-config.yaml</code> geändert habt, nur Fehler werfen.</p>
<p dir="auto">Also kopiert die <a href="https://codeberg.org/beandev/mastodon-playground/src/branch/main/mastobot-config.yaml" rel="noopener noreferrer">mastobot-config.yam</a> Datei in euren Home-Ordner.</p>
<p dir="auto">Tragt bei <code>name: ~</code> statt der <code>~</code> einen Namen ein, der eure App eindeutig identifiziert, zum Beispiel:</p>
<pre><code dir="auto"><span class="source"><span class="string"><span class="entity name tag">application</span></span><span class="punctuation">:</span>
<span class="string"><span class="entity name tag">name</span></span><span class="punctuation">:</span> <span class="string"><span class="entity name tag">HorstsMastodonApp</span></span>
<span class="string"><span class="entity name tag">debug</span></span><span class="punctuation">:</span> <span class="constant language">false</span> <span class="comment"><span class="punctuation comment">#</span> debug output
</span></span></code></pre>
<p dir="auto">Im Abschnitt der <code>instances</code> tragt ihr eure Mastodon-Instanz ein, wo ihr einen Account habt:</p>
<pre><code dir="auto"><span class="source"><span class="string"><span class="entity name tag">instances</span></span><span class="punctuation">:</span>
<span class="string"><span class="entity name tag">default</span></span><span class="punctuation">:</span>
<span class="string"><span class="entity name tag">url</span></span><span class="punctuation">:</span> <span class="string">https://social.example.com</span> <span class="comment"><span class="punctuation comment">#</span> your instance URL
</span></span></code></pre>
<p dir="auto">Das muss eine vollständige URL sein (und natürlich <em>nicht</em> https://social.example.com).</p>
<p dir="auto">Jetzt ruft das me.py Script auf.</p>
<p dir="auto">Ihr werdet einen Login bekommen, wo ihr eure E-Mail-Adresse und das Password eingeben müsst. Keine Angst, die Daten werden lokal nicht gespeichert. Klappt das Login damit, wird im Home-Ordner eine <code>.usertoken</code> Datei angelegt, wo nur eine ID drinsteht. Diese ID sollte <strong>niemals</strong> in andere Hände gelangen. Das ist ein Zusatzschlüssel, mit dem ihr euch einloggen könnt, ohne Benutzername und Kennwort angeben zu müssen. Das ist sozusagen eine “Hotel-RFID”-Karte zu eurem Zimmer im Mastodon-Instanz-Hotel.</p>
<p dir="auto">Wenn das alles geklappt hat, bekommt ihr ein großes JSON mit euren Account-Informationen.</p>
<p dir="auto">Ruft ihr das Script <code>me.py</code> nochmal auf, läuft es einfach durch und das JSON wird wieder angezeigt. Keine Registrierung, kein Login. </p>
<p dir="auto">Das funktioniert jetzt mit jedem Script auf eurem Rechner, dass den <code>MastodonHelper</code> mit der <code>open_or_create_app</code> Funktion und den gespeicherten Konfigurationsdaten nutzt.</p>
<p dir="auto">Also jedes Script mit dem Aufruf:</p>
<pre><code dir="auto"><span class="source"><span class=""><span class="keyword control">import</span> <span class=""><span class="">mastobot</span><span class="punctuation">.</span><span class="">MastodonHelper</span></span></span>
<span class=""><span class="">mastodon</span></span> <span class="keyword operator">=</span> <span class=""><span class=""><span class="">mastobot</span><span class="punctuation">.</span><span class="">MastodonHelper</span><span class="punctuation">.</span></span><span class=""><span class="variable function">open_or_create_app</span></span><span class="punctuation">(</span><span class="">
<span class="variable parameter">config_yaml</span><span class="keyword operator">=</span><span class="string"><span class="string"><span class="punctuation string">'</span></span></span><span class="string"><span class="string">mastobot-config.yaml<span class="punctuation string">'</span></span></span><span class="punctuation">,</span>
<span class="variable parameter">login</span><span class="keyword operator">=</span><span class="constant language">True</span>
</span><span class="punctuation">)</span></span>
</span></code></pre>
<p dir="auto">Loggt sich völlig automatisiert ein. </p>
<p dir="auto">Mit dieser Vorbereitung, werden alle weiteren Scripte, die ins git-Repository hochgeladen werden, sehr simpel im Prolog sein. </p>
<p dir="auto">Viel Spaß damit!</p>
<p dir="auto">Wie immer: Wenn ihr in eurer Bubble Menschen vermutet, die sich für solche Artikel interessieren, dann boosted! Danke :-)</p>
]]><![CDATA[Wenn sie wieder weg sind: Un-Following tracken]]>https://write.tchncs.de/~/BeanDevMastodon/Wenn%20sie%20wieder%20weg%20sind:%20Un-Following%20tracken/2022-05-22T17:40:47.741424+00:00Aljoscha Rittnerhttps://write.tchncs.de/@/beandev/2022-05-22T17:40:47.741424+00:00<![CDATA[<h1 dir="auto">Einleitung</h1>
<p dir="auto">Im <a href="https://write.tchncs.de/%7E/BeanDevMastodon/Mastodon%20Followers%20und%20Following%20importieren.%20CircleCount%20f%C3%BCr%20Arme" rel="noopener noreferrer">letzten Artikel</a> behandelte ich das Synchronisieren von Accounts, denen ich folge und die mir folgen. Allerdings gibt es mit der Implementation das Problem, dass die Accounts nur hinzugefügt oder aktualisiert werden.</p>
<p dir="auto">Den Fall, dass wir jemanden nicht mehr folgen oder wir selbst entfolgt werden, wird mit dem letzten Script nicht erkannt.</p>
<p dir="auto">Aus psychologischer Sicht kann man sich die Frage stellen: Will ich das überhaupt wissen? Viele Social-Media-Plattformen halten sich bei dieser Frage auch bedeckt. Man wird informiert, dass man jemanden hat, der sich für die eigenen Beiträge interessiert, aber das Entfolgen passiert still. Auch Mastodon, was als Medium versucht möglichst positive Vibes zu vermitteln, verfährt so. </p>
<p dir="auto">In der Synchronisation der Accounts spiegelt sich das auch wider, aber auf einer reinen technischen Ebene. Wie erkennt man aus einer REST Schnittstelle, die darauf basiert nur existierende Daten zu lieferen, welche eben nicht mehr da sind?</p>
<p dir="auto">Mastodon hat eine Notification-API, um über Veränderung informiert zu werden. Aber die ist UI-getrieben (und dort will man Un-Follows nicht kommunizieren). Das fällt also flach. Man könnte schauen, ob sich die Anzahl verändert hat (die bekommt man aus den Account-Daten als Statistik mit). Aber das ist ungenau, denn es könnte ein Follow und Un-Follow geben und damit hat sich die absolute Anzahl nicht geändert. Bleibt also nur Brute-Force. Es müssen alle Accounts verglichen werden. D.h. man schaut, welche Account-IDs von der Mastodon-Instanz geliefert werden und vergleicht sie mit den IDs in OpenSearch. Da gibt es dann eine Differenz, die man dann als Un-Follow interpretieren kann (wenn in OpenSearch die ID existiert, aber Mastodon diese nicht mehr listet).</p>
<p dir="auto">Diese Full-Diff Vergleiche bieten eine sehr stabile Aussage über Bewegungen zwischen zwei Zeitpunkten der Abfragen, allerdings nicht, wann es genau passiert ist. Damit können wir leben. Man muss nur im Fehlerfall aufpassen, dass eine leere Antwort der Mastodon-Instanz nicht als kompletter Un-Follow aller Accounts interpretiert wird.</p>
<p dir="auto">Deswegen lösche ich auch ungern bei solchen Abgleichen Daten, die vermeintlich auf einer Remote-Instanz nicht mehr existieren. Das kann dann auf einmal zu kompletten lokalen Datenverlust führen.</p>
<h1 dir="auto">Mehr Indexe</h1>
<p dir="auto">Da ich die <code>followers</code> und <code>following</code> als aktuellen Zustand der letzten Abfrage beibehalten möchte, verschiebe ich einfach die Accounts, die meine Mastodon-Instanz nicht mehr als Verbindung listet. Dafür brauche ich zwei weitere Indexe: <code>un-followers</code> und <code>un-following</code>. Das Tolle ist, dass das <a href="https://write.tchncs.de/%7E/BeanDevOpenSearch/Index%20mit%20IndexTemplate%20erzeugen" rel="noopener noreferrer">Script zum Anlegen der Account-Indexe</a> schon so generisch ist. Da muss ich nur die zwei Namen der neuen Indexe hinzufügen:</p>
<pre><code dir="auto"><span class="source"><span class="constant language">...</span>
<span class="comment"><span class="punctuation comment">#</span> Namen der Indexe
</span><span class=""><span class="">index_names</span></span> <span class="keyword operator">=</span> <span class=""><span class="punctuation">[</span><span class="string"><span class="string"><span class="punctuation string">'</span></span></span><span class="string"><span class="string">followers<span class="punctuation string">'</span></span></span><span class="punctuation">,</span> <span class="string"><span class="string"><span class="punctuation string">'</span></span></span><span class="string"><span class="string">following<span class="punctuation string">'</span></span></span><span class="punctuation">,</span> <span class="string"><span class="string"><span class="punctuation string">'</span></span></span><span class="string"><span class="string">un-followers<span class="punctuation string">'</span></span></span><span class="punctuation">,</span> <span class="string"><span class="string"><span class="punctuation string">'</span></span></span><span class="string"><span class="string">un-following<span class="punctuation string">'</span></span></span><span class="punctuation">]</span></span>
<span class="punctuation">.</span><span class="punctuation">.</span><span class="punctuation">.</span>
</span></code></pre>
<p dir="auto">Und ja, ich wusste, dass ich das mal machen werde, deswegen hat die Schleife schon den Test eingebaut:</p>
<pre><code dir="auto"><span class="source"><span class="constant language">...</span>
<span class=""><span class="keyword control">for</span> <span class="">index_name</span> <span class=""><span class="keyword control">in</span></span></span><span class=""> <span class=""><span class="">index_names</span></span><span class="punctuation">:</span></span>
<span class=""><span class="keyword control">if</span> <span class="keyword operator">not</span> <span class=""><span class=""><span class="">client</span><span class="punctuation">.</span><span class="">indices</span><span class="punctuation">.</span></span><span class=""><span class="variable function">exists</span></span><span class="punctuation">(</span><span class=""><span class=""><span class="">index_name</span></span></span><span class="punctuation">)</span></span><span class="punctuation">:</span></span>
<span class="constant language">...</span>
</span></code></pre>
<p dir="auto">Wenn man das Script <code>osCreateFollowersIndexes.py</code>, mit den zwei zusätzlichen Index-Namen wieder aufruft, werden nur die beiden (noch nicht vorhandenen) Indexe angelegt.</p>
<p dir="auto">Nun haben wir vier Indexe, die Accounts aufnehmen können. </p>
<h1 dir="auto">Differenzen</h1>
<p dir="auto">Nein, persönliche Differenzen gab es bei den Un-Follows hoffentlich nicht, aber wir müssen die irgendwie ermittlen.</p>
<p dir="auto">Erinnern wir uns an die Methode, die unsere Accounts lädt:</p>
<pre><code dir="auto"><span class="source"><span class="function"><span class="storage type function">def</span> <span class="entity name function"><span class="">load_to_index</span></span></span><span class="function"><span class="punctuation">(</span></span><span class="function"><span class="variable parameter">client</span><span class="punctuation">,</span> <span class="variable parameter">index_name</span><span class="punctuation">,</span> <span class="variable parameter">initial_load</span><span class="punctuation">,</span> <span class="variable parameter">next_load</span><span class="punctuation">)</span></span><span class="function"><span class="punctuation function">:</span></span>
<span class=""><span class=""><span class="support function">print</span></span><span class="punctuation">(</span><span class=""><span class="storage type string">f</span><span class="string"><span class="string"><span class="punctuation string">'</span></span></span><span class="string"><span class="string">Import </span><span class=""><span class="punctuation">{</span><span class="source"><span class=""><span class="">index_name</span></span></span></span><span class=""><span class="punctuation">}</span></span><span class="string"><span class="punctuation string">'</span></span></span></span><span class="punctuation">)</span></span>
<span class=""><span class="">page</span></span> <span class="keyword operator">=</span> <span class="constant language">None</span>
<span class=""><span class="">created</span></span> <span class="keyword operator">=</span> <span class="constant numeric">0</span>
<span class=""><span class="">updated</span></span> <span class="keyword operator">=</span> <span class="constant numeric">0</span>
<span class=""><span class="keyword control">while</span> <span class="constant language">True</span><span class="punctuation">:</span></span>
<span class=""><span class="">page</span></span> <span class="keyword operator">=</span> <span class=""><span class=""><span class="variable function">initial_load</span></span><span class="punctuation">(</span><span class=""><span class="variable parameter">id</span><span class="keyword operator">=</span><span class=""><span class=""><span class="">me</span></span></span><span class=""><span class="punctuation">[</span></span><span class=""><span class="string"><span class="string"><span class="punctuation string">'</span></span></span><span class="string"><span class="string">id<span class="punctuation string">'</span></span></span></span><span class=""><span class="punctuation">]</span></span><span class="punctuation">,</span> <span class="variable parameter">limit</span><span class="keyword operator">=</span><span class="constant numeric">80</span></span><span class="punctuation">)</span></span> <span class=""><span class="keyword control">if</span> <span class=""><span class="">page</span></span> <span class="keyword operator">is</span> <span class="constant language">None</span> <span class="keyword control">else</span> <span class=""><span class=""><span class="variable function">next_load</span></span><span class="punctuation">(</span><span class=""><span class=""><span class="">page</span></span></span><span class="punctuation">)</span></span></span>
<span class=""><span class="keyword control">if</span> <span class=""><span class="">page</span></span><span class="punctuation">:</span></span>
<span class=""><span class="keyword control">for</span> <span class="">account</span> <span class=""><span class="keyword control">in</span></span></span><span class=""> <span class=""><span class="">page</span></span><span class="punctuation">:</span></span>
<span class=""><span class="">response</span></span> <span class="keyword operator">=</span> <span class=""><span class=""><span class="">client</span><span class="punctuation">.</span></span><span class=""><span class="variable function">index</span></span><span class="punctuation">(</span><span class=""><span class=""><span class="">index_name</span></span><span class="punctuation">,</span> <span class="variable parameter">id</span><span class="keyword operator">=</span><span class=""><span class=""><span class="">account</span></span></span><span class=""><span class="punctuation">[</span></span><span class=""><span class="string"><span class="string"><span class="punctuation string">'</span></span></span><span class="string"><span class="string">id<span class="punctuation string">'</span></span></span></span><span class=""><span class="punctuation">]</span></span><span class="punctuation">,</span> <span class="variable parameter">body</span><span class="keyword operator">=</span><span class=""><span class="">account</span></span></span><span class="punctuation">)</span></span>
<span class=""><span class="keyword control">if</span> <span class=""><span class=""><span class="">response</span></span></span><span class=""><span class="punctuation">[</span></span><span class=""><span class="string"><span class="string"><span class="punctuation string">'</span></span></span><span class="string"><span class="string">result<span class="punctuation string">'</span></span></span></span><span class=""><span class="punctuation">]</span></span> <span class="keyword operator">==</span> <span class="string"><span class="string"><span class="punctuation string">"</span></span></span><span class="string"><span class="string">created<span class="punctuation string">"</span></span></span><span class="punctuation">:</span></span>
<span class=""><span class=""><span class="support function">print</span></span><span class="punctuation">(</span><span class=""><span class="storage type string">f</span><span class="string"><span class="string"><span class="punctuation string">"</span></span></span><span class="string"><span class="string"> New in </span><span class=""><span class="punctuation">{</span><span class="source"><span class=""><span class="">index_name</span></span></span></span><span class=""><span class="punctuation">}</span></span><span class="string">: </span><span class=""><span class="punctuation">{</span><span class="source"><span class=""><span class=""><span class="">account</span></span></span><span class=""><span class="punctuation">[</span></span><span class=""><span class="string"><span class="string"><span class="punctuation string">'</span></span></span><span class="string"><span class="string">acct<span class="punctuation string">'</span></span></span></span><span class=""><span class="punctuation">]</span></span></span></span><span class=""><span class="punctuation">}</span></span><span class="string"><span class="punctuation string">"</span></span></span></span><span class="punctuation">)</span></span>
<span class=""><span class="">created</span></span> <span class="keyword operator">+=</span> <span class="constant numeric">1</span>
<span class=""><span class="keyword control">elif</span></span> <span class=""><span class=""><span class="">response</span></span></span><span class=""><span class="punctuation">[</span></span><span class=""><span class="string"><span class="string"><span class="punctuation string">'</span></span></span><span class="string"><span class="string">result<span class="punctuation string">'</span></span></span></span><span class=""><span class="punctuation">]</span></span> <span class="keyword operator">==</span> <span class="string"><span class="string"><span class="punctuation string">"</span></span></span><span class="string"><span class="string">updated<span class="punctuation string">"</span></span></span><span class="punctuation variable">:</span>
<span class=""><span class="">updated</span></span> <span class="keyword operator">+=</span> <span class="constant numeric">1</span>
<span class=""><span class="keyword control">else</span><span class="punctuation">:</span></span>
<span class="keyword control">break</span>
<span class=""><span class=""><span class="">client</span><span class="punctuation">.</span><span class="">indices</span><span class="punctuation">.</span></span><span class=""><span class="variable function">refresh</span></span><span class="punctuation">(</span><span class=""><span class="variable parameter">index</span><span class="keyword operator">=</span><span class=""><span class="">index_name</span></span></span><span class="punctuation">)</span></span>
<span class=""><span class=""><span class="support function">print</span></span><span class="punctuation">(</span><span class=""><span class="storage type string">f</span><span class="string"><span class="string"><span class="punctuation string">'</span></span></span><span class="string"><span class="string">Finished "</span><span class=""><span class="punctuation">{</span><span class="source"><span class=""><span class="">index_name</span></span></span></span><span class=""><span class="punctuation">}</span></span><span class="string">". Created </span><span class=""><span class="punctuation">{</span><span class="source"><span class=""><span class="">created</span></span></span></span><span class=""><span class="punctuation">}</span></span><span class="string"> accounts and updated </span><span class=""><span class="punctuation">{</span><span class="source"><span class=""><span class="">updated</span></span></span></span><span class=""><span class="punctuation">}</span></span><span class="string"><span class="punctuation string">'</span></span></span></span><span class="punctuation">)</span></span>
</span></code></pre>
<p dir="auto">Dort müssen wir uns jetzt alle IDs merken, die wir von Mastodon erhalten. Ich mache es mir einfach und schreibe die alle in eine Liste (Set wäre besser und schneller). Da ich wohl nie Millionen von Followern haben werde, sehe ich keine Speicherprobleme alle IDs auf diese Weise zu merken.</p>
<p dir="auto">Die neue Variante:</p>
<pre><code dir="auto"><span class="source"><span class="function"><span class="storage type function">def</span> <span class="entity name function"><span class="">load_to_index</span></span></span><span class="function"><span class="punctuation">(</span></span><span class="function"><span class="variable parameter">client</span><span class="punctuation">,</span> <span class="variable parameter">index_name</span><span class="punctuation">,</span> <span class="variable parameter">initial_load</span><span class="punctuation">,</span> <span class="variable parameter">next_load</span><span class="punctuation">)</span></span><span class="function"><span class="punctuation function">:</span></span>
<span class=""><span class=""><span class="support function">print</span></span><span class="punctuation">(</span><span class=""><span class="storage type string">f</span><span class="string"><span class="string"><span class="punctuation string">'</span></span></span><span class="string"><span class="string">Import </span><span class=""><span class="punctuation">{</span><span class="source"><span class=""><span class="">index_name</span></span></span></span><span class=""><span class="punctuation">}</span></span><span class="string"><span class="punctuation string">'</span></span></span></span><span class="punctuation">)</span></span>
<span class=""><span class="">page</span></span> <span class="keyword operator">=</span> <span class="constant language">None</span>
<span class=""><span class="">created</span></span> <span class="keyword operator">=</span> <span class="constant numeric">0</span>
<span class=""><span class="">updated</span></span> <span class="keyword operator">=</span> <span class="constant numeric">0</span>
<span class=""><span class="">extracted</span></span> <span class="keyword operator">=</span> <span class=""><span class="punctuation">[</span><span class="punctuation">]</span></span>
<span class=""><span class="keyword control">while</span> <span class="constant language">True</span><span class="punctuation">:</span></span>
<span class=""><span class="">page</span></span> <span class="keyword operator">=</span> <span class=""><span class=""><span class="variable function">initial_load</span></span><span class="punctuation">(</span><span class=""><span class="variable parameter">id</span><span class="keyword operator">=</span><span class=""><span class=""><span class="">me</span></span></span><span class=""><span class="punctuation">[</span></span><span class=""><span class="string"><span class="string"><span class="punctuation string">'</span></span></span><span class="string"><span class="string">id<span class="punctuation string">'</span></span></span></span><span class=""><span class="punctuation">]</span></span><span class="punctuation">,</span> <span class="variable parameter">limit</span><span class="keyword operator">=</span><span class="constant numeric">80</span></span><span class="punctuation">)</span></span> <span class=""><span class="keyword control">if</span> <span class=""><span class="">page</span></span> <span class="keyword operator">is</span> <span class="constant language">None</span> <span class="keyword control">else</span> <span class=""><span class=""><span class="variable function">next_load</span></span><span class="punctuation">(</span><span class=""><span class=""><span class="">page</span></span></span><span class="punctuation">)</span></span></span>
<span class=""><span class="keyword control">if</span> <span class=""><span class="">page</span></span><span class="punctuation">:</span></span>
<span class=""><span class="keyword control">for</span> <span class="">account</span> <span class=""><span class="keyword control">in</span></span></span><span class=""> <span class=""><span class="">page</span></span><span class="punctuation">:</span></span>
<span class=""><span class="support function">id</span></span> <span class="keyword operator">=</span> <span class=""><span class=""><span class="">account</span></span></span><span class=""><span class="punctuation">[</span></span><span class=""><span class="string"><span class="string"><span class="punctuation string">'</span></span></span><span class="string"><span class="string">id<span class="punctuation string">'</span></span></span></span><span class=""><span class="punctuation">]</span></span>
<span class=""><span class=""><span class="">extracted</span><span class="punctuation">.</span></span><span class=""><span class="variable function">append</span></span> <span class="punctuation">(</span><span class=""><span class=""><span class="support function">id</span></span></span><span class="punctuation">)</span></span>
<span class=""><span class="">response</span></span> <span class="keyword operator">=</span> <span class=""><span class=""><span class="">client</span><span class="punctuation">.</span></span><span class=""><span class="variable function">index</span></span><span class="punctuation">(</span><span class=""><span class=""><span class="">index_name</span></span><span class="punctuation">,</span> <span class="variable parameter">id</span><span class="keyword operator">=</span><span class=""><span class="support function">id</span></span><span class="punctuation">,</span> <span class="variable parameter">body</span><span class="keyword operator">=</span><span class=""><span class="">account</span></span></span><span class="punctuation">)</span></span>
<span class=""><span class="keyword control">if</span> <span class=""><span class=""><span class="">response</span></span></span><span class=""><span class="punctuation">[</span></span><span class=""><span class="string"><span class="string"><span class="punctuation string">'</span></span></span><span class="string"><span class="string">result<span class="punctuation string">'</span></span></span></span><span class=""><span class="punctuation">]</span></span> <span class="keyword operator">==</span> <span class="string"><span class="string"><span class="punctuation string">"</span></span></span><span class="string"><span class="string">created<span class="punctuation string">"</span></span></span><span class="punctuation">:</span></span>
<span class=""><span class=""><span class="support function">print</span></span><span class="punctuation">(</span><span class=""><span class="storage type string">f</span><span class="string"><span class="string"><span class="punctuation string">"</span></span></span><span class="string"><span class="string"> New in </span><span class=""><span class="punctuation">{</span><span class="source"><span class=""><span class="">index_name</span></span></span></span><span class=""><span class="punctuation">}</span></span><span class="string">: </span><span class=""><span class="punctuation">{</span><span class="source"><span class=""><span class=""><span class="">account</span></span></span><span class=""><span class="punctuation">[</span></span><span class=""><span class="string"><span class="string"><span class="punctuation string">'</span></span></span><span class="string"><span class="string">acct<span class="punctuation string">'</span></span></span></span><span class=""><span class="punctuation">]</span></span></span></span><span class=""><span class="punctuation">}</span></span><span class="string"><span class="punctuation string">"</span></span></span></span><span class="punctuation">)</span></span>
<span class=""><span class="">created</span></span> <span class="keyword operator">+=</span> <span class="constant numeric">1</span>
<span class=""><span class="keyword control">elif</span></span> <span class=""><span class=""><span class="">response</span></span></span><span class=""><span class="punctuation">[</span></span><span class=""><span class="string"><span class="string"><span class="punctuation string">'</span></span></span><span class="string"><span class="string">result<span class="punctuation string">'</span></span></span></span><span class=""><span class="punctuation">]</span></span> <span class="keyword operator">==</span> <span class="string"><span class="string"><span class="punctuation string">"</span></span></span><span class="string"><span class="string">updated<span class="punctuation string">"</span></span></span><span class="punctuation variable">:</span>
<span class=""><span class="">updated</span></span> <span class="keyword operator">+=</span> <span class="constant numeric">1</span>
<span class=""><span class="keyword control">else</span><span class="punctuation">:</span></span>
<span class="keyword control">break</span>
<span class=""><span class=""><span class="">client</span><span class="punctuation">.</span><span class="">indices</span><span class="punctuation">.</span></span><span class=""><span class="variable function">refresh</span></span><span class="punctuation">(</span><span class=""><span class="variable parameter">index</span><span class="keyword operator">=</span><span class=""><span class="">index_name</span></span></span><span class="punctuation">)</span></span>
<span class=""><span class=""><span class="support function">print</span></span><span class="punctuation">(</span><span class=""><span class="storage type string">f</span><span class="string"><span class="string"><span class="punctuation string">'</span></span></span><span class="string"><span class="string">Finished "</span><span class=""><span class="punctuation">{</span><span class="source"><span class=""><span class="">index_name</span></span></span></span><span class=""><span class="punctuation">}</span></span><span class="string">". Created </span><span class=""><span class="punctuation">{</span><span class="source"><span class=""><span class="">created</span></span></span></span><span class=""><span class="punctuation">}</span></span><span class="string"> accounts and updated </span><span class=""><span class="punctuation">{</span><span class="source"><span class=""><span class="">updated</span></span></span></span><span class=""><span class="punctuation">}</span></span><span class="string"><span class="punctuation string">'</span></span></span></span><span class="punctuation">)</span></span>
</span></code></pre>
<p dir="auto">Ich bin kein Fan davon, Funktionen unnötig aufzublähen. Deswegen gibt es einen Aufruf zu einer neuen Methode, die sich dann um diese Liste der IDs auf den spezifischen Index (<code>followers</code> oder <code>following</code>) kümmert.</p>
<p dir="auto">Also noch diese Zeilen anfügen:</p>
<pre><code dir="auto"><span class="source"> <span class="comment"><span class="punctuation comment">#</span> Now we have in extracted the current active existing accounts assigned to the Mastodon-Account
</span> <span class="comment"><span class="punctuation comment">#</span> In the OS index we have maybe more. We must move the account to un-{index_name}
</span> <span class=""><span class=""><span class="variable function">move_unlisted</span></span><span class="punctuation">(</span><span class=""><span class=""><span class="">client</span></span><span class="punctuation">,</span> <span class=""><span class="">index_name</span></span><span class="punctuation">,</span> <span class=""><span class="">extracted</span></span></span><span class="punctuation">)</span></span>
</span></code></pre>
<h2 dir="auto">Verschiebebahnhof</h2>
<p dir="auto"><code>move_unlisted</code> gibt es noch nicht, also gleich mal anlegen. Da kommt auch sofort die Suche nach den Dokumenten (Accounts) rein. Das hatten wir so ähnlich schon mal in älteren Artikeln ausprobiert, allerdings machen wir es jetzt mit der Scroll API wie in dem neuen <a href="https://write.tchncs.de/%7E/BeanDevOpenSearch/Scrolled%20queries%20und%20Point-In-Time%20queries%20mit%20OpenSearch" rel="noopener noreferrer">Blog-Artikel für Scroll und PIT Abfragen</a> beschrieben:</p>
<pre><code dir="auto"><span class="source"><span class="function"><span class="storage type function">def</span> <span class="entity name function"><span class="">move_unlisted</span></span></span><span class="function"><span class="punctuation">(</span></span><span class="function"><span class="variable parameter">client</span><span class="punctuation">,</span> <span class="variable parameter">index_name</span><span class="punctuation">,</span> <span class="variable parameter">extracted</span><span class="punctuation">)</span></span><span class="function"><span class="punctuation function">:</span></span>
<span class=""><span class="">docs_listed</span></span><span class="keyword operator">=</span><span class="constant numeric">0</span>
<span class=""><span class="">docs_moved</span></span><span class="keyword operator">=</span><span class="constant numeric">0</span>
<span class=""><span class="">search_all</span></span><span class="keyword operator">=</span><span class=""><span class="punctuation">{</span>
<span class="string"><span class="string"><span class="punctuation string">"</span></span></span><span class="string"><span class="string">_source<span class="punctuation string">"</span></span></span><span class="punctuation">:</span> <span class="constant language">False</span><span class="punctuation">,</span>
<span class="string"><span class="string"><span class="punctuation string">"</span></span></span><span class="string"><span class="string">size<span class="punctuation string">"</span></span></span><span class="punctuation">:</span> <span class="constant numeric">100</span><span class="punctuation">,</span>
<span class="string"><span class="string"><span class="punctuation string">"</span></span></span><span class="string"><span class="string">query<span class="punctuation string">"</span></span></span><span class="punctuation">:</span> <span class=""><span class="punctuation">{</span>
<span class="string"><span class="string"><span class="punctuation string">"</span></span></span><span class="string"><span class="string">match_all<span class="punctuation string">"</span></span></span><span class="punctuation">:</span> <span class=""><span class="punctuation">{</span><span class=""><span class="punctuation">}</span></span></span>
<span class="punctuation">}</span></span>
<span class="punctuation">}</span></span>
<span class=""><span class="">resp</span></span><span class="keyword operator">=</span><span class=""><span class=""><span class="">client</span><span class="punctuation">.</span></span><span class=""><span class="variable function">search</span></span><span class="punctuation">(</span><span class=""><span class="variable parameter">index</span><span class="keyword operator">=</span><span class=""><span class="">index_name</span></span><span class="punctuation">,</span> <span class="variable parameter">body</span><span class="keyword operator">=</span><span class=""><span class="">search_all</span></span><span class="punctuation">,</span> <span class="variable parameter">scroll</span><span class="keyword operator">=</span><span class="string"><span class="string"><span class="punctuation string">'</span></span></span><span class="string"><span class="string">5s<span class="punctuation string">'</span></span></span></span><span class="punctuation">)</span></span>
<span class=""><span class="">scroll_id</span></span> <span class="keyword operator">=</span> <span class=""><span class=""><span class="">resp</span></span></span><span class=""><span class="punctuation">[</span></span><span class=""><span class="string"><span class="string"><span class="punctuation string">'</span></span></span><span class="string"><span class="string">_scroll_id<span class="punctuation string">'</span></span></span></span><span class=""><span class="punctuation">]</span></span>
<span class=""><span class="keyword control">while</span> <span class=""><span class=""><span class="support function">len</span></span><span class="punctuation">(</span><span class=""><span class=""><span class=""><span class="">resp</span></span></span><span class=""><span class="punctuation">[</span></span><span class=""><span class="string"><span class="string"><span class="punctuation string">'</span></span></span><span class="string"><span class="string">hits<span class="punctuation string">'</span></span></span></span><span class=""><span class="punctuation">]</span></span><span class=""><span class="punctuation">[</span></span><span class=""><span class="string"><span class="string"><span class="punctuation string">'</span></span></span><span class="string"><span class="string">hits<span class="punctuation string">'</span></span></span></span><span class=""><span class="punctuation">]</span></span></span><span class="punctuation">)</span></span><span class="punctuation">:</span></span>
<span class=""><span class="keyword control">for</span> <span class="">doc</span> <span class=""><span class="keyword control">in</span></span></span><span class=""> <span class=""><span class=""><span class="">resp</span></span></span><span class=""><span class="punctuation">[</span></span><span class=""><span class="string"><span class="string"><span class="punctuation string">'</span></span></span><span class="string"><span class="string">hits<span class="punctuation string">'</span></span></span></span><span class=""><span class="punctuation">]</span></span><span class=""><span class="punctuation">[</span></span><span class=""><span class="string"><span class="string"><span class="punctuation string">'</span></span></span><span class="string"><span class="string">hits<span class="punctuation string">'</span></span></span></span><span class=""><span class="punctuation">]</span></span><span class="punctuation">:</span></span>
<span class=""><span class="">docs_listed</span></span><span class="keyword operator">+=</span><span class="constant numeric">1</span>
<span class=""><span class="keyword control">if</span> <span class=""><span class=""><span class="support type">int</span></span><span class="punctuation">(</span><span class=""><span class=""><span class=""><span class="">doc</span></span></span><span class=""><span class="punctuation">[</span></span><span class=""><span class="string"><span class="string"><span class="punctuation string">'</span></span></span><span class="string"><span class="string">_id<span class="punctuation string">'</span></span></span></span><span class=""><span class="punctuation">]</span></span></span><span class="punctuation">)</span></span> <span class="keyword operator">not</span> <span class="keyword operator">in</span> <span class=""><span class="">extracted</span></span><span class="punctuation">:</span></span>
<span class="comment"><span class="punctuation comment">#</span> the account doc in OS is not any more listed in the Mastodon instance
</span> <span class=""><span class=""><span class="variable function">move_document</span></span><span class="punctuation">(</span><span class=""><span class=""><span class="">client</span></span><span class="punctuation">,</span> <span class="variable parameter">id</span><span class="keyword operator">=</span><span class=""><span class=""><span class="">doc</span></span></span><span class=""><span class="punctuation">[</span></span><span class=""><span class="string"><span class="string"><span class="punctuation string">'</span></span></span><span class="string"><span class="string">_id<span class="punctuation string">'</span></span></span></span><span class=""><span class="punctuation">]</span></span><span class="punctuation">,</span> <span class="variable parameter">index_from</span><span class="keyword operator">=</span><span class=""><span class="">index_name</span></span><span class="punctuation">,</span> <span class="variable parameter">index_to</span><span class="keyword operator">=</span><span class="string"><span class="string"><span class="punctuation string">"</span></span></span><span class="string"><span class="string">un-<span class="punctuation string">"</span></span></span> <span class="keyword operator">+</span> <span class=""><span class="">index_name</span></span></span><span class="punctuation">)</span></span>
<span class=""><span class="">docs_moved</span></span><span class="keyword operator">+=</span><span class="constant numeric">1</span>
<span class="comment"><span class="punctuation comment">#</span> make a request using the Scroll API
</span> <span class=""><span class="">resp</span></span> <span class="keyword operator">=</span> <span class=""><span class=""><span class="">client</span><span class="punctuation">.</span></span><span class=""><span class="variable function">scroll</span></span><span class="punctuation">(</span><span class="">
<span class="variable parameter">scroll_id</span> <span class="keyword operator">=</span> <span class=""><span class="">scroll_id</span></span><span class="punctuation">,</span>
<span class="variable parameter">scroll</span> <span class="keyword operator">=</span> <span class="string"><span class="string"><span class="punctuation string">'</span></span></span><span class="string"><span class="string">5s<span class="punctuation string">'</span></span></span> <span class="comment"><span class="punctuation comment">#</span> length of time to keep search context
</span> </span><span class="punctuation">)</span></span>
<span class=""><span class="">scroll_id</span></span> <span class="keyword operator">=</span> <span class=""><span class=""><span class="">resp</span></span></span><span class=""><span class="punctuation">[</span></span><span class=""><span class="string"><span class="string"><span class="punctuation string">'</span></span></span><span class="string"><span class="string">_scroll_id<span class="punctuation string">'</span></span></span></span><span class=""><span class="punctuation">]</span></span>
<span class=""><span class=""><span class="">client</span><span class="punctuation">.</span></span><span class=""><span class="variable function">clear_scroll</span></span><span class="punctuation">(</span><span class=""><span class="variable parameter">scroll_id</span><span class="keyword operator">=</span><span class=""><span class="">scroll_id</span></span></span><span class="punctuation">)</span></span>
<span class=""><span class=""><span class="support function">print</span></span><span class="punctuation">(</span><span class=""><span class="storage type string">f</span><span class="string"><span class="string"><span class="punctuation string">'</span></span></span><span class="string"><span class="string">In "</span><span class=""><span class="punctuation">{</span><span class="source"><span class=""><span class="">index_name</span></span></span></span><span class=""><span class="punctuation">}</span></span><span class="string">" </span><span class=""><span class="punctuation">{</span><span class="source"><span class=""><span class="">docs_listed</span></span></span></span><span class=""><span class="punctuation">}</span></span><span class="string"> found, <span class="punctuation string">'</span></span></span>
<span class="storage type string">f</span><span class="string"><span class="string"><span class="punctuation string">'</span></span></span><span class="string"><span class=""><span class="punctuation">{</span><span class="source"><span class=""><span class="">docs_moved</span></span></span></span><span class=""><span class="punctuation">}</span></span><span class="string"> to un-</span><span class=""><span class="punctuation">{</span><span class="source"><span class=""><span class="">index_name</span></span></span></span><span class=""><span class="punctuation">}</span></span><span class="string">. <span class="punctuation string">'</span></span></span>
<span class="storage type string">f</span><span class="string"><span class="string"><span class="punctuation string">'</span></span></span><span class="string"><span class=""><span class="punctuation">{</span><span class="source"><span class=""><span class="">docs_listed</span></span><span class="keyword operator">-</span><span class=""><span class="">docs_moved</span></span></span></span><span class=""><span class="punctuation">}</span></span><span class="string"> remaining<span class="punctuation string">'</span></span></span></span><span class="punctuation">)</span></span>
</span></code></pre>
<p dir="auto">Ok, das ist schon harter Tobak. Vor der While-Schleife ist die Abfrage, bei der wir zugleich OpenSearch Anweisen uns eine Scroll-ID zu geben. So weit, so klar. Die While-Bedingung ist eigentlich ein: Mach so lange, wie wir Treffer bekommen. Die For-Schleife iteriert durch unsere Dokumente, die wir angefragt haben. </p>
<p dir="auto">Die einzige spannende Logik ist wirklich die Prüfung, ob die <code>doc['id']</code> sich nicht in der Liste der <code>extracted</code> befindet. Wenn das so ist, dann haben wir den Fall, dass das Dokument (also der Account) im OpenSearch Index abgelegt wurde, aber in Mastodon keine Verbindung mehr zu meinem Account hat (die <code>id</code> ist nicht in <code>extracted</code> vorhanden). Damit hätten wir einen Treffer eines “Un-Follower” oder “Un-Following”. </p>
<p dir="auto">Da mit dieses verschachtelte Monstrum an Methode nicht noch komplexer wird, erfolgt dann eben wieder ein Unteraufruf der nächsten Funktion (die es noch nicht gibt): <code>move_document (...)</code>.</p>
<p dir="auto">Der Rest der obigen Methode ist nur noch das Behandeln der Scroll-IDs und ein wenig Statistik und Infos an uns Entwickler*innen.</p>
<h2 dir="auto">Ein Dokument verschieben</h2>
<p dir="auto">Also rein in die <code>move_document</code>, die wirklich nicht mehr viel macht:</p>
<pre><code dir="auto"><span class="source"><span class="function"><span class="storage type function">def</span> <span class="entity name function"><span class="">move_document</span></span></span><span class="function"><span class="punctuation">(</span></span><span class="function"><span class="variable parameter">client</span></span><span class="function"><span class="punctuation parameter">:</span> <span class=""><span class="">OpenSearch</span></span></span><span class="function"><span class="punctuation">,</span> <span class="variable parameter">id</span><span class="punctuation">,</span> <span class="variable parameter">index_from</span><span class="punctuation">,</span> <span class="variable parameter">index_to</span><span class="punctuation">)</span></span><span class="function"><span class="punctuation function">:</span></span>
<span class=""><span class="">doc</span></span> <span class="keyword operator">=</span> <span class=""><span class=""><span class="">client</span><span class="punctuation">.</span></span><span class=""><span class="variable function">get</span></span><span class="punctuation">(</span><span class=""><span class=""><span class="">index_from</span></span><span class="punctuation">,</span> <span class=""><span class="support function">id</span></span></span><span class="punctuation">)</span></span>
<span class=""><span class="">source</span></span> <span class="keyword operator">=</span> <span class=""><span class=""><span class="">doc</span></span></span><span class=""><span class="punctuation">[</span></span><span class=""><span class="string"><span class="string"><span class="punctuation string">'</span></span></span><span class="string"><span class="string">_source<span class="punctuation string">'</span></span></span></span><span class=""><span class="punctuation">]</span></span>
<span class=""><span class=""><span class="">client</span><span class="punctuation">.</span></span><span class=""><span class="variable function">index</span></span><span class="punctuation">(</span><span class=""><span class=""><span class="">index_to</span></span><span class="punctuation">,</span> <span class="variable parameter">id</span><span class="keyword operator">=</span><span class=""><span class="support function">id</span></span><span class="punctuation">,</span> <span class="variable parameter">body</span><span class="keyword operator">=</span><span class=""><span class="">source</span></span></span><span class="punctuation">)</span></span>
<span class=""><span class=""><span class="">client</span><span class="punctuation">.</span></span><span class=""><span class="variable function">delete</span></span><span class="punctuation">(</span><span class=""><span class=""><span class="">index_from</span></span><span class="punctuation">,</span> <span class=""><span class="support function">id</span></span></span><span class="punctuation">)</span></span>
<span class=""><span class=""><span class="">client</span><span class="punctuation">.</span><span class="">indices</span><span class="punctuation">.</span></span><span class=""><span class="variable function">refresh</span></span><span class="punctuation">(</span><span class=""><span class="variable parameter">index</span><span class="keyword operator">=</span><span class=""><span class="">index_from</span></span></span><span class="punctuation">)</span></span>
<span class=""><span class=""><span class="">client</span><span class="punctuation">.</span><span class="">indices</span><span class="punctuation">.</span></span><span class=""><span class="variable function">refresh</span></span><span class="punctuation">(</span><span class=""><span class="variable parameter">index</span><span class="keyword operator">=</span><span class=""><span class="">index_to</span></span></span><span class="punctuation">)</span></span>
<span class=""><span class=""><span class="support function">print</span></span><span class="punctuation">(</span><span class=""><span class="storage type string">f</span><span class="string"><span class="string"><span class="punctuation string">"</span></span></span><span class="string"><span class="string"> Moved </span><span class=""><span class="punctuation">{</span><span class="source"><span class=""><span class=""><span class="">source</span></span></span><span class=""><span class="punctuation">[</span></span><span class=""><span class="string"><span class="string"><span class="punctuation string">'</span></span></span><span class="string"><span class="string">acct<span class="punctuation string">'</span></span></span></span><span class=""><span class="punctuation">]</span></span></span></span><span class=""><span class="punctuation">}</span></span><span class="string"> to </span><span class=""><span class="punctuation">{</span><span class="source"><span class=""><span class="">index_to</span></span></span></span><span class=""><span class="punctuation">}</span></span><span class="string"> <span class="punctuation string">"</span></span></span></span><span class="punctuation">)</span></span>
</span></code></pre>
<p dir="auto">Sie ist sehr generisch geschrieben und kann daher mit beliebigen Dokumenten auf beliebigen Indexen ausgeführt werden. Der Aufrufer muss nur die passenden Parameter setzen. Das refresh ist etwas ineffizient, wenn wir das aus einer Schleife aufrufen, aber wir hoffen sowieso nur auf wenige Verschiebungen. </p>
<p dir="auto">Das <code>move_document</code> ist eine Kombination aus: Nimm das Dokument, indiziere es im Zielindex, lösche es im Quellindex, aktualisiere beide Indexe und schreib was Nettes in die Konsole. Fehlerbehandlung ist mal wieder ausgeklammert.</p>
<p dir="auto">Im Prinzip war das keine wirklich aufwendige Erweiterung. Etwas Komplexität kam durch den Einsatz der Scroll API. Aber es ist sinnvoll, sich Ergebnisse aus einem Datenpool grundsätzlich nur in Häppchen zu geben und ohne Scroll API wäre bei über 10.000 Dokumenten Schluss gewesen. Ob wir wirklich mal auf als 10.000 Follower haben werden… na ja, aber wir wissen nun wie es geht.</p>
<hr>
<p dir="auto">Noch ein paar Anmerkungen, bevor ich das Script mal komplett am Ende aufliste. Das Ausgeben der Informationen auf die Konsole ist eine tolle Sache, aber lebt nur diesen kurzen Augenblick und ist meistens schnell verpufft. Suchen wir explizit nach Fehlern, hilft uns das als Entwickler*innen. Tauchen aber Fehler erst im produktiven Betrieb auf, haben wir nichts in der Hand die Probleme nachzuvollziehen. </p>
<p dir="auto">Dafür gibt es überlicherweise Logging-Frameworks. Logging bedeutet, wir übergeben eine Meldung an ein Framework, dass daraus eine strukturierte Information macht und diese üblicherweise in Textdateien ablegt (für Menschen lesbar). Manche Logging-Frameworks bieten Connectoren, diese Log-Daten in Datenbanken zu schrieben, damit die durchsuchbar werden. Meistens handelt es sich dann wieder um ElasticSearch oder OpenSearch. </p>
<p dir="auto">Das könnten wir einfach verkürzen. Wir könnten einen Index für eine Arbneits-Historie anlegen und einen Index für Ausnahmen und Fehler. Ich baue das jetzt nicht ein, sondern werde dafür einen extra Artikel machen. Das ist nämlich eine ungeheuer sinnvolle Sache und man sollte das mal gemacht haben, um zu sehen, wie einfach das geht und welche Vorteile das bringt.</p>
<hr>
<h1 dir="auto">Das Script loadFollowersFollowingtoOS.py</h1>
<p dir="auto">Nun aber das komplette Script, das auch Un-Follows und Un-Following versteht:</p>
<pre><code dir="auto"><span class="source"><span class=""><span class="keyword control">import</span> <span class=""><span class="">json</span></span></span>
<span class=""><span class="keyword control">from</span></span><span class=""><span class=""> <span class=""><span class="">typing</span></span> <span class=""><span class="keyword control">import</span></span></span></span><span class=""></span><span class=""> <span class="">Any</span></span>
<span class=""><span class="keyword control">from</span></span><span class=""><span class=""> <span class=""><span class="">mastodon</span></span> <span class=""><span class="keyword control">import</span></span></span></span><span class=""></span><span class=""> <span class="">Mastodon</span></span>
<span class=""><span class="keyword control">from</span></span><span class=""><span class=""> <span class=""><span class="">opensearchpy</span></span> <span class=""><span class="keyword control">import</span></span></span></span><span class=""></span><span class=""> <span class="">OpenSearch</span></span>
<span class="function">
<span class="storage type function">def</span> <span class="entity name function"><span class="">login_os</span></span></span><span class="function"><span class="punctuation">(</span></span><span class="function"><span class="variable parameter">host</span><span class="punctuation">,</span> <span class="variable parameter">port</span><span class="punctuation">)</span></span><span class="function"><span class="punctuation function">:</span></span>
<span class="comment"><span class="punctuation comment">#</span> Client zu dem Dev Cluster (ohne SSL, ohne Anmeldung)
</span> <span class="keyword control">return</span> <span class=""><span class=""><span class="variable function">OpenSearch</span></span><span class="punctuation">(</span><span class="">
<span class="variable parameter">hosts</span><span class="keyword operator">=</span><span class=""><span class="punctuation">[</span><span class=""><span class="punctuation">{</span><span class="string"><span class="string"><span class="punctuation string">'</span></span></span><span class="string"><span class="string">host<span class="punctuation string">'</span></span></span><span class="punctuation">:</span> <span class=""><span class="">host</span></span><span class="punctuation">,</span> <span class="string"><span class="string"><span class="punctuation string">'</span></span></span><span class="string"><span class="string">port<span class="punctuation string">'</span></span></span><span class="punctuation">:</span> <span class=""><span class="">port</span></span><span class="punctuation">}</span></span><span class="punctuation">]</span></span><span class="punctuation">,</span>
<span class="variable parameter">http_compress</span><span class="keyword operator">=</span><span class="constant language">True</span><span class="punctuation">,</span> <span class="comment"><span class="punctuation comment">#</span> enables gzip compression for request bodies
</span> <span class="variable parameter">use_ssl</span><span class="keyword operator">=</span><span class="constant language">False</span>
</span><span class="punctuation">)</span></span>
<span class="function">
<span class="storage type function">def</span> <span class="entity name function"><span class="">login_mastodon</span></span></span><span class="function"><span class="punctuation">(</span></span><span class="function"><span class="variable parameter">url</span><span class="punctuation">,</span> <span class="variable parameter">user</span><span class="punctuation">,</span> <span class="variable parameter">password</span><span class="punctuation">)</span></span><span class="function"><span class="punctuation function">:</span></span>
<span class=""><span class="">mastodon</span></span> <span class="keyword operator">=</span> <span class=""><span class=""><span class="variable function">Mastodon</span></span><span class="punctuation">(</span><span class="">
<span class="variable parameter">client_id</span><span class="keyword operator">=</span><span class="string"><span class="string"><span class="punctuation string">'</span></span></span><span class="string"><span class="string">ep*****************************************Xo<span class="punctuation string">'</span></span></span><span class="punctuation">,</span> <span class="comment"><span class="punctuation comment">#</span> Eure ID
</span> <span class="variable parameter">client_secret</span><span class="keyword operator">=</span><span class="string"><span class="string"><span class="punctuation string">'</span></span></span><span class="string"><span class="string">AY******************************************0s<span class="punctuation string">'</span></span></span><span class="punctuation">,</span> <span class="comment"><span class="punctuation comment">#</span> das Geheimnis zur ID
</span> <span class="variable parameter">api_base_url</span><span class="keyword operator">=</span><span class=""><span class="">url</span></span>
</span><span class="punctuation">)</span></span>
<span class=""><span class="">mastodon</span><span class="punctuation">.</span><span class="">access_token</span></span> <span class="keyword operator">=</span> <span class=""><span class=""><span class="">mastodon</span><span class="punctuation">.</span></span><span class=""><span class="variable function">log_in</span></span><span class="punctuation">(</span><span class="">
<span class="variable parameter">username</span><span class="keyword operator">=</span><span class=""><span class="">user</span></span><span class="punctuation">,</span>
<span class="variable parameter">password</span><span class="keyword operator">=</span><span class=""><span class="">password</span></span><span class="punctuation">,</span>
<span class="variable parameter">scopes</span><span class="keyword operator">=</span><span class=""><span class="punctuation">[</span><span class="string"><span class="string"><span class="punctuation string">'</span></span></span><span class="string"><span class="string">read<span class="punctuation string">'</span></span></span><span class="punctuation">,</span> <span class="string"><span class="string"><span class="punctuation string">'</span></span></span><span class="string"><span class="string">write<span class="punctuation string">'</span></span></span><span class="punctuation">]</span></span>
</span><span class="punctuation">)</span></span>
<span class="keyword control">return</span> <span class=""><span class="">mastodon</span></span>
<span class="function">
<span class="storage type function">def</span> <span class="entity name function"><span class="">move_document</span></span></span><span class="function"><span class="punctuation">(</span></span><span class="function"><span class="variable parameter">client</span></span><span class="function"><span class="punctuation parameter">:</span> <span class=""><span class="">OpenSearch</span></span></span><span class="function"><span class="punctuation">,</span> <span class="variable parameter">id</span><span class="punctuation">,</span> <span class="variable parameter">index_from</span><span class="punctuation">,</span> <span class="variable parameter">index_to</span><span class="punctuation">)</span></span><span class="function"><span class="punctuation function">:</span></span>
<span class=""><span class="">doc</span></span> <span class="keyword operator">=</span> <span class=""><span class=""><span class="">client</span><span class="punctuation">.</span></span><span class=""><span class="variable function">get</span></span><span class="punctuation">(</span><span class=""><span class=""><span class="">index_from</span></span><span class="punctuation">,</span> <span class=""><span class="support function">id</span></span></span><span class="punctuation">)</span></span>
<span class=""><span class="">source</span></span> <span class="keyword operator">=</span> <span class=""><span class=""><span class="">doc</span></span></span><span class=""><span class="punctuation">[</span></span><span class=""><span class="string"><span class="string"><span class="punctuation string">'</span></span></span><span class="string"><span class="string">_source<span class="punctuation string">'</span></span></span></span><span class=""><span class="punctuation">]</span></span>
<span class=""><span class=""><span class="">client</span><span class="punctuation">.</span></span><span class=""><span class="variable function">index</span></span><span class="punctuation">(</span><span class=""><span class=""><span class="">index_to</span></span><span class="punctuation">,</span> <span class="variable parameter">id</span><span class="keyword operator">=</span><span class=""><span class="support function">id</span></span><span class="punctuation">,</span> <span class="variable parameter">body</span><span class="keyword operator">=</span><span class=""><span class="">source</span></span></span><span class="punctuation">)</span></span>
<span class=""><span class=""><span class="">client</span><span class="punctuation">.</span></span><span class=""><span class="variable function">delete</span></span><span class="punctuation">(</span><span class=""><span class=""><span class="">index_from</span></span><span class="punctuation">,</span> <span class=""><span class="support function">id</span></span></span><span class="punctuation">)</span></span>
<span class=""><span class=""><span class="">client</span><span class="punctuation">.</span><span class="">indices</span><span class="punctuation">.</span></span><span class=""><span class="variable function">refresh</span></span><span class="punctuation">(</span><span class=""><span class="variable parameter">index</span><span class="keyword operator">=</span><span class=""><span class="">index_from</span></span></span><span class="punctuation">)</span></span>
<span class=""><span class=""><span class="">client</span><span class="punctuation">.</span><span class="">indices</span><span class="punctuation">.</span></span><span class=""><span class="variable function">refresh</span></span><span class="punctuation">(</span><span class=""><span class="variable parameter">index</span><span class="keyword operator">=</span><span class=""><span class="">index_to</span></span></span><span class="punctuation">)</span></span>
<span class=""><span class=""><span class="support function">print</span></span><span class="punctuation">(</span><span class=""><span class="storage type string">f</span><span class="string"><span class="string"><span class="punctuation string">"</span></span></span><span class="string"><span class="string"> Moved </span><span class=""><span class="punctuation">{</span><span class="source"><span class=""><span class=""><span class="">source</span></span></span><span class=""><span class="punctuation">[</span></span><span class=""><span class="string"><span class="string"><span class="punctuation string">'</span></span></span><span class="string"><span class="string">acct<span class="punctuation string">'</span></span></span></span><span class=""><span class="punctuation">]</span></span></span></span><span class=""><span class="punctuation">}</span></span><span class="string"> to </span><span class=""><span class="punctuation">{</span><span class="source"><span class=""><span class="">index_to</span></span></span></span><span class=""><span class="punctuation">}</span></span><span class="string"> <span class="punctuation string">"</span></span></span></span><span class="punctuation">)</span></span>
<span class="function">
<span class="storage type function">def</span> <span class="entity name function"><span class="">move_unlisted</span></span></span><span class="function"><span class="punctuation">(</span></span><span class="function"><span class="variable parameter">client</span><span class="punctuation">,</span> <span class="variable parameter">index_name</span><span class="punctuation">,</span> <span class="variable parameter">extracted</span><span class="punctuation">)</span></span><span class="function"><span class="punctuation function">:</span></span>
<span class=""><span class="">docs_listed</span></span><span class="keyword operator">=</span><span class="constant numeric">0</span>
<span class=""><span class="">docs_moved</span></span><span class="keyword operator">=</span><span class="constant numeric">0</span>
<span class=""><span class="">search_all</span></span><span class="keyword operator">=</span><span class=""><span class="punctuation">{</span>
<span class="string"><span class="string"><span class="punctuation string">"</span></span></span><span class="string"><span class="string">_source<span class="punctuation string">"</span></span></span><span class="punctuation">:</span> <span class="constant language">False</span><span class="punctuation">,</span>
<span class="string"><span class="string"><span class="punctuation string">"</span></span></span><span class="string"><span class="string">size<span class="punctuation string">"</span></span></span><span class="punctuation">:</span> <span class="constant numeric">100</span><span class="punctuation">,</span>
<span class="string"><span class="string"><span class="punctuation string">"</span></span></span><span class="string"><span class="string">query<span class="punctuation string">"</span></span></span><span class="punctuation">:</span> <span class=""><span class="punctuation">{</span>
<span class="string"><span class="string"><span class="punctuation string">"</span></span></span><span class="string"><span class="string">match_all<span class="punctuation string">"</span></span></span><span class="punctuation">:</span> <span class=""><span class="punctuation">{</span><span class=""><span class="punctuation">}</span></span></span>
<span class="punctuation">}</span></span>
<span class="punctuation">}</span></span>
<span class=""><span class="">resp</span></span><span class="keyword operator">=</span><span class=""><span class=""><span class="">client</span><span class="punctuation">.</span></span><span class=""><span class="variable function">search</span></span><span class="punctuation">(</span><span class=""><span class="variable parameter">index</span><span class="keyword operator">=</span><span class=""><span class="">index_name</span></span><span class="punctuation">,</span> <span class="variable parameter">body</span><span class="keyword operator">=</span><span class=""><span class="">search_all</span></span><span class="punctuation">,</span> <span class="variable parameter">scroll</span><span class="keyword operator">=</span><span class="string"><span class="string"><span class="punctuation string">'</span></span></span><span class="string"><span class="string">5s<span class="punctuation string">'</span></span></span></span><span class="punctuation">)</span></span>
<span class=""><span class="">scroll_id</span></span> <span class="keyword operator">=</span> <span class=""><span class=""><span class="">resp</span></span></span><span class=""><span class="punctuation">[</span></span><span class=""><span class="string"><span class="string"><span class="punctuation string">'</span></span></span><span class="string"><span class="string">_scroll_id<span class="punctuation string">'</span></span></span></span><span class=""><span class="punctuation">]</span></span>
<span class=""><span class="keyword control">while</span> <span class=""><span class=""><span class="support function">len</span></span><span class="punctuation">(</span><span class=""><span class=""><span class=""><span class="">resp</span></span></span><span class=""><span class="punctuation">[</span></span><span class=""><span class="string"><span class="string"><span class="punctuation string">'</span></span></span><span class="string"><span class="string">hits<span class="punctuation string">'</span></span></span></span><span class=""><span class="punctuation">]</span></span><span class=""><span class="punctuation">[</span></span><span class=""><span class="string"><span class="string"><span class="punctuation string">'</span></span></span><span class="string"><span class="string">hits<span class="punctuation string">'</span></span></span></span><span class=""><span class="punctuation">]</span></span></span><span class="punctuation">)</span></span><span class="punctuation">:</span></span>
<span class=""><span class="keyword control">for</span> <span class="">doc</span> <span class=""><span class="keyword control">in</span></span></span><span class=""> <span class=""><span class=""><span class="">resp</span></span></span><span class=""><span class="punctuation">[</span></span><span class=""><span class="string"><span class="string"><span class="punctuation string">'</span></span></span><span class="string"><span class="string">hits<span class="punctuation string">'</span></span></span></span><span class=""><span class="punctuation">]</span></span><span class=""><span class="punctuation">[</span></span><span class=""><span class="string"><span class="string"><span class="punctuation string">'</span></span></span><span class="string"><span class="string">hits<span class="punctuation string">'</span></span></span></span><span class=""><span class="punctuation">]</span></span><span class="punctuation">:</span></span>
<span class=""><span class="">docs_listed</span></span><span class="keyword operator">+=</span><span class="constant numeric">1</span>
<span class=""><span class="keyword control">if</span> <span class=""><span class=""><span class="support type">int</span></span><span class="punctuation">(</span><span class=""><span class=""><span class=""><span class="">doc</span></span></span><span class=""><span class="punctuation">[</span></span><span class=""><span class="string"><span class="string"><span class="punctuation string">'</span></span></span><span class="string"><span class="string">_id<span class="punctuation string">'</span></span></span></span><span class=""><span class="punctuation">]</span></span></span><span class="punctuation">)</span></span> <span class="keyword operator">not</span> <span class="keyword operator">in</span> <span class=""><span class="">extracted</span></span><span class="punctuation">:</span></span>
<span class="comment"><span class="punctuation comment">#</span> the account doc in OS is not any more listed in the Mastodon instance
</span> <span class=""><span class=""><span class="variable function">move_document</span></span><span class="punctuation">(</span><span class=""><span class=""><span class="">client</span></span><span class="punctuation">,</span> <span class="variable parameter">id</span><span class="keyword operator">=</span><span class=""><span class=""><span class="">doc</span></span></span><span class=""><span class="punctuation">[</span></span><span class=""><span class="string"><span class="string"><span class="punctuation string">'</span></span></span><span class="string"><span class="string">_id<span class="punctuation string">'</span></span></span></span><span class=""><span class="punctuation">]</span></span><span class="punctuation">,</span> <span class="variable parameter">index_from</span><span class="keyword operator">=</span><span class=""><span class="">index_name</span></span><span class="punctuation">,</span> <span class="variable parameter">index_to</span><span class="keyword operator">=</span><span class="string"><span class="string"><span class="punctuation string">"</span></span></span><span class="string"><span class="string">un-<span class="punctuation string">"</span></span></span> <span class="keyword operator">+</span> <span class=""><span class="">index_name</span></span></span><span class="punctuation">)</span></span>
<span class=""><span class="">docs_moved</span></span><span class="keyword operator">+=</span><span class="constant numeric">1</span>
<span class="comment"><span class="punctuation comment">#</span> make a request using the Scroll API
</span> <span class=""><span class="">resp</span></span> <span class="keyword operator">=</span> <span class=""><span class=""><span class="">client</span><span class="punctuation">.</span></span><span class=""><span class="variable function">scroll</span></span><span class="punctuation">(</span><span class="">
<span class="variable parameter">scroll_id</span> <span class="keyword operator">=</span> <span class=""><span class="">scroll_id</span></span><span class="punctuation">,</span>
<span class="variable parameter">scroll</span> <span class="keyword operator">=</span> <span class="string"><span class="string"><span class="punctuation string">'</span></span></span><span class="string"><span class="string">5s<span class="punctuation string">'</span></span></span> <span class="comment"><span class="punctuation comment">#</span> length of time to keep search context
</span> </span><span class="punctuation">)</span></span>
<span class=""><span class="">scroll_id</span></span> <span class="keyword operator">=</span> <span class=""><span class=""><span class="">resp</span></span></span><span class=""><span class="punctuation">[</span></span><span class=""><span class="string"><span class="string"><span class="punctuation string">'</span></span></span><span class="string"><span class="string">_scroll_id<span class="punctuation string">'</span></span></span></span><span class=""><span class="punctuation">]</span></span>
<span class=""><span class=""><span class="">client</span><span class="punctuation">.</span></span><span class=""><span class="variable function">clear_scroll</span></span><span class="punctuation">(</span><span class=""><span class="variable parameter">scroll_id</span><span class="keyword operator">=</span><span class=""><span class="">scroll_id</span></span></span><span class="punctuation">)</span></span>
<span class=""><span class=""><span class="support function">print</span></span><span class="punctuation">(</span><span class=""><span class="storage type string">f</span><span class="string"><span class="string"><span class="punctuation string">'</span></span></span><span class="string"><span class="string">In "</span><span class=""><span class="punctuation">{</span><span class="source"><span class=""><span class="">index_name</span></span></span></span><span class=""><span class="punctuation">}</span></span><span class="string">" </span><span class=""><span class="punctuation">{</span><span class="source"><span class=""><span class="">docs_listed</span></span></span></span><span class=""><span class="punctuation">}</span></span><span class="string"> found, <span class="punctuation string">'</span></span></span>
<span class="storage type string">f</span><span class="string"><span class="string"><span class="punctuation string">'</span></span></span><span class="string"><span class=""><span class="punctuation">{</span><span class="source"><span class=""><span class="">docs_moved</span></span></span></span><span class=""><span class="punctuation">}</span></span><span class="string"> to un-</span><span class=""><span class="punctuation">{</span><span class="source"><span class=""><span class="">index_name</span></span></span></span><span class=""><span class="punctuation">}</span></span><span class="string">. <span class="punctuation string">'</span></span></span>
<span class="storage type string">f</span><span class="string"><span class="string"><span class="punctuation string">'</span></span></span><span class="string"><span class=""><span class="punctuation">{</span><span class="source"><span class=""><span class="">docs_listed</span></span><span class="keyword operator">-</span><span class=""><span class="">docs_moved</span></span></span></span><span class=""><span class="punctuation">}</span></span><span class="string"> remaining<span class="punctuation string">'</span></span></span></span><span class="punctuation">)</span></span>
<span class="function">
<span class="storage type function">def</span> <span class="entity name function"><span class="">load_to_index</span></span></span><span class="function"><span class="punctuation">(</span></span><span class="function"><span class="variable parameter">client</span><span class="punctuation">,</span> <span class="variable parameter">index_name</span><span class="punctuation">,</span> <span class="variable parameter">initial_load</span><span class="punctuation">,</span> <span class="variable parameter">next_load</span><span class="punctuation">)</span></span><span class="function"><span class="punctuation function">:</span></span>
<span class=""><span class=""><span class="support function">print</span></span><span class="punctuation">(</span><span class=""><span class="storage type string">f</span><span class="string"><span class="string"><span class="punctuation string">'</span></span></span><span class="string"><span class="string">Import </span><span class=""><span class="punctuation">{</span><span class="source"><span class=""><span class="">index_name</span></span></span></span><span class=""><span class="punctuation">}</span></span><span class="string"><span class="punctuation string">'</span></span></span></span><span class="punctuation">)</span></span>
<span class=""><span class="">page</span></span> <span class="keyword operator">=</span> <span class="constant language">None</span>
<span class=""><span class="">created</span></span> <span class="keyword operator">=</span> <span class="constant numeric">0</span>
<span class=""><span class="">updated</span></span> <span class="keyword operator">=</span> <span class="constant numeric">0</span>
<span class=""><span class="">extracted</span></span> <span class="keyword operator">=</span> <span class=""><span class="punctuation">[</span><span class="punctuation">]</span></span>
<span class=""><span class="keyword control">while</span> <span class="constant language">True</span><span class="punctuation">:</span></span>
<span class=""><span class="">page</span></span> <span class="keyword operator">=</span> <span class=""><span class=""><span class="variable function">initial_load</span></span><span class="punctuation">(</span><span class=""><span class="variable parameter">id</span><span class="keyword operator">=</span><span class=""><span class=""><span class="">me</span></span></span><span class=""><span class="punctuation">[</span></span><span class=""><span class="string"><span class="string"><span class="punctuation string">'</span></span></span><span class="string"><span class="string">id<span class="punctuation string">'</span></span></span></span><span class=""><span class="punctuation">]</span></span><span class="punctuation">,</span> <span class="variable parameter">limit</span><span class="keyword operator">=</span><span class="constant numeric">80</span></span><span class="punctuation">)</span></span> <span class=""><span class="keyword control">if</span> <span class=""><span class="">page</span></span> <span class="keyword operator">is</span> <span class="constant language">None</span> <span class="keyword control">else</span> <span class=""><span class=""><span class="variable function">next_load</span></span><span class="punctuation">(</span><span class=""><span class=""><span class="">page</span></span></span><span class="punctuation">)</span></span></span>
<span class=""><span class="keyword control">if</span> <span class=""><span class="">page</span></span><span class="punctuation">:</span></span>
<span class=""><span class="keyword control">for</span> <span class="">account</span> <span class=""><span class="keyword control">in</span></span></span><span class=""> <span class=""><span class="">page</span></span><span class="punctuation">:</span></span>
<span class=""><span class="support function">id</span></span> <span class="keyword operator">=</span> <span class=""><span class=""><span class="">account</span></span></span><span class=""><span class="punctuation">[</span></span><span class=""><span class="string"><span class="string"><span class="punctuation string">'</span></span></span><span class="string"><span class="string">id<span class="punctuation string">'</span></span></span></span><span class=""><span class="punctuation">]</span></span>
<span class=""><span class=""><span class="">extracted</span><span class="punctuation">.</span></span><span class=""><span class="variable function">append</span></span> <span class="punctuation">(</span><span class=""><span class=""><span class="support function">id</span></span></span><span class="punctuation">)</span></span>
<span class=""><span class="">response</span></span> <span class="keyword operator">=</span> <span class=""><span class=""><span class="">client</span><span class="punctuation">.</span></span><span class=""><span class="variable function">index</span></span><span class="punctuation">(</span><span class=""><span class=""><span class="">index_name</span></span><span class="punctuation">,</span> <span class="variable parameter">id</span><span class="keyword operator">=</span><span class=""><span class="support function">id</span></span><span class="punctuation">,</span> <span class="variable parameter">body</span><span class="keyword operator">=</span><span class=""><span class="">account</span></span></span><span class="punctuation">)</span></span>
<span class=""><span class="keyword control">if</span> <span class=""><span class=""><span class="">response</span></span></span><span class=""><span class="punctuation">[</span></span><span class=""><span class="string"><span class="string"><span class="punctuation string">'</span></span></span><span class="string"><span class="string">result<span class="punctuation string">'</span></span></span></span><span class=""><span class="punctuation">]</span></span> <span class="keyword operator">==</span> <span class="string"><span class="string"><span class="punctuation string">"</span></span></span><span class="string"><span class="string">created<span class="punctuation string">"</span></span></span><span class="punctuation">:</span></span>
<span class=""><span class=""><span class="support function">print</span></span><span class="punctuation">(</span><span class=""><span class="storage type string">f</span><span class="string"><span class="string"><span class="punctuation string">"</span></span></span><span class="string"><span class="string"> New in </span><span class=""><span class="punctuation">{</span><span class="source"><span class=""><span class="">index_name</span></span></span></span><span class=""><span class="punctuation">}</span></span><span class="string">: </span><span class=""><span class="punctuation">{</span><span class="source"><span class=""><span class=""><span class="">account</span></span></span><span class=""><span class="punctuation">[</span></span><span class=""><span class="string"><span class="string"><span class="punctuation string">'</span></span></span><span class="string"><span class="string">acct<span class="punctuation string">'</span></span></span></span><span class=""><span class="punctuation">]</span></span></span></span><span class=""><span class="punctuation">}</span></span><span class="string"><span class="punctuation string">"</span></span></span></span><span class="punctuation">)</span></span>
<span class=""><span class="">created</span></span> <span class="keyword operator">+=</span> <span class="constant numeric">1</span>
<span class=""><span class="keyword control">elif</span></span> <span class=""><span class=""><span class="">response</span></span></span><span class=""><span class="punctuation">[</span></span><span class=""><span class="string"><span class="string"><span class="punctuation string">'</span></span></span><span class="string"><span class="string">result<span class="punctuation string">'</span></span></span></span><span class=""><span class="punctuation">]</span></span> <span class="keyword operator">==</span> <span class="string"><span class="string"><span class="punctuation string">"</span></span></span><span class="string"><span class="string">updated<span class="punctuation string">"</span></span></span><span class="punctuation variable">:</span>
<span class=""><span class="">updated</span></span> <span class="keyword operator">+=</span> <span class="constant numeric">1</span>
<span class=""><span class="keyword control">else</span><span class="punctuation">:</span></span>
<span class="keyword control">break</span>
<span class=""><span class=""><span class="">client</span><span class="punctuation">.</span><span class="">indices</span><span class="punctuation">.</span></span><span class=""><span class="variable function">refresh</span></span><span class="punctuation">(</span><span class=""><span class="variable parameter">index</span><span class="keyword operator">=</span><span class=""><span class="">index_name</span></span></span><span class="punctuation">)</span></span>
<span class=""><span class=""><span class="support function">print</span></span><span class="punctuation">(</span><span class=""><span class="storage type string">f</span><span class="string"><span class="string"><span class="punctuation string">'</span></span></span><span class="string"><span class="string">Finished "</span><span class=""><span class="punctuation">{</span><span class="source"><span class=""><span class="">index_name</span></span></span></span><span class=""><span class="punctuation">}</span></span><span class="string">". Created </span><span class=""><span class="punctuation">{</span><span class="source"><span class=""><span class="">created</span></span></span></span><span class=""><span class="punctuation">}</span></span><span class="string"> accounts and updated </span><span class=""><span class="punctuation">{</span><span class="source"><span class=""><span class="">updated</span></span></span></span><span class=""><span class="punctuation">}</span></span><span class="string"><span class="punctuation string">'</span></span></span></span><span class="punctuation">)</span></span>
<span class="comment"><span class="punctuation comment">#</span> Now we have in extracted the current active existing accounts assigned to the Mastodon-Account
</span> <span class="comment"><span class="punctuation comment">#</span> In the OS index we have maybe more. We must move the account to un-{index_name}
</span> <span class=""><span class=""><span class="variable function">move_unlisted</span></span><span class="punctuation">(</span><span class=""><span class=""><span class="">client</span></span><span class="punctuation">,</span> <span class=""><span class="">index_name</span></span><span class="punctuation">,</span> <span class=""><span class="">extracted</span></span></span><span class="punctuation">)</span></span>
<span class=""><span class="">client</span></span> <span class="keyword operator">=</span> <span class=""><span class=""><span class="variable function">login_os</span></span><span class="punctuation">(</span><span class=""><span class="string"><span class="string"><span class="punctuation string">'</span></span></span><span class="string"><span class="string">localhost<span class="punctuation string">'</span></span></span><span class="punctuation">,</span> <span class="constant numeric">9200</span></span><span class="punctuation">)</span></span>
<span class=""><span class="">mastodon</span></span> <span class="keyword operator">=</span> <span class=""><span class=""><span class="variable function">login_mastodon</span></span><span class="punctuation">(</span><span class=""><span class="string"><span class="string"><span class="punctuation string">'</span></span></span><span class="string"><span class="string">https://social.tchncs.de<span class="punctuation string">'</span></span></span><span class="punctuation">,</span> <span class="string"><span class="string"><span class="punctuation string">'</span></span></span><span class="string"><span class="string"><email-adresse>, <span class="punctuation string">'</span></span></span><span class="keyword operator"><</span><span class=""><span class="">kennwort</span></span> <span class=""><span class="">des</span></span> <span class=""><span class="">accounts</span></span><span class="keyword operator">></span><span class="string"><span class="string"><span class="punctuation string">'</span></span></span><span class="string"><span class="string">)<span class="invalid">
</span></span></span>
<span class="comment"><span class="punctuation comment">#</span> It's me:
</span><span class="variable parameter">me</span> <span class="keyword operator">=</span> <span class=""><span class=""><span class="">mastodon</span><span class="punctuation">.</span></span><span class=""><span class="variable function">me</span></span><span class="punctuation">(</span><span class="punctuation">)</span></span>
<span class=""><span class=""><span class="support function">print</span> </span><span class="punctuation">(</span><span class=""><span class="storage type string">f</span><span class="string"><span class="string"><span class="punctuation string">"</span></span></span><span class="string"><span class="string">Me: </span><span class=""><span class="punctuation">{</span><span class="source"><span class=""><span class=""><span class="">me</span></span></span><span class=""><span class="punctuation">[</span></span><span class=""><span class="string"><span class="string"><span class="punctuation string">'</span></span></span><span class="string"><span class="string">id<span class="punctuation string">'</span></span></span></span><span class=""><span class="punctuation">]</span></span></span></span><span class=""><span class="punctuation">}</span></span><span class="string"> = </span><span class=""><span class="punctuation">{</span><span class="source"><span class=""><span class=""><span class="">me</span></span></span><span class=""><span class="punctuation">[</span></span><span class=""><span class="string"><span class="string"><span class="punctuation string">'</span></span></span><span class="string"><span class="string">username<span class="punctuation string">'</span></span></span></span><span class=""><span class="punctuation">]</span></span></span></span><span class=""><span class="punctuation">}</span></span><span class="string"><span class="punctuation string">"</span></span></span></span><span class="punctuation">)</span></span>
<span class="comment"><span class="punctuation comment">#</span> Followers von Mastodon nach OpenSearch kopieren:
</span><span class=""><span class=""><span class="variable function">load_to_index</span></span><span class="punctuation">(</span><span class=""><span class=""><span class="">client</span></span><span class="punctuation">,</span> <span class="string"><span class="string"><span class="punctuation string">'</span></span></span><span class="string"><span class="string">followers<span class="punctuation string">'</span></span></span><span class="punctuation">,</span> <span class=""><span class="">mastodon</span><span class="punctuation">.</span><span class="">account_followers</span></span><span class="punctuation">,</span> <span class=""><span class="">mastodon</span><span class="punctuation">.</span><span class="">fetch_next</span></span></span><span class="punctuation">)</span></span>
<span class=""><span class=""><span class="variable function">load_to_index</span></span><span class="punctuation">(</span><span class=""><span class=""><span class="">client</span></span><span class="punctuation">,</span> <span class="string"><span class="string"><span class="punctuation string">'</span></span></span><span class="string"><span class="string">following<span class="punctuation string">'</span></span></span><span class="punctuation">,</span> <span class=""><span class="">mastodon</span><span class="punctuation">.</span><span class="">account_following</span></span><span class="punctuation">,</span> <span class=""><span class="">mastodon</span><span class="punctuation">.</span><span class="">fetch_next</span></span></span><span class="punctuation">)</span></span>
</span></span></span></code></pre>
<p dir="auto">Eine Ausgabe, wie es mal bei mir ausgesehen hat (die Sternchen sind natürlich reale Daten aus Mastodon):</p>
<pre><code dir="auto">Me: ****** = beandev
Import followers
New in followers: **********@*****.****
New in followers: ******@*****.**
New in followers: *************@*****.*****
New in followers: *******@*****.**
New in followers: *********@*****.**
Finished "followers". Created 5 accounts and updated 196
Moved ********** to un-followed
In "followers" 201 found, 1 to un-followers. 200 remaining
Import following
New in following: *************@*****.*****
New in following: ************@*****.*******
New in following: ********@*****.*****
Finished "following". Created 3 accounts and updated 139
In "following" 142 found, 0 to un-following. 142 remaining
</code></pre>
<p dir="auto">Ja, also ein Account hat mich wohl verlassen… Aber jeder Abschied hat auch was Gutes. Hier war es möglich zu testen, ob die neue Logik funktioniert.</p>
<p dir="auto">Wer war das wohl?</p>
<p dir="auto"><a href="http://localhost:9200/un-followers/_search?pretty=true" rel="noopener noreferrer">http://localhost:9200/un-followers/_search?pretty=true</a></p>
<p dir="auto">Egal… </p>
<p dir="auto">Viel Spaß mit dem Script! Interessiert sich Eure Bubble auch für <a href="//write.tchncs.de/tag/Python" title="Python" rel="noopener noreferrer">#Python</a>, <a href="//write.tchncs.de/tag/Mastodon" title="Mastodon" rel="noopener noreferrer">#Mastodon</a> und <a href="//write.tchncs.de/tag/OpenSearch" title="OpenSearch" rel="noopener noreferrer">#OpenSearch</a>? Dann boosted den Artikel!</p>
]]>