{"id":74953,"date":"2020-03-22T08:42:22","date_gmt":"2020-03-22T05:42:22","guid":{"rendered":"https:\/\/prohoster.info\/blog\/administrirovanie\/dba-gramotno-organizovyvaem-sinhronizaczii-i-importy"},"modified":"2020-03-22T08:42:22","modified_gmt":"2020-03-22T05:42:22","slug":"dba-gramotno-organizovyvaem-sinhronizaczii-i-importy","status":"publish","type":"post","link":"https:\/\/prohoster.info\/de\/blog\/administrirovanie\/dba-gramotno-organizovyvaem-sinhronizaczii-i-importy","title":{"rendered":"DBA: Synchronisationen und Importe sinnvoll organisieren","gt_translate_keys":[{"key":"rendered","format":"text"}]},"content":{"rendered":"<p>Bei der komplexen Verarbeitung gro\u00dfer Datens\u00e4tze (verschiedene <noindex><a rel=\"nofollow\" href=\"https:\/\/ru.wikipedia.org\/wiki\/ETL\">ETL-Prozesse<\/a><\/noindex>: Importe, Konvertierungen und Synchronisationen mit externen Quellen) entsteht oft die Notwendigkeit <b>tempor\u00e4r \"zu speichern\" und sofort schnell zu verarbeiten<\/b> etwas Umfangreiches.<\/p>\n<p>Eine typische Aufgabe dieser Art lautet im Allgemeinen etwa so: <i>\"Hier hat die Buchhaltung die <noindex><a rel=\"nofollow\" href=\"https:\/\/sbis.ru\/accounting\">letzten eingegangenen Zahlungen aus dem Kundenbankupdate exportiert, die m\u00fcssen schnell auf die Website hochgeladen und den Konten zugewiesen werden\"<\/a><\/noindex> Doch wenn das Volumen dieses \"etwas\" in Hunderten von Megabytes gemessen wird und der Dienst dabei 24 \u00d7 7 mit der Datenbank arbeiten muss, treten viele Nebenwirkungen auf, die Ihr Leben erschweren.<\/i><\/p>\n<p>Aber wenn das Volumen dieses \u201eetwas\u201c in Hunderten von Megabyte gemessen wird und der Dienst gleichzeitig mit der Datenbank im 24\/7-Betrieb weiter laufen muss, treten viele Nebenwirkungen auf, die Ihnen das Leben schwer machen werden.<br \/>\n<img decoding=\"async\" alt=\"DBA: Synchronisationen und Importe sinnvoll organisieren\" src=\"\/wp-content\/uploads\/2020\/03\/f74afb2cd6f5f8de26a0932166933c95.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n1. Wo hochladen?<br \/>\n<noindex><a rel=\"nofollow\" name=\"habracut\"><\/a><\/noindex><\/p>\n<h2>Zun\u00e4chst sollten wir kl\u00e4ren, wohin wir die Daten hochladen k\u00f6nnen, die wir \"verarbeiten\" wollen.<\/h2>\n<p>\n1.1. Tempor\u00e4re Tabellen (TEMPORARY TABLE)<\/p>\n<h3>Prinzipiell sind tempor\u00e4re Tabellen in PostgreSQL ganz normale Tabellen wie alle anderen auch. Daher sind Aberglauben wie<\/h3>\n<p>\n\"dort wird alles nur im Speicher gespeichert, und der kann ausgehen\" <i><b>nicht zutreffend. Aber es gibt auch einige wesentliche Unterschiede.<\/b><\/i>Eine eigene \"Namespaces\" f\u00fcr jede Verbindung zur Datenbank<\/p>\n<h4>Wenn zwei Verbindungen gleichzeitig versuchen,<\/h4>\n<p>\nCREATE TABLE x <code>, dann wird einer von ihnen definitiv<\/code>einen Fehler der Nicht-Eindeutigkeit <b>von DB-Objekten erhalten.<\/b> Wenn jedoch beide versuchen,<\/p>\n<p>, wird jeder dies ordnungsgem\u00e4\u00df durchf\u00fchren und jeder wird <code>CREATE <b>VOR\u00dcBERGEHEND<\/b> TABELLE x<\/code>seinen eigenen Instanz <b>der Tabelle erhalten. Und es wird nichts Gemeinsames zwischen ihnen geben.<\/b> \"Selbstzerst\u00f6rung\" bei der Trennung<\/p>\n<h4>Beim Schlie\u00dfen der Verbindung werden alle tempor\u00e4ren Tabellen automatisch gel\u00f6scht, daher macht es \"von Hand\"<\/h4>\n<p>\nDROP TABLE x <code>keinen Sinn, au\u00dfer ...<\/code> Wenn Sie \u00fcber<\/p>\n<p>pgbouncer im Transaktionsmodus <b>, dann denkt die Datenbank weiterhin, dass diese Verbindung noch aktiv ist, und in ihr existiert diese tempor\u00e4re Tabelle nach wie vor.<\/b>Deshalb wird der Versuch, sie erneut aus einer anderen Verbindung zu pgbouncer zu erstellen, zu einem Fehler f\u00fchren. Aber das l\u00e4sst sich umgehen, indem man<\/p>\n<p>Es ist jedoch besser, so etwas zu vermeiden, da man dann \"unerwartet\" Daten finden k\u00f6nnte, die vom \"vorherigen Besitzer\" \u00fcbrig geblieben sind. Stattdessen ist es viel besser, tats\u00e4chlich das Handbuch zu lesen und zu sehen, dass man bei der Erstellung einer Tabelle die M\u00f6glichkeit hat, diese zu erg\u00e4nzen. <code>TEMPOR\u00c4RE TABELLE ERSTELLEN <b>WENN NICHT EXISTIERT<\/b> x<\/code>.<\/p>\n<p>Nat\u00fcrlich ist es besser, dies so nicht zu machen, da man dann \u201epl\u00f6tzlich\u201c Daten findet, die vom \u201evorherigen Besitzer\u201c \u00fcbrig geblieben sind. Stattdessen ist es viel besser, das Handbuch zu lesen und zu sehen, dass beim Erstellen einer Tabelle die M\u00f6glichkeit besteht, erg\u00e4nzende Informationen hinzuzuf\u00fcgen. <code>BEI BEST\u00c4TIGUNG <b>DROP<\/b><\/code> \u2014 das hei\u00dft, nach Abschluss der Transaktion wird die Tabelle automatisch gel\u00f6scht.<\/p>\n<h4>Nicht-Replikation<\/h4>\n<p>\nAufgrund der Zugeh\u00f6rigkeit zu einer bestimmten Verbindung werden tempor\u00e4re Tabellen nicht repliziert. Daf\u00fcr <b>befreit es von der Notwendigkeit der doppelten Datenspeicherung<\/b> in Heap + WAL, weshalb INSERT\/UPDATE\/DELETE darin erheblich schneller ist.<\/p>\n<p>Aber da eine tempor\u00e4re Tabelle dennoch eine \"fast gew\u00f6hnliche\" Tabelle ist, kann sie auch auf der Replik nicht erstellt werden. Zumindest im Moment, obwohl der entsprechende Patch schon lange existiert.<\/p>\n<h3>1.2. Nicht-protokollierte Tabellen (UNLOGGED TABLE)<\/h3>\n<p>\nWas ist jedoch zu tun, wenn Sie beispielsweise einen ziemlich schwerf\u00e4lligen ETL-Prozess haben, den Sie nicht innerhalb einer einzigen Transaktion realisieren k\u00f6nnen, und Sie dennoch <b>, dann denkt die Datenbank weiterhin, dass diese Verbindung noch aktiv ist, und in ihr existiert diese tempor\u00e4re Tabelle nach wie vor.<\/b>?..<\/p>\n<p>Oder wenn der Datenstrom so gro\u00df ist, dass <b>die Bandbreite einer einzigen Verbindung<\/b> zur DB (sprich, eines Prozesses auf der CPU) nicht ausreicht? ..<\/p>\n<p>Oder wenn Teile der Operationen <b>asynchron<\/b> in verschiedenen Verbindungen durchgef\u00fchrt werden? ..<\/p>\n<p>Hier gibt es nur eine M\u00f6glichkeit \u2014 <b>vor\u00fcbergehend eine Nicht-Tempor\u00e4re Tabelle zu erstellen<\/b>. Wortspiel, ja. Das hei\u00dft:<\/p>\n<ul>\n<li>Ich habe \"meine\" Tabellen mit maximal zuf\u00e4lligen Namen erstellt, um nicht mit jemandem in Konflikt zu geraten<\/li>\n<li><b>Extraktion<\/b>: Ich habe Daten aus einer externen Quelle in sie geladen<\/li>\n<li><b>Transformation<\/b>: Ich habe sie umgewandelt und die Schl\u00fcsselfelder gef\u00fcllt<\/li>\n<li><b>mit dem ausgew\u00e4hlten Testplan<\/b>: Ich habe die fertigen Daten in die Zieltabellen \u00fcbertragen<\/li>\n<li>und meine Tabellen gel\u00f6scht<\/li>\n<\/ul>\n<p>\nUnd jetzt \u2014 ein Wermutstropfen. Im Wesentlichen <b>werden alle Schreibvorg\u00e4nge in PostgreSQL zweimal durchgef\u00fchrt<\/b> \u2014 <noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/company\/postgrespro\/blog\/461523\/\">zuerst im WAL<\/a><\/noindex>, dann in die Tabellen- \/ Indexk\u00f6rper. All dies wurde zur Unterst\u00fctzung von ACID und zur korrekten Sichtbarkeit der Daten zwischen <code>COMMIT<\/code>\u201eeingebetteten und <code>ROLLBACK<\/code>\u201eeingebetteten Transaktionen.<\/p>\n<p>Aber das brauchen wir nicht! Bei uns ist der gesamte Prozess <b>entweder vollst\u00e4ndig erfolgreich oder nicht<\/b>. Es spielt keine Rolle, wie viele Zwischentransaktionen darin stattfinden \u2014 uns interessiert nicht, \u201eden Prozess von der Mitte aus fortzusetzen\u201c, besonders wenn unklar ist, wo sie war.<\/p>\n<p>Daf\u00fcr haben die Entwickler von PostgreSQL bereits in Version 9.1 eine Funktion eingef\u00fchrt, wie <noindex><a rel=\"nofollow\" href=\"https:\/\/postgrespro.ru\/docs\/postgresql\/12\/sql-createtable#SQL-CREATETABLE-UNLOGGED\">nicht-protokollierte (UNLOGGED) Tabellen<\/a><\/noindex>:<\/p>\n<blockquote><p>Mit diesem Hinweis wird die Tabelle als nicht-protokolliert erstellt. Die Daten, die in nicht-protokollierte Tabellen geschrieben werden, durchlaufen nicht das Protokoll der Voraufzeichnung (siehe Kapitel 29), wodurch solche Tabellen <b>deutlich schneller arbeiten als gew\u00f6hnliche.<\/b>Sie sind jedoch nicht vor einem Ausfall gesch\u00fctzt; bei einem Ausfall oder einem pl\u00f6tzlichen Serverabbruch wird die nicht-protokollierte Tabelle <b>automatisch abgeschnitten.<\/b>Dar\u00fcber hinaus wird der Inhalt der nicht-protokollierten Tabelle <b>nicht repliziert.<\/b> auf die verwalteten Server. Alle Indizes, die f\u00fcr eine nicht protokollierte Tabelle erstellt werden, werden automatisch nicht protokolliert.<\/p><\/blockquote>\n<p>Kurz gesagt, <b>wird es deutlich schneller sein,<\/b>, aber wenn der DB-Server \u201eausf\u00e4llt\u201c, wird es unangenehm. Kommt das h\u00e4ufig vor, und ist Ihr ETL-Prozess in der Lage, dies korrekt \u201eaus der Mitte\u201c nach \u201eBelebung\u201c der DB zu verarbeiten?..<\/p>\n<p>Wenn nicht, und der oben genannte Fall Ihrem \u00e4hnelt - verwenden Sie <code>UNLOGGED<\/code>, aber aktivieren Sie dieses Attribut niemals f\u00fcr reale Tabellen <b>, deren Daten Ihnen wichtig sind.<\/b>1.3. ON COMMIT { DELETE ROWS | DROP }<\/p>\n<h3>Diese Konstruktion erm\u00f6glicht es, beim Erstellen einer Tabelle ein automatisches Verhalten beim Abschluss der Transaktion festzulegen.<\/h3>\n<p>\nIch habe bereits oben geschrieben, dass er generiert,<\/p>\n<p>\u00dcber <code>BEI BEST\u00c4TIGUNG <b>DROP<\/b><\/code> , aber die Situation mit <code>DROP TABLE<\/code>interessanter - hier wird erzeugt <code>BEI BEST\u00c4TIGUNG <b>ZEILE L\u00d6SCHEN<\/b><\/code> TRUNCATE TABLE <code>Da die gesamte Infrastruktur zur Speicherung der Metadaten der tempor\u00e4ren Tabelle genau die gleiche ist wie die einer normalen Tabelle, f\u00fchrt<\/code>.<\/p>\n<p>das st\u00e4ndige Erstellen und L\u00f6schen tempor\u00e4rer Tabellen zu einem starken \"Aufbl\u00e4hen\" der Systemtabellen <b>pg_class, pg_attribute, pg_attrdef, pg_depend,\u2026<\/b> Stellen Sie sich jetzt vor, Sie haben einen Worker, der \u00fcber eine direkte Verbindung zur DB jede Sekunde eine neue Transaktion \u00f6ffnet, eine tempor\u00e4re Tabelle erstellt, f\u00fcllt, verarbeitet und l\u00f6scht\u2026 Der M\u00fcll in den Systemtabellen wird sich geh\u00e4uft ansammeln, und das f\u00fchrt zu zus\u00e4tzlichen Verz\u00f6gerungen bei jeder Operation.<\/p>\n<p>Im Allgemeinen sollte man das vermeiden! In diesem Fall ist es viel effizienter,<\/p>\n<p>CREATE TEMPORARY TABLE x ... ON COMMIT DELETE ROWS <code>au\u00dferhalb des Transaktionszyklus zu halten - dann wird die Tabelle zu Beginn jeder neuen Transaktion bereits<\/code> existieren <b>(wir sparen den Aufruf<\/b> ), aber <code>CREATE<\/code>wird leer sein <b>, dank<\/b>(auch diesen Aufruf haben wir gespart) beim Abschluss der vorherigen Transaktion. <code>TRUNCATE<\/code> 1.4. LIKE\u2026 INCLUDING \u2026<\/p>\n<h3>1.4. WIE\u2026 EINBEZIEHEN \u2026<\/h3>\n<p>\nAber Faulheit ist der Motor des Fortschritts! Daher<\/p>\n<p>kann eine neue Tabelle \u201enach Vorbild\u201c <b>viel einfacher erstellt werden:<\/b> CREATE TEMPORARY TABLE import_table(\n  LIKE target_table\n);<\/p>\n<pre><code class=\"sql\">Da in diese Tabelle sp\u00e4ter ziemlich viele Daten eingef\u00fcgt werden k\u00f6nnen, wird die Suche darin alles andere als schnell sein. Aber dagegen gibt es eine traditionelle L\u00f6sung - Indizes! Und ja,<\/code><\/pre>\n<p>\nauch tempor\u00e4re Tabellen k\u00f6nnen Indizes haben. <b>Da oft die ben\u00f6tigten Indizes mit den Indizes der Zieltabelle \u00fcbereinstimmen, kann man einfach schreiben,<\/b>.<\/p>\n<p>Wenn Sie auch <code>LIKE Ziel_Tabelle <b>EINSCHLIESSLICH INDEXEN<\/b><\/code>.<\/p>\n<p>DEFAULT <code>STANDARD<\/code>-Werte (zum Beispiel zur Bef\u00fcllung der Werte des Prim\u00e4rschl\u00fcssels) kann man nutzen <code>LIKE Ziel_Tabelle <b>EINSCHLIESSLICH DER STANDARDWERT<\/b><\/code>. Oder einfach \u2014 <code>LIKE Ziel_Tabelle <b>EINSCHLIESSLICH ALLER<\/b><\/code> \u2014 kopiert die Standardwerte, Indizes, Constraints,\u2026<\/p>\n<p>Aber hier muss man schon verstehen, dass wenn Sie die <b>Importtabelle sofort mit Indizes erstellt haben, die Daten l\u00e4nger ben\u00f6tigt werden<\/b>, als wenn man zuerst alles importiert und dann die Indizes hinzuf\u00fcgt \u2014 siehe als Beispiel, wie das <noindex><a rel=\"nofollow\" href=\"https:\/\/postgrespro.ru\/docs\/postgresql\/12\/app-pgdump\">pg_dump<\/a><\/noindex>.<\/p>\n<p>Im Allgemeinen, <noindex><a rel=\"nofollow\" href=\"https:\/\/postgrespro.ru\/docs\/postgresql\/12\/sql-createtable\">RTFM<\/a><\/noindex>!<\/p>\n<h2>2. Wie schreiben?<\/h2>\n<p>\nIch sage einfach \u2014 verwenden Sie <code><noindex><a rel=\"nofollow\" href=\"https:\/\/postgrespro.ru\/docs\/postgresql\/12\/sql-copy\">COPY<\/a><\/noindex><\/code>-Stream statt \"Batch\" <code>INSERT<\/code>, <noindex><a rel=\"nofollow\" href=\"https:\/\/www.citusdata.com\/blog\/2017\/11\/08\/faster-bulk-loading-in-postgresql-with-copy\/\">Beschleunigung um ein Vielfaches<\/a><\/noindex>. Man kann sogar direkt aus einer vorher formatierten Datei.<\/p>\n<h2>3. Wie verarbeiten?<\/h2>\n<p>\nAlso, nehmen wir an, dass unser Input ungef\u00e4hr so aussieht:<\/p>\n<ul>\n<li>Sie haben in Ihrer Datenbank eine Tabelle mit Kundendaten von <b>1M Datens\u00e4tzen<\/b><\/li>\n<li>jeden Tag sendet der Kunde Ihnen ein neues <b>volles \"Abbild\"<\/b><\/li>\n<li>Aus Erfahrung wissen Sie, dass sich von Mal zu Mal <b>nicht mehr als 10K Datens\u00e4tze \u00e4ndern<\/b><\/li>\n<\/ul>\n<p>\nEin klassisches Beispiel f\u00fcr eine solche Situation ist <noindex><a rel=\"nofollow\" href=\"https:\/\/www.gnivc.ru\/technical_support\/classifiers_reference\/kladr\/\">die KLRAD-Datenbank<\/a><\/noindex> \u2014 es gibt viele Adressen, aber in jedem w\u00f6chentlichen Update an \u00c4nderungen (Umbenennungen von St\u00e4dten, Fusionen von Stra\u00dfen, das Auftauchen neuer H\u00e4user) gibt es insgesamt sehr wenige, selbst im Ma\u00dfstab des gesamten Landes.<\/p>\n<h3>3.1. Algorithmus zur vollst\u00e4ndigen Synchronisierung<\/h3>\n<p>\nZur Vereinfachung nehmen wir an, dass es nicht n\u00f6tig ist, die Daten neu zu strukturieren \u2014 bringen Sie einfach die Tabelle in die gew\u00fcnschte Form, das hei\u00dft:<\/p>\n<ul>\n<li><b>l\u00f6schen<\/b> alles, was nicht mehr vorhanden ist<\/li>\n<li><b>das System aus dem p9-Zweig des Repositories \u201eSisyphus\u201c aktualisieren. F\u00fcr neue Unternehmenskunden besteht die M\u00f6glichkeit, Testversionen zu erhalten, und Privatnutzern wird traditionell angeboten, die ben\u00f6tigte Version des OS \u201eAlt\u201c kostenlos von der Website \u201eBasalt SPO\u201c oder von der neuen Download-Seite<\/b> alles, was bereits war und aktualisiert werden muss<\/li>\n<li><b>einf\u00fcgen<\/b> alles, was noch nicht vorhanden war<\/li>\n<\/ul>\n<p>\nWarum sollte man die Operationen genau in dieser Reihenfolge durchf\u00fchren? Weil die Tabellengr\u00f6\u00dfe auf diese Weise minimal wachsen wird (<noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/company\/tensor\/blog\/491366\/\">denken Sie an MVCC!<\/a><\/noindex>).<\/p>\n<h4>DELETE FROM dst<\/h4>\n<p>\nNein, nat\u00fcrlich kann man mit nur zwei Operationen auskommen:<\/p>\n<ul>\n<li><b>l\u00f6schen<\/b> (<code>DELETE<\/code>) tats\u00e4chlich alles<\/li>\n<li><b>einf\u00fcgen<\/b> alles aus dem neuen Abbild<\/li>\n<\/ul>\n<p>\nAber dabei wird, dank MVCC, die <b>Tabellengr\u00f6\u00dfe genau um das Zweifache wachsen<\/b>! +1M Abbilden von Datens\u00e4tzen in der Tabelle aufgrund von 10K-Updates ist ziemlich \u00fcbertrieben\u2026<\/p>\n<h4>TRUNCATE dst<\/h4>\n<p>\nEin erfahrener Entwickler wei\u00df, dass man die gesamte Tabelle relativ kosteng\u00fcnstig leeren kann:<\/p>\n<ul>\n<li><b>bereinigen<\/b> (<code>TRUNCATE<\/code>) die gesamte Tabelle<\/li>\n<li><b>einf\u00fcgen<\/b> alles aus dem neuen Abbild<\/li>\n<\/ul>\n<p>\nDie Methode ist effektiv, <noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/company\/tensor\/blog\/481866\/\">in manchen F\u00e4llen durchaus anwendbar<\/a><\/noindex>, aber es gibt ein Problem\u2026 1M Datens\u00e4tze wird viel Zeit in Anspruch nehmen, sodass wir es uns nicht erlauben k\u00f6nnen, die Tabelle f\u00fcr all die Zeit leer zu lassen (wie es ohne Umschlie\u00dfung in eine einzige Transaktion geschehen w\u00fcrde).<\/p>\n<p>Das bedeutet:<\/p>\n<ul>\n<li>wir beginnen <b>eine lange Transaktion<\/b><\/li>\n<li><code>TRUNCATE<\/code> legt <b>AccessExclusive<\/b>- die Sperrung<\/li>\n<li>wir machen lange Einf\u00fcgungen, w\u00e4hrend alle anderen in der Zwischenzeit <b>nicht einmal <code>SELECT<\/code><\/b><\/li>\n<\/ul>\n<p>\nEs sieht nicht gut aus\u2026<\/p>\n<h4>ALTER TABLE\u2026 UMBENENNEN\u2026 \/ TABELLE L\u00d6SCHEN \u2026<\/h4>\n<p>\nEine M\u00f6glichkeit w\u00e4re, alles in eine separate neue Tabelle zu laden und dann einfach auf den Platz der alten zu benennen. Ein paar unangenehme Kleinigkeiten:<\/p>\n<ul>\n<li>doch auch <b>AccessExclusive<\/b>, wenn auch erheblich weniger zeitaufwendig<\/li>\n<li>alle Abfragepl\u00e4ne \/ Statistiken dieser Tabelle werden zur\u00fcckgesetzt, <noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/company\/tensor\/blog\/479656\/\">muss ANALYZE durchgef\u00fchrt werden<\/a><\/noindex><\/li>\n<li><b>alle Fremdschl\u00fcssel<\/b> (FK) auf die Tabelle<\/li>\n<\/ul>\n<p>\nEs gab einen WIP-Patch von Simon Riggs, der vorschlug, eine <code>ALTER<\/code>-Operation f\u00fcr den Austausch des Tabellenk\u00f6rpers auf Dateiebene vorzunehmen, ohne die Statistiken und FK zu ber\u00fchren, aber er konnte kein quorum erreichen.<\/p>\n<h4>DELETE, UPDATE, INSERT<\/h4>\n<p>\nAlso entscheiden wir uns f\u00fcr die nicht-blockierende Variante aus drei Operationen. Fast drei\u2026 Wie l\u00e4sst sich das am effizientesten umsetzen?<\/p>\n<pre><code class=\"sql\">-- alles geschieht im Rahmen einer Transaktion, damit niemand die \"Zwischen\"-Zust\u00e4nde sieht\nBEGIN;\n\n-- wir erstellen eine tempor\u00e4re Tabelle mit importierten Daten\nCREATE TEMPORARY TABLE tmp(\n  LIKE dst INCLUDING INDEXES -- nach dem Vorbild, einschlie\u00dflich der Indizes\n) ON COMMIT DROP; -- au\u00dferhalb der Transaktion ben\u00f6tigen wir sie nicht\n\n-- schnell neue Daten \u00fcber COPY einf\u00fcllen\nCOPY tmp FROM STDIN;\n-- ...\n-- .\n\n-- fehlende Eintr\u00e4ge l\u00f6schen\nDELETE FROM\n  dst D\nUSING\n  dst X\nLEFT JOIN\n  tmp Y\n    USING(pk1, pk2) -- Prim\u00e4rschl\u00fcsselfelder\nWHERE\n  (D.pk1, D.pk2) = (X.pk1, X.pk2) AND\n  Y IST NICHT UNTERSCHIEDLICH VON NULL; -- \u201eAnti-Join\u201c\n\n-- verbleibende Eintr\u00e4ge aktualisieren\nUPDATE\n  dst D\nSET\n  (f1, f2, f3) = (T.f1, T.f2, T.f3)\nFROM\n  tmp T\nWHERE\n  (D.pk1, D.pk2) = (T.pk1, T.pk2) AND\n  (D.f1, D.f2, D.f3) IST NICHT UNTERSCHIEDLICH VON (T.f1, T.f2, T.f3); -- es besteht kein Grund, \u00dcbereinstimmungen zu aktualisieren\n\n-- fehlende Eintr\u00e4ge einf\u00fcgen\nINSERT INTO\n  dst\nSELECT\n  T.*\nFROM\n  tmp T\nLEFT JOIN\n  dst D\n    USING(pk1, pk2)\nWHERE\n  D IST NICHT UNTERSCHIEDLICH VON NULL;\n\nCOMMIT;\n<\/code><\/pre>\n<p><\/p>\n<h3>3.2. Nachverarbeitung des Imports<\/h3>\n<p>\nIm gleichen KLRAD m\u00fcssen alle ge\u00e4nderten Eintr\u00e4ge zus\u00e4tzlich durch die Nachbearbeitung laufen \u2014 normalisiert, Schl\u00fcsselw\u00f6rter extrahiert, in die erforderlichen Strukturen gebracht. Aber wie kann man herausfinden \u2014 <b>was genau ge\u00e4ndert wurde<\/b>, ohne dabei den Synchronisierungscode zu verkomplizieren, idealerweise \u00fcberhaupt nicht zu ber\u00fchren?<\/p>\n<p>Wenn nur Ihr Prozess zur Synchronisierungszeit Schreibzugriff hat, k\u00f6nnen Sie einen Trigger verwenden, der alle \u00c4nderungen f\u00fcr uns erfasst:<\/p>\n<pre><code class=\"sql\">-- Zieltabelle\nCREATE TABLE kladr(...);\nCREATE TABLE kladr_house(...);\n\n-- Tabellen mit \u00c4nderungsprotokoll\nCREATE TABLE kladr$log(\n  ro kladr, -- hier liegen vollst\u00e4ndige Abbilder der alten\/neuen Eintr\u00e4ge\n  rn kladr\n);\n\nCREATE TABLE kladr_house$log(\n  ro kladr_house,\n  rn kladr_house\n);\n\n-- allgemeine Funktion zur Protokollierung von \u00c4nderungen\nCREATE OR REPLACE FUNCTION diff$log() RETURNS trigger AS $$\nDECLARE\n  dst varchar = TG_TABLE_NAME || '$log';\n  stmt text = '';\nBEGIN\n  -- \u00dcberpr\u00fcfen, ob Protokollierung bei der Aktualisierung des Eintrags erforderlich ist\n  IF TG_OP = 'UPDATE' THEN\n    IF NEW IS NOT DISTINCT FROM OLD THEN\n      RETURN NEW;\n    END IF;\n  END IF;\n  -- Eintrag im Protokoll erstellen\n  stmt = 'INSERT INTO ' || dst::text || '(ro,rn)VALUES(';\n  CASE TG_OP\n    WHEN 'INSERT' THEN\n      EXECUTE stmt || 'NULL,$1)' USING NEW;\n    WHEN 'UPDATE' THEN\n      EXECUTE stmt || '$1,$2)' USING OLD, NEW;\n    WHEN 'DELETE' THEN\n      EXECUTE stmt || '$1,NULL)' USING OLD;\n  END CASE;\n  RETURN NEW;\nEND;\n$$ LANGUAGE plpgsql;\n<\/code><\/pre>\n<p>\nJetzt k\u00f6nnen wir die Trigger vor Beginn der Synchronisierung anwenden (oder aktivieren \u00fcber <code>ALTER TABLE ... ENABLE TRIGGER ...<\/code>):<\/p>\n<pre><code class=\"sql\">CREATE TRIGGER log\n  AFTER INSERT OR UPDATE OR DELETE\n  ON kladr\n    FOR EACH ROW\n      EXECUTE PROCEDURE diff$log();\n\nCREATE TRIGGER log\n  AFTER INSERT OR UPDATE OR DELETE\n  ON kladr_house\n    FOR EACH ROW\n      EXECUTE PROCEDURE diff$log();\n<\/code><\/pre>\n<p>\nUnd dann extrahieren wir einfach aus den log-Tabellen alle erforderlichen \u00c4nderungen und schicken sie an zus\u00e4tzliche Bearbeitungsmechanismen.<\/p>\n<h3>3.3. Import von verkn\u00fcpften Datens\u00e4tzen<\/h3>\n<p>\nWir haben oben F\u00e4lle betrachtet, in denen die Datenstrukturen von Quelle und Empf\u00e4nger \u00fcbereinstimmen. Aber was tun, wenn der Export aus dem externen System ein Format hat, das von der Speicherung in unserer Datenbank abweicht?<\/p>\n<p>Nehmen wir als Beispiel die Speicherung von Kunden und deren Rechnungen, den klassischen Fall \u00abviele-zu-eins\u00bb:<\/p>\n<pre><code class=\"sql\">CREATE TABLE client(\n  client_id\n    serial\n      PRIMARY KEY\n, inn\n    varchar\n      UNIQUE\n, name\n    varchar\n);\n\nCREATE TABLE invoice(\n  invoice_id\n    serial\n      PRIMARY KEY\n, client_id\n    integer\n      REFERENCES client(client_id)\n, number\n    varchar\n, dt\n    date\n, sum\n    numeric(32,2)\n);<\/code><\/pre>\n<p>\nUnd die Daten von der externen Quelle kommen bei uns im Format 'alles in einem':<\/p>\n<pre><code class=\"sql\">CREATE TEMPORARY TABLE invoice_import(\n  client_inn\n    varchar\n, client_name\n    varchar\n, invoice_number\n    varchar\n, invoice_dt\n    date\n, invoice_sum\n    numeric(32,2)\n);<\/code><\/pre>\n<p>\nOffensichtlich k\u00f6nnen die Kundendaten in diesem Format dupliziert werden, wobei die Hauptzeile 'Rechnung' ist:<\/p>\n<pre><code class=\"plaintext\">0123456789;Wasi;A-01;2020-03-16;1000.00\n9876543210;Petja;A-02;2020-03-16;666.00\n0123456789;Wasi;B-03;2020-03-16;9999.00\n<\/code><\/pre>\n<p>\nF\u00fcr das Modell f\u00fcgen wir einfach unsere Testdaten ein, aber denken wir daran \u2014 <code>COPY<\/code> effizienter!<\/p>\n<pre><code class=\"sql\">INSERT INTO invoice_import\nVALUES\n  ('0123456789', 'Wasi', 'A-01', '2020-03-16', 1000.00)\n, ('9876543210', 'Petja', 'A-02', '2020-03-16', 666.00)\n, ('0123456789', 'Wasi', 'B-03', '2020-03-16', 9999.00);<\/code><\/pre>\n<p>\nZuerst identifizieren wir die 'Schnitte', auf die sich unsere 'Fakten' beziehen. In unserem Fall beziehen sich Rechnungen auf Kunden:<\/p>\n<pre><code class=\"sql\">CREATE TEMPORARY TABLE client_import AS\nSELECT DISTINCT ON(client_inn)\n-- man kann einfach SELECT DISTINCT verwenden, wenn die Daten von vornherein nicht widerspr\u00fcchlich sind\n  client_inn inn\n, client_name \"name\"\nFROM\n  invoice_import;<\/code><\/pre>\n<p>\nUm die Rechnungen korrekt mit den Kunden-IDs zu verkn\u00fcpfen, m\u00fcssen wir diese Identifikatoren zun\u00e4chst herausfinden oder generieren. Lassen Sie uns daf\u00fcr Felder hinzuf\u00fcgen:<\/p>\n<pre><code class=\"sql\">ALTER TABLE invoice_import HINZUF\u00dcGEN SPALTE client_id integer;\nALTER TABLE client_import HINZUF\u00dcGEN SPALTE client_id integer;<\/code><\/pre>\n<p>\nWir werden die oben beschriebene Methode zur Synchronisierung der Tabellen mit einer kleinen Anpassung verwenden \u2013 wir werden nichts in der Zieltabelle aktualisieren oder l\u00f6schen, da der Import von Kunden bei uns \u201eappend-only\u201c ist:<\/p>\n<pre><code class=\"sql\">-- wir setzen in der Importtabelle die IDs der bereits vorhandenen Datens\u00e4tze\nUPDATE\n  client_import T\nSET\n  client_id = D.client_id\nFROM\n  client D\nWHERE\n  T.inn = D.inn; -- eindeutiger Schl\u00fcssel\n\n-- f\u00fcgen Sie die fehlenden Datens\u00e4tze ein und setzen Sie deren IDs\nMIT ins ALS (\n  INSERT INTO client(\n    inn\n  , name\n  )\n  W\u00c4HLE\n    inn\n  , name\n  FROM\n    client_import\n  WHERE\n    client_id IS NULL -- wenn die ID nicht gesetzt wurde\n  RETURNING *\n)\nUPDATE\n  client_import T\nSET\n  client_id = D.client_id\nFROM\n  ins D\nWHERE\n  T.inn = D.inn; -- eindeutiger Schl\u00fcssel\n\n-- wir setzen die Kunden-IDs bei den Rechnungsdatens\u00e4tzen\nUPDATE\n  invoice_import T\nSET\n  client_id = D.client_id\nFROM\n  client_import D\nWHERE\n  T.client_inn = D.inn; -- anwendungsbezogener Schl\u00fcssel\n<\/code><\/pre>\n<p>\nIm Grunde ist alles \u2013 in <code>invoice_import<\/code> ist jetzt das Verkn\u00fcpfungsfeld ausgef\u00fcllt <code>client_id<\/code>, mit dem wir die Rechnung einf\u00fcgen werden.<br \/>\n<br \/>Quelle: <a content=\"nofollow\" rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/company\/tensor\/blog\/492464\/\">habr.com<\/a> <\/p>","protected":false,"gt_translate_keys":[{"key":"rendered","format":"html"}]},"excerpt":{"rendered":"<p>\u041f\u0440\u0438 \u0441\u043b\u043e\u0436\u043d\u043e\u0439 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0435 \u0431\u043e\u043b\u044c\u0448\u0438\u0445 \u043d\u0430\u0431\u043e\u0440\u043e\u0432 \u0434\u0430\u043d\u043d\u044b\u0445 (\u0440\u0430\u0437\u043d\u044b\u0435 ETL-\u043f\u0440\u043e\u0446\u0435\u0441\u0441\u044b: \u0438\u043c\u043f\u043e\u0440\u0442\u044b, \u043a\u043e\u043d\u0432\u0435\u0440\u0442\u0430\u0446\u0438\u0438 \u0438 \u0441\u0438\u043d\u0445\u0440\u043e\u043d\u0438\u0437\u0430\u0446\u0438\u0438 \u0441 \u0432\u043d\u0435\u0448\u043d\u0438\u043c \u0438\u0441\u0442\u043e\u0447\u043d\u0438\u043a\u043e\u043c) \u0447\u0430\u0441\u0442\u043e \u0432\u043e\u0437\u043d\u0438\u043a\u0430\u0435\u0442 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e\u0441\u0442\u044c \u0432\u0440\u0435\u043c\u0435\u043d\u043d\u043e \u00ab\u0437\u0430\u043f\u043e\u043c\u043d\u0438\u0442\u044c\u00bb, \u0438 \u0441\u0440\u0430\u0437\u0443 \u0431\u044b\u0441\u0442\u0440\u043e \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0430\u0442\u044c \u0447\u0442\u043e-\u0442\u043e \u043e\u0431\u044a\u0435\u043c\u043d\u043e\u0435. \u0422\u0438\u043f\u043e\u0432\u0430\u044f \u0437\u0430\u0434\u0430\u0447\u0430 \u043f\u043e\u0434\u043e\u0431\u043d\u043e\u0433\u043e \u0440\u043e\u0434\u0430 \u0437\u0432\u0443\u0447\u0438\u0442 \u043e\u0431\u044b\u0447\u043d\u043e \u043f\u0440\u0438\u043c\u0435\u0440\u043d\u043e \u0442\u0430\u043a: \u00ab\u0412\u043e\u0442 \u0442\u0443\u0442 \u0431\u0443\u0445\u0433\u0430\u043b\u0442\u0435\u0440\u0438\u044f \u0432\u044b\u0433\u0440\u0443\u0437\u0438\u043b\u0430 \u0438\u0437 \u043a\u043b\u0438\u0435\u043d\u0442-\u0431\u0430\u043d\u043a\u0430 \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0438\u0435 \u043f\u043e\u0441\u0442\u0443\u043f\u0438\u0432\u0448\u0438\u0435 \u043e\u043f\u043b\u0430\u0442\u044b, \u043d\u0430\u0434\u043e \u0438\u0445 \u0431\u044b\u0441\u0442\u0440\u0435\u043d\u044c\u043a\u043e \u0432\u043a\u0430\u0447\u0430\u0442\u044c \u043d\u0430 \u0441\u0430\u0439\u0442 \u0438 \u043f\u0440\u0438\u0432\u044f\u0437\u0430\u0442\u044c \u043a \u0441\u0447\u0435\u0442\u0430\u043c\u00bb \u041d\u043e \u043a\u043e\u0433\u0434\u0430 [&hellip;]<\/p>\n","protected":false,"gt_translate_keys":[{"key":"rendered","format":"html"}]},"author":1,"featured_media":74954,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[688],"tags":[],"class_list":["post-74953","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-administrirovanie"],"aioseo_notices":[],"aioseo_head":"\n\t\t<!-- All in One SEO 5.0.1.1 - aioseo.com -->\n\t<meta name=\"description\" content=\"\u041f\u0440\u0438 \u0441\u043b\u043e\u0436\u043d\u043e\u0439 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0435 \u0431\u043e\u043b\u044c\u0448\u0438\u0445 \u043d\u0430\u0431\u043e\u0440\u043e\u0432 \u0434\u0430\u043d\u043d\u044b\u0445 (\u0440\u0430\u0437\u043d\u044b\u0435 ETL-\u043f\u0440\u043e\u0446\u0435\u0441\u0441\u044b: \u0438\u043c\u043f\u043e\u0440\u0442\u044b, \u043a\u043e\u043d\u0432\u0435\u0440\u0442\u0430\u0446\u0438\u0438 \u0438 \u0441\u0438\u043d\u0445\u0440\u043e\u043d\u0438\u0437\u0430\u0446\u0438\u0438 \u0441 \u0432\u043d\u0435\u0448\u043d\u0438\u043c \u0438\u0441\u0442\u043e\u0447\u043d\u0438\u043a\u043e\u043c) \u0447\u0430\u0441\u0442\u043e.\" \/>\n\t<meta name=\"robots\" content=\"max-image-preview:large\" \/>\n\t<meta name=\"author\" content=\"Yuri Gagarin\"\/>\n\t<link rel=\"canonical\" href=\"https:\/\/prohoster.info\/de\/blog\/administrirovanie\/dba-gramotno-organizovyvaem-sinhronizaczii-i-importy\" \/>\n\t<meta name=\"generator\" content=\"All in One SEO (AIOSEO) 5.0.1.1\" \/>\n\t\t<meta property=\"og:locale\" content=\"de_DE\" \/>\n\t\t<meta property=\"og:site_name\" content=\"ProHoster | \u041a\u0443\u043f\u0438\u0442\u044c \u043d\u0430\u0434\u0435\u0436\u043d\u044b\u0439 \u0445\u043e\u0441\u0442\u0438\u043d\u0433 \u0434\u043b\u044f \u0441\u0430\u0439\u0442\u043e\u0432 \u0441 \u0437\u0430\u0449\u0438\u0442\u043e\u0439 \u043e\u0442 DDoS, VPS VDS \u0441\u0435\u0440\u0432\u0435\u0440\u044b\" \/>\n\t\t<meta property=\"og:type\" content=\"article\" \/>\n\t\t<meta property=\"og:title\" content=\"\ud83e\udd47DBA: \u0433\u0440\u0430\u043c\u043e\u0442\u043d\u043e \u043e\u0440\u0433\u0430\u043d\u0438\u0437\u043e\u0432\u044b\u0432\u0430\u0435\u043c \u0441\u0438\u043d\u0445\u0440\u043e\u043d\u0438\u0437\u0430\u0446\u0438\u0438 \u0438 \u0438\u043c\u043f\u043e\u0440\u0442\u044b | ProHoster\" \/>\n\t\t<meta property=\"og:description\" content=\"\u041f\u0440\u0438 \u0441\u043b\u043e\u0436\u043d\u043e\u0439 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0435 \u0431\u043e\u043b\u044c\u0448\u0438\u0445 \u043d\u0430\u0431\u043e\u0440\u043e\u0432 \u0434\u0430\u043d\u043d\u044b\u0445 (\u0440\u0430\u0437\u043d\u044b\u0435 ETL-\u043f\u0440\u043e\u0446\u0435\u0441\u0441\u044b: \u0438\u043c\u043f\u043e\u0440\u0442\u044b, \u043a\u043e\u043d\u0432\u0435\u0440\u0442\u0430\u0446\u0438\u0438 \u0438 \u0441\u0438\u043d\u0445\u0440\u043e\u043d\u0438\u0437\u0430\u0446\u0438\u0438 \u0441 \u0432\u043d\u0435\u0448\u043d\u0438\u043c \u0438\u0441\u0442\u043e\u0447\u043d\u0438\u043a\u043e\u043c) \u0447\u0430\u0441\u0442\u043e.\" \/>\n\t\t<meta property=\"og:url\" content=\"https:\/\/prohoster.info\/de\/blog\/administrirovanie\/dba-gramotno-organizovyvaem-sinhronizaczii-i-importy\" \/>\n\t\t<meta property=\"og:image\" content=\"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg\" \/>\n\t\t<meta property=\"og:image:secure_url\" content=\"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg\" \/>\n\t\t<meta property=\"og:image:width\" content=\"350\" \/>\n\t\t<meta property=\"og:image:height\" content=\"350\" \/>\n\t\t<meta property=\"article:published_time\" content=\"2020-03-22T05:42:22+00:00\" \/>\n\t\t<meta property=\"article:modified_time\" content=\"2020-03-22T05:42:22+00:00\" \/>\n\t\t<meta property=\"article:publisher\" content=\"https:\/\/www.facebook.com\/prohoster\" \/>\n\t\t<meta property=\"article:author\" content=\"https:\/\/www.facebook.com\/prohoster\" \/>\n\t\t<!-- All in One SEO -->\n\n","aioseo_head_json":{"title":"\ud83e\udd47DBA: Wir organisieren Synchronisationen und Importe professionell | ProHoster","description":"Bei der komplexen Verarbeitung gro\u00dfer Datens\u00e4tze (verschiedene ETL-Prozesse: Importe, Konversionen und Synchronisationen mit externen Quellen) h\u00e4ufig.","canonical_url":"https:\/\/prohoster.info\/de\/blog\/administrirovanie\/dba-gramotno-organizovyvaem-sinhronizaczii-i-importy","robots":"max-image-preview:large","keywords":"","webmasterTools":{"miscellaneous":""},"schema":null,"og:locale":"de_DE","og:site_name":"ProHoster | \u041a\u0443\u043f\u0438\u0442\u044c \u043d\u0430\u0434\u0435\u0436\u043d\u044b\u0439 \u0445\u043e\u0441\u0442\u0438\u043d\u0433 \u0434\u043b\u044f \u0441\u0430\u0439\u0442\u043e\u0432 \u0441 \u0437\u0430\u0449\u0438\u0442\u043e\u0439 \u043e\u0442 DDoS, VPS VDS \u0441\u0435\u0440\u0432\u0435\u0440\u044b","og:type":"article","og:title":"\ud83e\udd47DBA: \u0433\u0440\u0430\u043c\u043e\u0442\u043d\u043e \u043e\u0440\u0433\u0430\u043d\u0438\u0437\u043e\u0432\u044b\u0432\u0430\u0435\u043c \u0441\u0438\u043d\u0445\u0440\u043e\u043d\u0438\u0437\u0430\u0446\u0438\u0438 \u0438 \u0438\u043c\u043f\u043e\u0440\u0442\u044b | ProHoster","og:description":"\u041f\u0440\u0438 \u0441\u043b\u043e\u0436\u043d\u043e\u0439 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0435 \u0431\u043e\u043b\u044c\u0448\u0438\u0445 \u043d\u0430\u0431\u043e\u0440\u043e\u0432 \u0434\u0430\u043d\u043d\u044b\u0445 (\u0440\u0430\u0437\u043d\u044b\u0435 ETL-\u043f\u0440\u043e\u0446\u0435\u0441\u0441\u044b: \u0438\u043c\u043f\u043e\u0440\u0442\u044b, \u043a\u043e\u043d\u0432\u0435\u0440\u0442\u0430\u0446\u0438\u0438 \u0438 \u0441\u0438\u043d\u0445\u0440\u043e\u043d\u0438\u0437\u0430\u0446\u0438\u0438 \u0441 \u0432\u043d\u0435\u0448\u043d\u0438\u043c \u0438\u0441\u0442\u043e\u0447\u043d\u0438\u043a\u043e\u043c) \u0447\u0430\u0441\u0442\u043e.","og:url":"https:\/\/prohoster.info\/de\/blog\/administrirovanie\/dba-gramotno-organizovyvaem-sinhronizaczii-i-importy","og:image":"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg","og:image:secure_url":"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg","og:image:width":350,"og:image:height":350,"article:published_time":"2020-03-22T05:42:22+00:00","article:modified_time":"2020-03-22T05:42:22+00:00","article:publisher":"https:\/\/www.facebook.com\/prohoster","article:author":"https:\/\/www.facebook.com\/prohoster"},"aioseo_meta_data":{"post_id":"74953","title":null,"description":null,"keywords":null,"keyphrases":null,"primary_term":null,"canonical_url":null,"og_title":null,"og_description":null,"og_object_type":"default","og_image_type":"default","og_image_url":null,"og_image_width":null,"og_image_height":null,"og_image_custom_url":null,"og_image_custom_fields":null,"og_video":null,"og_custom_url":null,"og_article_section":null,"og_article_tags":null,"twitter_use_og":false,"twitter_card":"default","twitter_image_type":"default","twitter_image_url":null,"twitter_image_custom_url":null,"twitter_image_custom_fields":null,"twitter_title":null,"twitter_description":null,"schema":{"blockGraphs":[],"customGraphs":[],"default":{"data":{"Article":[],"Course":[],"Dataset":[],"FAQPage":[],"Movie":[],"Person":[],"Product":[],"ProductReview":[],"Car":[],"Recipe":[],"Service":[],"SoftwareApplication":[],"WebPage":[]},"graphName":"","isEnabled":true},"graphs":[]},"schema_type":null,"schema_type_options":null,"pillar_content":false,"robots_default":true,"robots_noindex":false,"robots_noarchive":false,"robots_nosnippet":false,"robots_nofollow":false,"robots_noimageindex":false,"robots_noodp":false,"robots_notranslate":false,"robots_max_snippet":null,"robots_max_videopreview":null,"robots_max_imagepreview":"large","priority":null,"frequency":null,"local_seo":null,"seo_analyzer_scan_date":null,"breadcrumb_settings":null,"limit_modified_date":false,"reviewed_by":null,"ai":null,"created":"2021-02-28 18:04:26","updated":"2022-09-30 13:25:20","focus_keyword":null,"additional_keywords":null,"truseo_locale":null},"gt_translate_keys":[{"key":"link","format":"url"}],"_links":{"self":[{"href":"https:\/\/prohoster.info\/de\/wp-json\/wp\/v2\/posts\/74953","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/prohoster.info\/de\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/prohoster.info\/de\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/prohoster.info\/de\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/prohoster.info\/de\/wp-json\/wp\/v2\/comments?post=74953"}],"version-history":[{"count":0,"href":"https:\/\/prohoster.info\/de\/wp-json\/wp\/v2\/posts\/74953\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/prohoster.info\/de\/wp-json\/wp\/v2\/media\/74954"}],"wp:attachment":[{"href":"https:\/\/prohoster.info\/de\/wp-json\/wp\/v2\/media?parent=74953"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/prohoster.info\/de\/wp-json\/wp\/v2\/categories?post=74953"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/prohoster.info\/de\/wp-json\/wp\/v2\/tags?post=74953"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}