{"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\/fr\/blog\/administrirovanie\/dba-gramotno-organizovyvaem-sinhronizaczii-i-importy","title":{"rendered":"DBA : nous organisons efficacement les synchronisations et les importations.","gt_translate_keys":[{"key":"rendered","format":"text"}]},"content":{"rendered":"<p>Lors du traitement complexe de grands ensembles de donn\u00e9es (divers <noindex><a rel=\"nofollow\" href=\"https:\/\/ru.wikipedia.org\/wiki\/ETL\">processus ETL<\/a><\/noindex>: importations, conversions et synchronisations avec une source externe), il est souvent n\u00e9cessaire <b>de \u00ab m\u00e9moriser \u00bb temporairement et de traiter rapidement<\/b> quelque chose de volumineux.<\/p>\n<p>Une t\u00e2che typique de ce type est g\u00e9n\u00e9ralement formul\u00e9e comme suit : <i>\u00ab Voici ce que <noindex><a rel=\"nofollow\" href=\"https:\/\/sbis.ru\/accounting\">la comptabilit\u00e9 a extrait de la banque client<\/a><\/noindex> des derniers paiements re\u00e7us, il faut les t\u00e9l\u00e9charger rapidement sur le site et les lier aux comptes \u00bb<\/i><\/p>\n<p>Mais lorsque le volume de cette \u00ab chose \u00bb commence \u00e0 \u00eatre mesur\u00e9 en centaines de m\u00e9gaoctets, et que le service doit continuer \u00e0 fonctionner avec une base en mode 24\/7, de nombreux effets secondaires apparaissent qui vont vous compliquer la vie.<br \/>\n<img decoding=\"async\" alt=\"DBA : nous organisons efficacement les synchronisations et les importations.\" src=\"\/wp-content\/uploads\/2020\/03\/f74afb2cd6f5f8de26a0932166933c95.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nPour y faire face dans PostgreSQL (et pas seulement dans ce syst\u00e8me), il est possible d'utiliser certaines capacit\u00e9s d'optimisation qui permettront de traiter le tout plus rapidement et avec moins de ressources.<br \/>\n<noindex><a rel=\"nofollow\" name=\"habracut\"><\/a><\/noindex><\/p>\n<h2>1. O\u00f9 charger ?<\/h2>\n<p>\nCommen\u00e7ons par d\u00e9terminer o\u00f9 nous pouvons charger les donn\u00e9es que nous souhaitons \u00ab traiter \u00bb.<\/p>\n<h3>1.1. Tables temporaires (TEMPORARY TABLE)<\/h3>\n<p>\nEn principe, pour PostgreSQL, les tables temporaires sont comme toutes les autres tables. Par cons\u00e9quent, les superstitions du type <i><b>\u00ab l\u00e0, tout est stock\u00e9 uniquement en m\u00e9moire, et elle peut se remplir \u00bb<\/b><\/i>sont incorrectes. Mais il existe \u00e9galement quelques diff\u00e9rences essentielles.<\/p>\n<h4>Un \u00ab espace de noms \u00bb pour chaque connexion \u00e0 la BDD<\/h4>\n<p>\nSi deux connexions tentent d'ex\u00e9cuter simultan\u00e9ment <code>CREATE TABLE x<\/code>, alors quelqu'un recevra certainement <b>une erreur d'unicit\u00e9<\/b> des objets de la BDD.<\/p>\n<p>En revanche, si les deux tentent d'ex\u00e9cuter <code>CREATE <b>TEMPORAIRE<\/b> TABLE x<\/code>, alors les deux le feront normalement, et chacun recevra <b>sa propre instance<\/b> de la table. Et il n'y aura rien en commun entre elles.<\/p>\n<h4>\u00ab Auto-destruction \u00bb lors de la d\u00e9connexion<\/h4>\n<p>\nLors de la fermeture de la connexion, toutes les tables temporaires sont automatiquement supprim\u00e9es, donc il n'y a aucun sens \u00e0 ex\u00e9cuter <code>DROP TABLE x<\/code> , \u00e0 part\u2026<\/p>\n<p>Si vous travaillez via <b>pgbouncer en mode transactionnel<\/b>, alors la base continue de penser que cette connexion est toujours active, et cette table temporaire existe toujours dans celle-ci.<\/p>\n<p>Donc, essayer de la cr\u00e9er \u00e0 nouveau, d\u00e9j\u00e0 \u00e0 partir d'une autre connexion \u00e0 pgbouncer, entra\u00eenera une erreur. Mais cela peut \u00eatre contourn\u00e9 en utilisant <code>CR\u00c9ER UNE TABLE TEMPORAIRE <b>SI NON EXISSE<\/b> x<\/code>.<\/p>\n<p>Il est vrai que mieux vaut ne pas faire cela, car vous pourriez alors \u00ab d\u00e9couvrir par surprise \u00bb des donn\u00e9es laiss\u00e9es par le \u00ab pr\u00e9c\u00e9dent propri\u00e9taire \u00bb. Il est bien mieux de lire le manuel et de voir qu'il est possible d'ajouter des options lors de la cr\u00e9ation de la table. <code>EN REMPLACEMENT <b>DROP<\/b><\/code> \u2014 c'est-\u00e0-dire qu'\u00e0 la fin de la transaction, la table sera automatiquement supprim\u00e9e.<\/p>\n<h4>Non-r\u00e9plication<\/h4>\n<p>\nEn raison de son appartenance \u00e0 une seule connexion, les tables temporaires ne sont pas r\u00e9pliqu\u00e9es. Mais cela <b>\u00e9limine le besoin d'\u00e9crire les donn\u00e9es deux fois<\/b> dans le heap + WAL, rendant ainsi les INSERT\/UPDATE\/DELETE beaucoup plus rapides.<\/p>\n<p>Mais puisque la temporaire est tout de m\u00eame une \u00ab presque ordinaire \u00bb table, il n\u2019est pas possible de la cr\u00e9er sur la r\u00e9plique non plus. Du moins, pas pour le moment, bien qu'un patch correspondant circule depuis longtemps.<\/p>\n<h3>1.2. Tables non journalis\u00e9es (UNLOGGED TABLE)<\/h3>\n<p>\nMais que faire, par exemple, si vous avez un processus ETL lourd, qui ne peut pas \u00eatre r\u00e9alis\u00e9 dans le cadre d'une seule transaction, et que vous avez quand m\u00eame <b>pgbouncer en mode transactionnel<\/b>?..<\/p>\n<p>Ou si le flux de donn\u00e9es est si important que <b>la bande passante d'une seule connexion<\/b> avec la base de donn\u00e9es (lire, un processus sur le CPU) n'est pas suffisante ?..<\/p>\n<p>Ou certaines op\u00e9rations se d\u00e9roulent <b>de mani\u00e8re asynchrone<\/b> dans diff\u00e9rentes connexions ?..<\/p>\n<p>Il n'y a qu'une seule option ici \u2014 <b>cr\u00e9er temporairement une table non temporaire<\/b>. Un jeu de mots, n'est-ce pas. En d'autres termes :<\/p>\n<ul>\n<li>j'ai cr\u00e9\u00e9 \u00ab mes \u00bb tables avec des noms aussi al\u00e9atoires que possible, pour ne pas croiser celles des autres<\/li>\n<li><b>Extraire<\/b>: j'y ai charg\u00e9 des donn\u00e9es provenant d'une source externe<\/li>\n<li><b>Transformer<\/b>: j'ai trait\u00e9, rempli les champs de liaison cl\u00e9s<\/li>\n<li><b>Charge<\/b>: j'ai transf\u00e9r\u00e9 les donn\u00e9es pr\u00eates dans les tables cibles<\/li>\n<li>j'ai supprim\u00e9 \u00ab mes \u00bb tables<\/li>\n<\/ul>\n<p>\nEt maintenant \u2014 une mauvaise nouvelle. En essence, <b>toute \u00e9criture dans PostgreSQL se produit deux fois<\/b> \u2014 <noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/company\/postgrespro\/blog\/461523\/\">d'abord dans le WAL<\/a><\/noindex>, puis dans les corps des tables\/indices. Tout cela est fait pour prendre en charge ACID et garantir la visibilit\u00e9 correcte des donn\u00e9es entre <code>COMMIT<\/code>\u2018int\u00e9gr\u00e9s et <code>ROLLBACK<\/code>\u2018int\u00e9gr\u00e9s dans les transactions.<\/p>\n<p>Mais nous n'avons pas besoin de cela ! Tout notre processus <b>a soit enti\u00e8rement r\u00e9ussi, soit \u00e9chou\u00e9.<\/b>. Peu importe combien de transactions interm\u00e9diaires il y aura \u2014 nous ne sommes pas int\u00e9ress\u00e9s par \u00ab continuer le processus \u00e0 partir du milieu \u00bb, surtout quand il est difficile de savoir o\u00f9 il en \u00e9tait.<\/p>\n<p>Pour cela, les d\u00e9veloppeurs de PostgreSQL ont introduit d\u00e8s la version 9.1 quelque chose appel\u00e9 <noindex><a rel=\"nofollow\" href=\"https:\/\/postgrespro.ru\/docs\/postgresql\/12\/sql-createtable#SQL-CREATETABLE-UNLOGGED\">tables non journalis\u00e9es (UNLOGGED).<\/a><\/noindex>:<\/p>\n<blockquote><p>Avec cette option, la table est cr\u00e9\u00e9e comme non journalis\u00e9e. Les donn\u00e9es \u00e9crites dans des tables non journalis\u00e9es ne passent pas par le journal de pr\u00e9-\u00e9criture (cf. Chapitre 29), ce qui fait que ces tables <b>fonctionnent beaucoup plus rapidement que les normales.<\/b>Cependant, elles ne sont pas prot\u00e9g\u00e9es contre les pannes ; en cas de panne ou de coupure accidentelle du serveur, la table non journalis\u00e9e <b>est automatiquement tronqu\u00e9e.<\/b>De plus, le contenu d'une table non journalis\u00e9e <b>n'est pas r\u00e9pliqu\u00e9.<\/b> sur des serveurs responsables. Tous les index cr\u00e9\u00e9s pour une table non journalis\u00e9e deviennent automatiquement non journalis\u00e9s.<\/p><\/blockquote>\n<p>En r\u00e9sum\u00e9, <b>cela sera beaucoup plus rapide<\/b>, mais si le serveur de base de donn\u00e9es \u00ab tombe \u00bb - cela peut \u00eatre d\u00e9sagr\u00e9able. Mais cela arrive-t-il souvent, et votre processus ETL sait-il le g\u00e9rer correctement \u00ab \u00e0 partir du milieu \u00bb apr\u00e8s le \u00ab r\u00e9veil \u00bb de la base de donn\u00e9es ?..<\/p>\n<p>Si ce n'est pas le cas et que le cas ci-dessus ressemble au v\u00f4tre - utilisez <code>UNLOGGED<\/code>, mais ne jamais <b>activer cet attribut sur des tables r\u00e9elles<\/b>, dont les donn\u00e9es vous sont pr\u00e9cieuses.<\/p>\n<h3>1.3. ON COMMIT { DELETE ROWS | DROP }<\/h3>\n<p>\nCette construction permet de d\u00e9finir un comportement automatique lors de la fin de la transaction lors de la cr\u00e9ation de la table.<\/p>\n<p>\u00c0 propos de <code>EN REMPLACEMENT <b>DROP<\/b><\/code> comme je l'ai d\u00e9j\u00e0 mentionn\u00e9, elle g\u00e9n\u00e8re <code>DROP TABLE<\/code>, mais ici avec <code>EN REMPLACEMENT <b>SUPPRIMER DES LIGNES<\/b><\/code> la situation est plus int\u00e9ressante - ici cela g\u00e9n\u00e8re <code>TRUNCATE TABLE<\/code>.<\/p>\n<p>\u00c9tant donn\u00e9 que toute l'infrastructure de stockage de la m\u00e9ta-description de la table temporaire est exactement la m\u00eame que celle de la table normale, <b>la cr\u00e9ation et la suppression constantes de tables temporaires entra\u00eenent un important \u00ab gonflement \u00bb des tables syst\u00e8me<\/b> pg_class, pg_attribute, pg_attrdef, pg_depend,\u2026<\/p>\n<p>Maintenant imaginez que vous avez un worker avec une connexion directe \u00e0 la base de donn\u00e9es, qui ouvre une nouvelle transaction chaque seconde, cr\u00e9e, remplit, traite et supprime une table temporaire\u2026 Des d\u00e9chets s'accumuleront en exc\u00e8s dans les tables syst\u00e8me, ce qui provoquera des ralentissements suppl\u00e9mentaires \u00e0 chaque op\u00e9ration.<\/p>\n<p>En g\u00e9n\u00e9ral, ne faites pas cela ! Dans ce cas, il est beaucoup plus efficace <code>CREATE TEMPORARY TABLE x ... ON COMMIT DELETE ROWS<\/code> de le faire en dehors de la boucle des transactions - alors au d\u00e9but de chaque nouvelle transaction, les tables existeront d\u00e9j\u00e0 <b>existera<\/b> (\u00e9conomisons l'appel <code>CREATE<\/code>), mais <b>elle sera vide<\/b>, gr\u00e2ce \u00e0 <code>TRUNCATE<\/code> (nous avons aussi \u00e9conomis\u00e9 son appel) \u00e0 la fin de la transaction pr\u00e9c\u00e9dente.<\/p>\n<h3>1.4. COMME\u2026 Y COMPRIS \u2026<\/h3>\n<p>\nJe l'ai mentionn\u00e9 au d\u00e9but, l'un des cas d'utilisation typiques pour les tables temporaires est les diff\u00e9rents types d'importations - et le d\u00e9veloppeur copie et colle fatigu\u00e9 la liste des champs de la table cible dans la d\u00e9claration de sa table temporaire\u2026<\/p>\n<p>Mais la paresse est le moteur du progr\u00e8s ! Donc <b>vous pouvez cr\u00e9er une nouvelle table \u00ab \u00e0 partir d'un mod\u00e8le \u00bb<\/b> beaucoup plus facilement :<\/p>\n<pre><code class=\"sql\">CREATE TEMPORARY TABLE import_table(\n  LIKE target_table\n);<\/code><\/pre>\n<p>\n\u00c9tant donn\u00e9 que beaucoup de donn\u00e9es peuvent ensuite \u00eatre g\u00e9n\u00e9r\u00e9es dans cette table, les recherches \u00e0 son sujet ne seront absolument pas rapides. Mais il y a une solution traditionnelle \u00e0 cela - les index ! Et, oui, <b>une table temporaire peut aussi avoir des index<\/b>.<\/p>\n<p>Comme souvent, les index n\u00e9cessaires co\u00efncident avec les index de la table cible, il suffit d'\u00e9crire <code>LIKE target_table <b>Y COMPRIS INDEXES<\/b><\/code>.<\/p>\n<p>Si vous avez aussi besoin de <code>DEFAULT<\/code>- valeurs (par exemple, pour remplir les valeurs de la cl\u00e9 primaire), vous pouvez utiliser <code>LIKE target_table <b>Y COMPRIS LES D\u00c9FAUTS<\/b><\/code>. Ou simplement \u2014 <code>LIKE target_table <b>Y COMPRIS TOUT<\/b><\/code> \u2014 copiera les valeurs par d\u00e9faut, les index, les contraintes,\u2026<\/p>\n<p>Mais ici, il faut d\u00e9j\u00e0 comprendre que si vous avez cr\u00e9\u00e9 <b>une table d'importation directement avec des index, le chargement des donn\u00e9es prendra plus de temps<\/b>, que si vous chargez d'abord toutes les donn\u00e9es, puis appliquez les index \u2014 regardez par exemple comment cela fait <noindex><a rel=\"nofollow\" href=\"https:\/\/postgrespro.ru\/docs\/postgresql\/12\/app-pgdump\">pg_dump<\/a><\/noindex>.<\/p>\n<p>En g\u00e9n\u00e9ral, <noindex><a rel=\"nofollow\" href=\"https:\/\/postgrespro.ru\/docs\/postgresql\/12\/sql-createtable\">RTFM<\/a><\/noindex>!<\/p>\n<h2>2. Comment \u00e9crire ?<\/h2>\n<p>\nJe vais dire simplement \u2014 utilisez <code><noindex><a rel=\"nofollow\" href=\"https:\/\/postgrespro.ru\/docs\/postgresql\/12\/sql-copy\">COPY<\/a><\/noindex><\/code>- flux au lieu de 'paquet' <code>INSERT<\/code>, <noindex><a rel=\"nofollow\" href=\"https:\/\/www.citusdata.com\/blog\/2017\/11\/08\/faster-bulk-loading-in-postgresql-with-copy\/\">acc\u00e9l\u00e9ration par centaines<\/a><\/noindex>. Vous pouvez m\u00eame le faire directement \u00e0 partir d'un fichier pr\u00e9alablement pr\u00e9par\u00e9.<\/p>\n<h2>3. Comment traiter ?<\/h2>\n<p>\nAinsi, supposons que notre situation de d\u00e9part ressemble \u00e0 peu pr\u00e8s \u00e0 cela :<\/p>\n<ul>\n<li>vous avez une table dans la base contenant les donn\u00e9es clients avec <b>1M d'enregistrements<\/b><\/li>\n<li>chaque jour le client vous envoie un nouveau <b>image compl\u00e8te<\/b><\/li>\n<li>d'apr\u00e8s l'exp\u00e9rience, vous savez que, d'une fois \u00e0 l'autre, <b>il ne change pas plus de 10K enregistrements<\/b><\/li>\n<\/ul>\n<p>\nUn exemple classique de cette situation est <noindex><a rel=\"nofollow\" href=\"https:\/\/www.gnivc.ru\/technical_support\/classifiers_reference\/kladr\/\">la base KLDAR<\/a><\/noindex> \u2014 il y a beaucoup d'adresses, mais dans chaque extraction hebdomadaire de changements (renommage de localit\u00e9s, fusion de rues, apparition de nouveaux b\u00e2timents), il y a tr\u00e8s peu de changements m\u00eame \u00e0 l'\u00e9chelle du pays.<\/p>\n<h3>3.1. Algorithme de synchronisation compl\u00e8te<\/h3>\n<p>\nPour simplifier, supposons que vous n'avez m\u00eame pas besoin de restructurer les donn\u00e9es \u2014 il suffit de mettre la table dans le format requis, c'est-\u00e0-dire :<\/p>\n<ul>\n<li><b>\u00e0 supprimer<\/b> tout ce qui n'existe plus<\/li>\n<li><b>Les options pour les processeurs \u00abElbrus\u00bb sont disponibles sur<\/b> tout ce qui existait d\u00e9j\u00e0 et doit \u00eatre mis \u00e0 jour<\/li>\n<li><b>ins\u00e9rer<\/b> tout ce qui n'existait pas encore<\/li>\n<\/ul>\n<p>\nPourquoi effectuer les op\u00e9rations dans cet ordre ? Parce que c'est pr\u00e9cis\u00e9ment ainsi que la taille de la table augmentera au minimum (<noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/company\/tensor\/blog\/491366\/\">n'oublie pas le MVCC !<\/a><\/noindex>).<\/p>\n<h4>DELETE FROM dst<\/h4>\n<p>\nNon, bien s\u00fbr, il est possible de se contenter de deux op\u00e9rations uniquement :<\/p>\n<ul>\n<li><b>\u00e0 supprimer<\/b> (<code>SUPPRIMER<\/code>) la table enti\u00e8re<\/li>\n<li><b>ins\u00e9rer<\/b> tout du nouvel ensemble d'enregistrements<\/li>\n<\/ul>\n<p>\nMais en m\u00eame temps, gr\u00e2ce au MVCC, <b>la taille de la table doublera exactement<\/b>! Obtenir +1M d'images d'enregistrements dans la table \u00e0 cause de la mise \u00e0 jour de 10K \u2014 ce n'est pas la redondance id\u00e9ale\u2026<\/p>\n<h4>TRUNCATE dst<\/h4>\n<p>\nUn d\u00e9veloppeur plus exp\u00e9riment\u00e9 sait qu'il est possible de nettoyer toute la table \u00e0 un co\u00fbt raisonnable :<\/p>\n<ul>\n<li><b>effacer<\/b> (<code>TRUNCATE<\/code>) la table enti\u00e8re<\/li>\n<li><b>ins\u00e9rer<\/b> tout du nouvel ensemble d'enregistrements<\/li>\n<\/ul>\n<p>\nM\u00e9thode efficace, <noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/company\/tensor\/blog\/481866\/\">parfois tout \u00e0 fait applicable<\/a><\/noindex>, mais il y a un probl\u00e8me\u2026 Nous allons ins\u00e9rer 1M d'enregistrements pendant un long moment, et donc nous ne pouvons pas nous permettre de garder la table vide tout ce temps (comme cela se produira sans envelopper le tout dans une seule transaction).<\/p>\n<p>Cela signifie que :<\/p>\n<ul>\n<li>nous commen\u00e7ons <b>une transaction longue<\/b><\/li>\n<li><code>TRUNCATE<\/code> impose <b>une<\/b>- verrouillage<\/li>\n<li>nous ins\u00e9rons longtemps, et tous les autres pendant ce temps <b>ne peuvent m\u00eame pas <code>SELECT<\/code><\/b><\/li>\n<\/ul>\n<p>\nCe n'est pas bon\u2026<\/p>\n<h4>ALTER TABLE\u2026 RENUMMER\u2026 \/ DROPPER TABLE \u2026<\/h4>\n<p>\nUne possibilit\u00e9 serait de tout placer dans une nouvelle table distincte, puis de la renommer \u00e0 la place de l'ancienne. Quelques petits points d\u00e9sagr\u00e9ables :<\/p>\n<ul>\n<li>c'est aussi vrai <b>une<\/b>, m\u00eame si cela prend beaucoup moins de temps<\/li>\n<li>tous les plans de requ\u00eates\/statistiques de cette table sont r\u00e9initialis\u00e9s, <noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/company\/tensor\/blog\/479656\/\">il faut ex\u00e9cuter ANALYZE<\/a><\/noindex><\/li>\n<li><b>toutes les cl\u00e9s \u00e9trang\u00e8res (FK) sur la table sont cass\u00e9es<\/b> (FK) sur la table<\/li>\n<\/ul>\n<p>\nIl y avait un patch WIP de Simon Riggs qui proposait de faire <code>ALTER<\/code>-l'op\u00e9ration pour remplacer le corps de la table au niveau des fichiers, sans toucher aux statistiques et aux FK, mais il n'a pas rassembl\u00e9 le quorum.<\/p>\n<h4>DELETE, UPDATE, INSERT<\/h4>\n<p>\nAinsi, nous nous arr\u00eatons sur l'option non bloquante parmi les trois op\u00e9rations. Presque trois\u2026 Comment proc\u00e9der de la mani\u00e8re la plus efficace ?<\/p>\n<pre><code class=\"sql\">-- tout se fait dans le cadre de la transaction, pour que personne ne voie les \"\u00e9tats interm\u00e9diaires\"\nBEGIN;\n\n-- cr\u00e9er une table temporaire avec les donn\u00e9es import\u00e9es\nCREATE TEMPORARY TABLE tmp(\n  LIKE dst INCLUDING INDEXES -- comme un mod\u00e8le, avec les index\n) ON COMMIT DROP; -- en dehors de la transaction, elle ne nous est pas n\u00e9cessaire\n\n-- ins\u00e9rer rapidement le nouveau mod\u00e8le via COPY\nCOPY tmp FROM STDIN;\n-- ...\n-- .\n\n-- supprimer les absents\nDELETE FROM\n  dst D\nUSING\n  dst X\nLEFT JOIN\n  tmp Y\n    USING(pk1, pk2) -- champs de la cl\u00e9 primaire\nWHERE\n  (D.pk1, D.pk2) = (X.pk1, X.pk2) AND\n  Y IS NOT DISTINCT FROM NULL; -- \"anti-join\"\n\n-- mettre \u00e0 jour les restants\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) IS DISTINCT FROM (T.f1, T.f2, T.f3); -- pas besoin de mettre \u00e0 jour les correspondances\n\n-- ins\u00e9rer les absents\nINSERT INTO\n  dst\nSELECT\n  T.*\nFROM\n  tmp T\nLEFT JOIN\n  dst D\n    USING(pk1, pk2)\nWHERE\n  D IS NOT DISTINCT FROM NULL;\n\nCOMMIT;\n<\/code><\/pre>\n<p><\/p>\n<h3>3.2. Post-traitement de l'import<\/h3>\n<p>\nDans le m\u00eame KAD, toutes les empreintes modifi\u00e9es doivent \u00eatre \u00e9galement soumises \u00e0 un post-traitement \u2014 normaliser, extraire les mots-cl\u00e9s, les amener aux structures requises. Mais comment savoir \u2014 <b>quelles modifications ont \u00e9t\u00e9 apport\u00e9es<\/b>, sans compliquer le code de synchronisation, id\u00e9alement, sans m\u00eame y toucher ?<\/p>\n<p>S'il n'y a qu'un seul processus ayant acc\u00e8s en \u00e9criture au moment de la synchronisation, il est possible d'utiliser un d\u00e9clencheur qui recueillera toutes les modifications pour nous :<\/p>\n<pre><code class=\"sql\">-- tables cibles\nCREATE TABLE kladr(...);\nCREATE TABLE kladr_house(...);\n\n-- tables avec l'historique des modifications\nCREATE TABLE kladr$log(\n  ro kladr, -- ici se trouvent les enregistrements anciens\/nouveaux\n  rn kladr\n);\n\nCREATE TABLE kladr_house$log(\n  ro kladr_house,\n  rn kladr_house\n);\n\n-- fonction g\u00e9n\u00e9rale de journalisation des modifications\nCREATE OR REPLACE FUNCTION diff$log() RETURNS trigger AS $$\nDECLARE\n  dst varchar = TG_TABLE_NAME || '$log';\n  stmt text = '';\nBEGIN\n  -- v\u00e9rifie la n\u00e9cessit\u00e9 de journaliser lors de la mise \u00e0 jour d'un enregistrement\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  -- cr\u00e9e un enregistrement de journal\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>\nNous pouvons maintenant appliquer (ou activer via le d\u00e9but de la synchronisation) les d\u00e9clencheurs : <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>\nEt ensuite, nous extrayons tranquillement tous les changements n\u00e9cessaires des tables de log et les passons par des gestionnaires suppl\u00e9mentaires.<\/p>\n<h3>3.3. Importation de jeux de donn\u00e9es associ\u00e9s<\/h3>\n<p>\nNous avons examin\u00e9 ci-dessus les cas o\u00f9 les structures de donn\u00e9es de la source et du destinataire sont identiques. Mais que faire si l'export d'un syst\u00e8me externe a un format diff\u00e9rent de la structure de stockage dans notre base ?<\/p>\n<p>Prenons l'exemple du stockage des clients et de leurs factures, un cas classique de \u00ab plusieurs \u00e0 un \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>\nEt voici que l'export d'une source externe nous arrivera sous la forme \u00ab tout-en-un \u00bb :<\/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>\nIl est \u00e9vident que les donn\u00e9es des clients peuvent \u00eatre dupliqu\u00e9es dans ce cas, et l'enregistrement principal est la \u00ab facture \u00bb :<\/p>\n<pre><code class=\"plaintext\">0123456789;Vassia;A-01;2020-03-16;1000.00\n9876543210;Petia;A-02;2020-03-16;666.00\n0123456789;Vassia;B-03;2020-03-16;9999.00\n<\/code><\/pre>\n<p>\nPour le mod\u00e8le, nous allons simplement ins\u00e9rer nos donn\u00e9es de test, mais gardons \u00e0 l'esprit \u2014 <code>COPY<\/code> plus efficace !<\/p>\n<pre><code class=\"sql\">INSERT INTO invoice_import\nVALUES\n  ('0123456789', 'Vassia', 'A-01', '2020-03-16', 1000.00)\n, ('9876543210', 'Petia', 'A-02', '2020-03-16', 666.00)\n, ('0123456789', 'Vassia', 'B-03', '2020-03-16', 9999.00);<\/code><\/pre>\n<p>\nCommen\u00e7ons par identifier les \u00ab segments \u00bb auxquels nos \u00ab faits \u00bb se r\u00e9f\u00e8rent. Dans notre cas, les factures font r\u00e9f\u00e9rence aux clients :<\/p>\n<pre><code class=\"sql\">CREATE TEMPORARY TABLE client_import AS\nSELECT DISTINCT ON(client_inn)\n-- on peut simplement utiliser SELECT DISTINCT si les donn\u00e9es sont a priori non contradictoires\n  client_inn inn\n, client_name \"name\"\nFROM\n  invoice_import;<\/code><\/pre>\n<p>\nPour lier correctement les factures aux ID des clients, nous devons d'abord conna\u00eetre ou g\u00e9n\u00e9rer ces identifiants. Ajoutons des champs pour cela :<\/p>\n<pre><code class=\"sql\">ALTER TABLE invoice_import AJOUTER UNE COLONNE client_id entier;\nALTER TABLE client_import AJOUTER UNE COLONNE client_id entier;<\/code><\/pre>\n<p>\nNous allons utiliser la m\u00e9thode de synchronisation des tables d\u00e9crite ci-dessus avec un l\u00e9ger ajustement : nous ne mettrons rien \u00e0 jour ni ne supprimerons dans la table cible, car l'import des clients est \u00ab append-only \u00bb :<\/p>\n<pre><code class=\"sql\">-- attribuons dans la table d'importation les ID des enregistrements d\u00e9j\u00e0 existants\nMise \u00e0 jour\n  client_import T\nR\u00c9GLEZ\n  client_id = D.client_id\nDE\n  client D\nO\u00d9\n  T.inn = D.inn; -- cl\u00e9 unique\n\n-- ins\u00e9rons les enregistrements manquants et attribuons leurs ID\nAVEC ins AS (\n  INS\u00c9RER DANS client(\n    inn\n  , nom\n  )\n  S\u00c9LECTIONNER\n    inn\n  , nom\n  DE\n    client_import\n  O\u00d9\n    client_id EST NULL -- si l'ID n'a pas \u00e9t\u00e9 attribu\u00e9\n  RETOURNANT *\n)\nMise \u00e0 jour\n  client_import T\nR\u00c9GLEZ\n  client_id = D.client_id\nDE\n  ins D\nO\u00d9\n  T.inn = D.inn; -- cl\u00e9 unique\n\n-- attribuons les ID des clients aux enregistrements des factures\nMise \u00e0 jour\n  invoice_import T\nR\u00c9GLEZ\n  client_id = D.client_id\nDE\n  client_import D\nO\u00d9\n  T.client_inn = D.inn; -- cl\u00e9 fonctionnelle\n<\/code><\/pre>\n<p>\nTout est fait \u2014 dans <code>invoice_import<\/code> nous avons maintenant rempli le champ de liaison <code>client_id<\/code>, avec lequel nous allons ins\u00e9rer la facture.<br \/>\n<br \/>Source : <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.2 - 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\/fr\/blog\/administrirovanie\/dba-gramotno-organizovyvaem-sinhronizaczii-i-importy\" \/>\n\t<meta name=\"generator\" content=\"All in One SEO (AIOSEO) 5.0.2\" \/>\n\t\t<meta property=\"og:locale\" content=\"fr_FR\" \/>\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\/fr\/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 : organisons judicieusement les synchronisations et les importations | ProHoster","description":"Lors du traitement complexe de grands ensembles de donn\u00e9es (diff\u00e9rents processus ETL : importations, conversions et synchronisations avec des sources externes) souvent.","canonical_url":"https:\/\/prohoster.info\/fr\/blog\/administrirovanie\/dba-gramotno-organizovyvaem-sinhronizaczii-i-importy","robots":"max-image-preview:large","keywords":"","webmasterTools":{"miscellaneous":""},"schema":null,"og:locale":"fr_FR","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\/fr\/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\/fr\/wp-json\/wp\/v2\/posts\/74953","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/prohoster.info\/fr\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/prohoster.info\/fr\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/prohoster.info\/fr\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/prohoster.info\/fr\/wp-json\/wp\/v2\/comments?post=74953"}],"version-history":[{"count":0,"href":"https:\/\/prohoster.info\/fr\/wp-json\/wp\/v2\/posts\/74953\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/prohoster.info\/fr\/wp-json\/wp\/v2\/media\/74954"}],"wp:attachment":[{"href":"https:\/\/prohoster.info\/fr\/wp-json\/wp\/v2\/media?parent=74953"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/prohoster.info\/fr\/wp-json\/wp\/v2\/categories?post=74953"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/prohoster.info\/fr\/wp-json\/wp\/v2\/tags?post=74953"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}