{"id":37956,"date":"2019-10-31T22:20:47","date_gmt":"2019-10-31T19:20:47","guid":{"rendered":"https:\/\/prohoster.info\/blog\/newsql-nosql-acid\/"},"modified":"2019-10-31T22:20:47","modified_gmt":"2019-10-31T19:20:47","slug":"newsql-nosql-acid","status":"publish","type":"post","link":"https:\/\/prohoster.info\/fr\/blog\/administrirovanie\/newsql-nosql-acid","title":{"rendered":"NewSQL = NoSQL+ACID","gt_translate_keys":[{"key":"rendered","format":"text"}]},"content":{"rendered":"<p><img decoding=\"async\" alt=\"NewSQL = NoSQL+ACID\" src=\"\/wp-content\/uploads\/2019\/09\/973145efa566bca10ffe7ba95733a1d5.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nJusqu'\u00e0 r\u00e9cemment, Odnoklassniki avait environ 50 To de donn\u00e9es trait\u00e9es en temps r\u00e9el stock\u00e9es dans SQL Server. Pour un tel volume, assurer un acc\u00e8s rapide, fiable et r\u00e9silient aux pannes via un datacenter en utilisant une base de donn\u00e9es SQL est pratiquement impossible. Dans de tels cas, l'une des solutions NoSQL est g\u00e9n\u00e9ralement utilis\u00e9e, mais tout ne peut pas \u00eatre transf\u00e9r\u00e9 vers NoSQL : certaines entit\u00e9s n\u00e9cessitent des garanties de transactions ACID. <\/p>\n<p>Cela nous a amen\u00e9s \u00e0 utiliser un stockage NewSQL, c'est-\u00e0-dire une base de donn\u00e9es offrant la r\u00e9silience, la scalabilit\u00e9 et la rapidit\u00e9 des syst\u00e8mes NoSQL, tout en conservant les garanties ACID famili\u00e8res des syst\u00e8mes classiques. Les syst\u00e8mes industriels en fonctionnement de cette nouvelle classe sont peu nombreux, c'est pourquoi nous en avons r\u00e9alis\u00e9 un nous-m\u00eames et l'avons mis en production. <\/p>\n<p>Comment cela fonctionne et quel en a \u00e9t\u00e9 le r\u00e9sultat \u2014 lis la suite.<br \/>\n<noindex><a rel=\"nofollow\" name=\"habracut\"><\/a><\/noindex><br \/>\nAujourd'hui, l'audience mensuelle d'Odnoklassniki d\u00e9passe 70 millions de visiteurs uniques. Nous <noindex><a rel=\"nofollow\" href=\"https:\/\/www.similarweb.com\/top-websites\/category\/internet-and-telecom\/social-network\">sommes dans le top cinq<\/a><\/noindex> des plus grands r\u00e9seaux sociaux du monde, et parmi les vingt sites sur lesquels les utilisateurs passent le plus de temps. L'infrastructure d'OK g\u00e8re des charges tr\u00e8s \u00e9lev\u00e9es : plus d'un million de requ\u00eates HTTP\/seconde en front-end. Des parties du parc de serveurs, comptant plus de 8000 unit\u00e9s, sont situ\u00e9es \u00e0 proximit\u00e9 les unes des autres \u2014 dans quatre centres de donn\u00e9es \u00e0 Moscou, ce qui permet d'assurer une latence r\u00e9seau inf\u00e9rieure \u00e0 1 ms entre eux.<\/p>\n<p>Nous utilisons Cassandra depuis 2010, \u00e0 partir de la version 0.6. Aujourd'hui, plusieurs dizaines de clusters sont en exploitation. Le cluster le plus rapide traite plus de 4 millions d'op\u00e9rations par seconde, tandis que le plus important stocke 260 To. <\/p>\n<p>Cependant, tout cela repr\u00e9sente des clusters NoSQL ordinaires, utilis\u00e9s pour stocker <noindex><a rel=\"nofollow\" href=\"https:\/\/ru.wikipedia.org\/wiki\/%D0%A1%D0%BE%D0%B3%D0%BB%D0%B0%D1%81%D0%BE%D0%B2%D0%B0%D0%BD%D0%BD%D0%BE%D1%81%D1%82%D1%8C_%D0%B2_%D0%BA%D0%BE%D0%BD%D0%B5%D1%87%D0%BD%D0%BE%D0%BC_%D1%81%D1%87%D1%91%D1%82%D0%B5\">des donn\u00e9es faiblement coh\u00e9rentes.<\/a><\/noindex> Nous souhaitions remplacer le stockage principal coh\u00e9rent, Microsoft SQL Server, utilis\u00e9 depuis le lancement d'Odnoklassniki. Le stockage \u00e9tait compos\u00e9 de plus de 300 machines SQL Server Standard Edition, contenant 50 To de donn\u00e9es \u2014 des entit\u00e9s commerciales. Ces donn\u00e9es sont modifi\u00e9es dans le cadre de transactions ACID et n\u00e9cessitent <noindex><a rel=\"nofollow\" href=\"https:\/\/ru.wikipedia.org\/wiki\/ACID\">une haute coh\u00e9rence.<\/a><\/noindex>.<\/p>\n<p>Pour la r\u00e9partition des donn\u00e9es sur les n\u0153uds SQL Server, nous avons utilis\u00e9 tant du partitionnement vertical qu'horizontal. <noindex><a rel=\"nofollow\" href=\"https:\/\/en.wikipedia.org\/wiki\/Partition_(database)\">partitionnement<\/a><\/noindex> (sharding). Historiquement, nous avons utilis\u00e9 un sch\u00e9ma de sharding des donn\u00e9es simple : chaque entit\u00e9 se voit attribuer un jeton \u2014 une fonction de l'ID de l'entit\u00e9. Les entit\u00e9s partageant le m\u00eame jeton \u00e9taient plac\u00e9es sur un seul serveur SQL. La relation de type master-detail \u00e9tait mise en \u0153uvre de mani\u00e8re \u00e0 ce que les jetons de l'enregistrement principal et de l'enregistrement d\u00e9riv\u00e9 co\u00efncident toujours et soient pr\u00e9sents sur un seul serveur. Dans un r\u00e9seau social, presque tous les enregistrements sont g\u00e9n\u00e9r\u00e9s au nom d'un utilisateur \u2014 ce qui signifie que toutes les donn\u00e9es de l'utilisateur, au sein d'un m\u00eame sous-syst\u00e8me fonctionnel, sont stock\u00e9es sur un seul serveur. Ainsi, dans une transaction commerciale, presque toutes les tables appartenaient \u00e0 un seul serveur SQL, ce qui permettait d'assurer la coh\u00e9rence des donn\u00e9es gr\u00e2ce \u00e0 des transactions ACID locales, sans n\u00e9cessit\u00e9 d'utiliser <noindex><a rel=\"nofollow\" href=\"https:\/\/en.wikipedia.org\/wiki\/Two-phase_commit_protocol\">des transactions ACID distribu\u00e9es lentes et peu fiables.<\/a><\/noindex> . Gr\u00e2ce au sharding et pour am\u00e9liorer les performances SQL :<\/p>\n<p>Nous n'utilisons pas de contraintes de cl\u00e9 \u00e9trang\u00e8re, car, lors du sharding, l'ID de l'entit\u00e9 peut se trouver sur un autre serveur.<\/p>\n<ul>\n<li>Nous n'utilisons pas de proc\u00e9dures stock\u00e9es ou de d\u00e9clencheurs en raison de la charge suppl\u00e9mentaire sur le CPU du SGBD.<\/li>\n<li>Nous n'utilisons pas de JOINs en raison de tout ce qui pr\u00e9c\u00e8de et de la multitude de lectures al\u00e9atoires depuis le disque.<\/li>\n<li>En dehors des transactions, pour r\u00e9duire les blocages, nous utilisons le niveau d'isolation Read Uncommitted.<\/li>\n<li>Nous effectuons uniquement des transactions courtes (en moyenne moins de 100 ms).<\/li>\n<li>Nous n'utilisons pas de mises \u00e0 jour et de suppressions multi-lignes en raison du grand nombre de blocages \u2014 nous mettons \u00e0 jour un seul enregistrement \u00e0 la fois.<\/li>\n<li>Les requ\u00eates sont toujours ex\u00e9cut\u00e9es uniquement par index \u2014 une requ\u00eate avec un plan de lecture compl\u00e8te de la table pour nous signifie une surcharge de la base de donn\u00e9es et son \u00e9chec.<\/li>\n<li>Ces \u00e9tapes ont permis de tirer presque le maximum de performance des serveurs SQL. Cependant, les probl\u00e8mes augmentaient de plus en plus. Examinons-les.<\/li>\n<\/ul>\n<p>\nProbl\u00e8mes avec SQL<\/p>\n<h2>\u00c9tant donn\u00e9 que nous utilisions un sharding fait maison, l'ajout de nouveaux shards \u00e9tait effectu\u00e9 manuellement par les administrateurs. Pendant tout ce temps, les r\u00e9pliques de donn\u00e9es \u00e9volutives ne traitaient pas de requ\u00eates.<\/h2>\n<p><\/p>\n<ul>\n<li>\u00c0 mesure que le nombre d'enregistrements dans une table augmente, la vitesse d'insertion et de modification diminue, et l'ajout d'index \u00e0 une table existante provoque une chute exponentielle de la vitesse, la cr\u00e9ation et la recr\u00e9ation d'index se fait avec des temps d'arr\u00eat. <\/li>\n<li>La pr\u00e9sence en production d'un petit nombre de Windows pour SQL Server complique la gestion de l'infrastructure.<\/li>\n<li>Mais le principal probl\u00e8me \u2014<\/li>\n<\/ul>\n<p>\nMais le principal probl\u00e8me est \u2014 <\/p>\n<h2>R\u00e9silience<\/h2>\n<p>\nUn serveur SQL classique pr\u00e9sente une mauvaise r\u00e9silience. Supposons que vous n'ayez qu'un seul serveur de base de donn\u00e9es, et qu'il tombe en panne une fois tous les trois ans. Pendant ce temps, le site ne fonctionne pas pendant 20 minutes, ce qui est acceptable. Si vous avez 64 serveurs, le site est hors service une fois toutes les trois semaines. Et si vous avez 200 serveurs, le site ne fonctionne pas chaque semaine. C'est un probl\u00e8me. <\/p>\n<p>Que peut-on faire pour am\u00e9liorer la r\u00e9silience des serveurs SQL ? Wikip\u00e9dia nous sugg\u00e8re de construire un <noindex><a rel=\"nofollow\" href=\"https:\/\/en.wikipedia.org\/wiki\/High-availability_cluster\">cluster \u00e0 haute disponibilit\u00e9<\/a><\/noindex>: o\u00f9, en cas de d\u00e9faillance de l'un des composants, il y a un duplicata.<\/p>\n<p>Cela n\u00e9cessite un parc de mat\u00e9riel co\u00fbteux : de nombreuses redondances, de la fibre optique, des syst\u00e8mes de stockage partag\u00e9s, et de plus, l'activation de la redondance fonctionne de mani\u00e8re peu fiable : environ 10 % des activations se terminent par l'\u00e9chec de la n\u0153ud de secours, entra\u00eenant le n\u0153ud principal. <\/p>\n<p>Mais le principal inconv\u00e9nient d'un tel cluster \u00e0 haute disponibilit\u00e9 est l'absence totale de disponibilit\u00e9 en cas de d\u00e9faillance du centre de donn\u00e9es o\u00f9 il se trouve. Odnoklassniki dispose de quatre centres de donn\u00e9es, et nous devons assurer le fonctionnement en cas de sinistre total dans l'un d'eux.<\/p>\n<p>Pour cela, on pourrait appliquer une <noindex><a rel=\"nofollow\" href=\"https:\/\/technet.microsoft.com\/en-us\/library\/ms151196.aspx\">r\u00e9plication Multi-Master<\/a><\/noindex> int\u00e9gr\u00e9e \u00e0 SQL Server. Cette solution est beaucoup plus co\u00fbteuse en raison des frais logiciels et souffre de probl\u00e8mes de r\u00e9plication bien connus - des d\u00e9lais de transactions impr\u00e9visibles lors de la r\u00e9plication synchrone et des d\u00e9lais dans l'application des r\u00e9plications (et, par cons\u00e9quent, des modifications perdues) en mode asynchrone. La n\u00e9cessit\u00e9 de <noindex><a rel=\"nofollow\" href=\"https:\/\/docs.microsoft.com\/en-us\/sql\/relational-databases\/replication\/transactional\/peer-to-peer-conflict-detection-in-peer-to-peer-replication\">r\u00e9soudre manuellement les conflits<\/a><\/noindex> rend cette option compl\u00e8tement inapplicable pour nous.<\/p>\n<p>Tous ces probl\u00e8mes n\u00e9cessitaient une solution radicale, et nous avons commenc\u00e9 \u00e0 les analyser en d\u00e9tail. Ici, nous devons nous familiariser avec ce que fait principalement SQL Server : les transactions.<\/p>\n<h2>Une transaction simple<\/h2>\n<p>\nPrenons la transaction la plus simple du point de vue d'un programmeur SQL appliqu\u00e9 : l'ajout d'une photo \u00e0 un album. Les albums et les photos sont stock\u00e9s dans diff\u00e9rentes tables. Un album a un compteur de photos publiques. Ainsi, cette transaction se d\u00e9compose en plusieurs \u00e9tapes : <\/p>\n<ol>\n<li>Nous verrouillons l'album par cl\u00e9.<\/li>\n<li>Nous cr\u00e9ons un enregistrement dans la table des photos. <\/li>\n<li>Si la photo est publique, nous incr\u00e9mentons le compteur de photos publiques dans l'album, mettons \u00e0 jour l'enregistrement, et validons la transaction.<\/li>\n<\/ol>\n<p>\nOu sous forme de pseudocode :<\/p>\n<pre><code>TX.start(\"Albums\", id);\nAlbum album = albums.lock(id);\nPhoto photo = photos.create(\u2026);\n\nif (photo.status == PUBLIC) {\n    album.incPublicPhotosCount();\n}\nalbum.update();\n\nTX.commit();<\/code><\/pre>\n<p>\nNous voyons que le sc\u00e9nario de transaction d'entreprise le plus courant consiste \u00e0 lire les donn\u00e9es de la base de donn\u00e9es en m\u00e9moire du serveur d'applications, \u00e0 modifier quelque chose et \u00e0 enregistrer les nouvelles valeurs dans la base de donn\u00e9es. En g\u00e9n\u00e9ral, dans une telle transaction, nous mettons \u00e0 jour plusieurs entit\u00e9s, plusieurs tables. <\/p>\n<p>Lors de l'ex\u00e9cution d'une transaction, une modification concurrente des m\u00eames donn\u00e9es par un autre syst\u00e8me peut se produire. Par exemple, un anti-spam peut d\u00e9cider qu'un utilisateur est suspect et que toutes les photos de cet utilisateur ne doivent plus \u00eatre publiques, qu'elles doivent \u00eatre soumises \u00e0 mod\u00e9ration, ce qui signifie que photo.status doit \u00eatre chang\u00e9 en une autre valeur et que les compteurs correspondants doivent \u00eatre ajust\u00e9s. Il est \u00e9vident que si cette op\u00e9ration se produit sans garanties d'atome d'application et d'isolation des modifications concurrentes, comme dans <noindex><a rel=\"nofollow\" href=\"https:\/\/ru.wikipedia.org\/wiki\/ACID\">ACID<\/a><\/noindex>, le r\u00e9sultat ne sera pas celui attendu \u2014 soit le compteur de photos affichera une valeur incorrecte, soit toutes les photos ne seront pas soumises \u00e0 mod\u00e9ration. <\/p>\n<p>Un tel code, manipulant diff\u00e9rentes entit\u00e9s commerciales dans le cadre d'une seule transaction, a \u00e9t\u00e9 \u00e9crit en tr\u00e8s grand nombre au fil des ans depuis l'existence de Odnoklassniki. De notre exp\u00e9rience en migrations vers NoSQL avec <noindex><a rel=\"nofollow\" href=\"https:\/\/en.wikipedia.org\/wiki\/Eventual_consistency\">Eventual Consistency<\/a><\/noindex> , nous savons que les plus grandes difficult\u00e9s (et les d\u00e9lais associ\u00e9s) sont li\u00e9es \u00e0 la n\u00e9cessit\u00e9 de d\u00e9velopper du code destin\u00e9 \u00e0 maintenir la coh\u00e9rence des donn\u00e9es. Par cons\u00e9quent, la principale exigence pour le nouveau stockage \u00e9tait d'assurer des transactions ACID r\u00e9elles pour la logique appliqu\u00e9e. <\/p>\n<p>D'autres exigences tout aussi importantes \u00e9taient :<\/p>\n<ul>\n<li>En cas d'\u00e9chec du centre de donn\u00e9es, \u00e0 la fois la lecture et l'\u00e9criture doivent \u00eatre disponibles dans le nouveau stockage.<\/li>\n<li>Maintien de la vitesse actuelle de d\u00e9veloppement. Cela signifie qu'en travaillant avec le nouveau stockage, le volume de code doit rester \u00e0 peu pr\u00e8s le m\u00eame, sans qu'il soit n\u00e9cessaire d'ajouter quoi que ce soit au stockage, de d\u00e9velopper des algorithmes de r\u00e9solution de conflits, de maintenir des index secondaires, etc. <\/li>\n<li>La rapidit\u00e9 de fonctionnement du nouveau stockage doit \u00eatre suffisamment \u00e9lev\u00e9e, tant pour la lecture des donn\u00e9es que pour le traitement des transactions, ce qui signifiait en effet l'inapplicabilit\u00e9 de solutions acad\u00e9miquement strictes, universelles, mais lentes, comme par exemple <noindex><a rel=\"nofollow\" href=\"https:\/\/en.wikipedia.org\/wiki\/Two-phase_commit_protocol\">les commits \u00e0 deux phases<\/a><\/noindex>.<\/li>\n<li>Mise \u00e0 l'\u00e9chelle automatique \u00e0 la vol\u00e9e. <\/li>\n<li>Utilisation de serveurs ordinaires \u00e0 bas prix, sans avoir besoin d'acheter du mat\u00e9riel exotique. <\/li>\n<li>Possibilit\u00e9 de d\u00e9veloppement du stockage par les d\u00e9veloppeurs de l'entreprise. En d'autres termes, la priorit\u00e9 \u00e9tait donn\u00e9e aux solutions propri\u00e9taires ou open source, de pr\u00e9f\u00e9rence en Java.<\/li>\n<\/ul>\n<p><\/p>\n<h2>Solutions, solutions<\/h2>\n<p>\nEn analysant les solutions possibles, nous sommes arriv\u00e9s \u00e0 deux choix possibles d'architecture :<\/p>\n<p>Le premier consiste \u00e0 prendre n'importe quel serveur SQL et \u00e0 mettre en \u0153uvre la redondance n\u00e9cessaire, le m\u00e9canisme de mise \u00e0 l'\u00e9chelle, le cluster tol\u00e9rant aux pannes, la r\u00e9solution des conflits et des transactions ACID distribu\u00e9es, fiables et rapides. Nous avons \u00e9valu\u00e9 cette option comme \u00e9tant plut\u00f4t non triviale et laborieuse.<\/p>\n<p>La deuxi\u00e8me option consiste \u00e0 prendre un stockage NoSQL pr\u00eat \u00e0 l'emploi avec mise \u00e0 l'\u00e9chelle int\u00e9gr\u00e9e, un cluster tol\u00e9rant aux pannes, la r\u00e9solution des conflits et \u00e0 impl\u00e9menter nous-m\u00eames les transactions et SQL. \u00c0 premi\u00e8re vue, m\u00eame la t\u00e2che de mise en \u0153uvre de SQL, sans parler des transactions ACID, semble une t\u00e2che de plusieurs ann\u00e9es. Mais ensuite, nous avons r\u00e9alis\u00e9 que l'ensemble des fonctionnalit\u00e9s SQL que nous utilisons en pratique s'\u00e9loigne autant de l'ANSI SQL que <noindex><a rel=\"nofollow\" href=\"https:\/\/en.wikipedia.org\/wiki\/Apache_Cassandra#Cassandra_Query_Language\">Cassandra CQL<\/a><\/noindex> s'\u00e9loigne de l'ANSI SQL. Apr\u00e8s un examen plus attentif, nous avons compris qu'il est suffisamment proche de ce dont nous avons besoin.<\/p>\n<h2>Cassandra et CQL<\/h2>\n<p>\nAlors, qu'est-ce qui rend Cassandra int\u00e9ressante, quelles sont ses capacit\u00e9s ?<\/p>\n<p>Tout d'abord, il est possible de cr\u00e9er des tables prenant en charge diff\u00e9rents types de donn\u00e9es, et l'on peut effectuer des op\u00e9rations SELECT ou UPDATE sur la cl\u00e9 primaire.<\/p>\n<pre><code>CREATE TABLE photos (id bigint KEY, owner bigint,\u2026);\nSELECT * FROM photos WHERE id=?;\nUPDATE photos SET \u2026 WHERE id=?;<\/code><\/pre>\n<p>\nPour garantir la coh\u00e9rence des donn\u00e9es des r\u00e9pliques, Cassandra utilise <noindex><a rel=\"nofollow\" href=\"https:\/\/en.wikipedia.org\/wiki\/Quorum_(distributed_computing)\">une approche bas\u00e9e sur le quorum<\/a><\/noindex>. Dans le cas le plus simple, cela signifie que lorsqu'on d\u00e9ploie trois r\u00e9pliques d'une m\u00eame ligne sur diff\u00e9rentes n\u0153uds du cluster, l'\u00e9criture est consid\u00e9r\u00e9e comme r\u00e9ussie si la majorit\u00e9 des n\u0153uds (c'est-\u00e0-dire deux sur trois) ont confirm\u00e9 le succ\u00e8s de cette op\u00e9ration d'\u00e9criture. Les donn\u00e9es de la ligne sont consid\u00e9r\u00e9es comme coh\u00e9rentes si, lors de la lecture, la majorit\u00e9 des n\u0153uds ont \u00e9t\u00e9 interrog\u00e9s et ont confirm\u00e9 leur \u00e9tat. Ainsi, en ayant trois r\u00e9pliques, on garantit une coh\u00e9rence des donn\u00e9es compl\u00e8te et instantan\u00e9e en cas de d\u00e9faillance d'un n\u0153ud. Cette approche nous a permis de mettre en place un sch\u00e9ma encore plus fiable : toujours envoyer des requ\u00eates aux trois r\u00e9pliques, en attendant la r\u00e9ponse des deux plus rapides. La r\u00e9ponse tardive de la troisi\u00e8me r\u00e9plique est alors rejet\u00e9e. Le n\u0153ud dont la r\u00e9ponse est en retard peut rencontrer de graves probl\u00e8mes : ralentissements, collecte de d\u00e9chets dans la JVM, r\u00e9cup\u00e9ration de m\u00e9moire directe dans le noyau linux, d\u00e9faillance mat\u00e9rielle, coupure r\u00e9seau. Cependant, cela n'affecte en rien les op\u00e9rations du client ni les donn\u00e9es.<\/p>\n<p>L'approche o\u00f9 nous contactons trois n\u0153uds et recevons la r\u00e9ponse de deux est appel\u00e9e <noindex><a rel=\"nofollow\" href=\"https:\/\/en.wikipedia.org\/wiki\/Speculative_execution\">sp\u00e9culation<\/a><\/noindex>: la demande d'extras r\u00e9pliques est envoy\u00e9e avant m\u00eame qu'elle ne \u00ab tombe \u00bb. <\/p>\n<p>Un autre des avantages de Cassandra est le Batchlog - un m\u00e9canisme garantissant soit l'application compl\u00e8te, soit le rejet complet d'un lot de modifications que vous apportez. Cela nous permet de r\u00e9soudre A dans ACID - l'atomicit\u00e9 pr\u00eate \u00e0 l'emploi.<\/p>\n<p>La chose la plus proche des transactions dans Cassandra est ce que l'on appelle \u00ab<noindex><a rel=\"nofollow\" href=\"https:\/\/docs.datastax.com\/en\/cql\/3.3\/cql\/cql_using\/useInsertLWT.html\">transactions l\u00e9g\u00e8res<\/a><\/noindex>\u00bb. Mais elles sont loin des v\u00e9ritables transactions ACID : en r\u00e9alit\u00e9, c'est une possibilit\u00e9 de faire <noindex><a rel=\"nofollow\" href=\"https:\/\/en.wikipedia.org\/wiki\/Compare-and-swap\">CAS<\/a><\/noindex> sur les donn\u00e9es d'un seul enregistrement, en utilisant le consensus par le protocole lourd Paxos. Par cons\u00e9quent, la vitesse de telles transactions est faible. <\/p>\n<h2>Ce qui nous a manqu\u00e9 dans Cassandra<\/h2>\n<p>\nAinsi, nous devions impl\u00e9menter de v\u00e9ritables transactions ACID dans Cassandra. Avec lesquelles nous pourrions facilement r\u00e9aliser deux autres capacit\u00e9s pratiques des SGBD classiques : des index rapides et coh\u00e9rents, ce qui nous permettrait d'effectuer des s\u00e9lections de donn\u00e9es non seulement par cl\u00e9 primaire et un g\u00e9n\u00e9rateur de ID auto-incr\u00e9mentaux monotones normal.<\/p>\n<h4>C*One<\/h4>\n<p>\nAinsi est n\u00e9e une nouvelle base de donn\u00e9es <b>C*One<\/b>, compos\u00e9e de trois types de n\u0153uds serveurs :<\/p>\n<ul>\n<li>Stockage - (presque) serveurs standard Cassandra, responsables du stockage des donn\u00e9es sur les disques locaux. \u00c0 mesure que la charge et le volume des donn\u00e9es augmentent, leur nombre peut facilement \u00eatre \u00e9volu\u00e9 en dizaines et centaines.<\/li>\n<li>Les coordinateurs de transactions garantissent l'ex\u00e9cution des transactions. <\/li>\n<li>Les clients sont des serveurs d'applications qui r\u00e9alisent des op\u00e9rations commerciales et initient des transactions. Il peut y avoir des milliers de tels clients.<\/li>\n<\/ul>\n<p>\n<img decoding=\"async\" alt=\"NewSQL = NoSQL+ACID\" src=\"\/wp-content\/uploads\/2019\/09\/f3809d89404ae596c62b642dbdb9e431.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nTous les serveurs de tous types font partie d'un cluster commun, utilisant un protocole interne de messagerie Cassandra pour communiquer entre eux et <noindex><a rel=\"nofollow\" href=\"https:\/\/en.wikipedia.org\/wiki\/Gossip_protocol\">gossip<\/a><\/noindex> \u00e9changer des informations de cluster. Gr\u00e2ce \u00e0 Heartbeat, les serveurs prennent connaissance des pannes mutuelles, maintiennent un sch\u00e9ma de donn\u00e9es coh\u00e9rent \u2014 tables, leur structure et r\u00e9plication ; sch\u00e9ma de partitionnement, topologie du cluster, etc.<\/p>\n<h4>Clients<\/h4>\n<p>\n<img decoding=\"async\" alt=\"NewSQL = NoSQL+ACID\" src=\"\/wp-content\/uploads\/2019\/09\/80f83358f215cf59db3b657ba1083420.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nAu lieu des pilotes standard, un mode Fat Client est utilis\u00e9. Ce n\u0153ud ne stocke pas de donn\u00e9es, mais peut jouer le r\u00f4le de coordinateur d'ex\u00e9cution des requ\u00eates, c'est-\u00e0-dire que le client ex\u00e9cute lui-m\u00eame la fonction de coordinateur de ses requ\u00eates : il interroge les r\u00e9pliques du stockage et r\u00e9sout les conflits. Cela est non seulement plus fiable et plus rapide que le pilote standard, qui n\u00e9cessite une communication avec un coordinateur distant, mais permet \u00e9galement de g\u00e9rer le transfert des requ\u00eates. En dehors d'une transaction ouverte sur le client, les requ\u00eates sont dirig\u00e9es vers les stockages. Si le client a ouvert une transaction, toutes les requ\u00eates dans le cadre de celle-ci sont envoy\u00e9es au coordinateur de transactions.<br \/>\n<img decoding=\"async\" alt=\"NewSQL = NoSQL+ACID\" src=\"\/wp-content\/uploads\/2019\/09\/709bf227eeb8f71abeff703a72780efd.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<\/p>\n<h2>Coordinateur de transactions C*One<\/h2>\n<p>\nLe coordinateur est ce que nous avons mis en \u0153uvre pour C*One depuis le d\u00e9but. Il est responsable de la gestion des transactions, des verrous et de l'ordre d'application des transactions.<\/p>\n<p>Pour chaque transaction trait\u00e9e, le coordinateur g\u00e9n\u00e8re un horodatage : chaque horodatage suivant est sup\u00e9rieur \u00e0 celui de la transaction pr\u00e9c\u00e9dente. Comme dans Cassandra, le syst\u00e8me de r\u00e9solution des conflits est bas\u00e9 sur les horodatages (parmi deux enregistrements conflictuels, celui avec l'horodatage le plus r\u00e9cent est consid\u00e9r\u00e9 comme valide), le conflit sera toujours r\u00e9solu en faveur de la transaction suivante. Ainsi, nous avons r\u00e9alis\u00e9 <noindex><a rel=\"nofollow\" href=\"https:\/\/ru.wikipedia.org\/wiki\/%D0%A7%D0%B0%D1%81%D1%8B_%D0%9B%D1%8D%D0%BC%D0%BF%D0%BE%D1%80%D1%82%D0%B0\">les horloges de Lamport<\/a><\/noindex> \u2014 une mani\u00e8re peu co\u00fbteuse de r\u00e9soudre les conflits dans un syst\u00e8me r\u00e9parti.<\/p>\n<h2>Verrous<\/h2>\n<p>\nPour garantir l'isolation, nous avons d\u00e9cid\u00e9 d'utiliser la m\u00e9thode la plus simple \u2014 les verrous pessimistes bas\u00e9s sur la cl\u00e9 primaire de l'enregistrement. En d'autres termes, dans la transaction, l'enregistrement doit d'abord \u00eatre verrouill\u00e9, puis lu, modifi\u00e9 et enregistr\u00e9. Ce n'est qu'apr\u00e8s une validation r\u00e9ussie que l'enregistrement peut \u00eatre d\u00e9verrouill\u00e9, afin que les transactions concurrentes puissent l'utiliser.<\/p>\n<p>La mise en \u0153uvre d'un tel verrouillage est simple dans un environnement non distribu\u00e9. Dans un syst\u00e8me distribu\u00e9, il existe deux approches principales : soit mettre en \u0153uvre un verrouillage distribu\u00e9 sur le cluster, soit r\u00e9partir les transactions de mani\u00e8re \u00e0 ce que les transactions impliquant un m\u00eame enregistrement soient toujours trait\u00e9es par le m\u00eame coordinateur.<\/p>\n<p>\u00c9tant donn\u00e9 que, dans notre cas, les donn\u00e9es sont d\u00e9j\u00e0 r\u00e9parties en groupes de transactions locales dans SQL, il a \u00e9t\u00e9 d\u00e9cid\u00e9 d'attribuer des coordinateurs aux groupes de transactions locales : un coordinateur g\u00e8re toutes les transactions avec un jeton de 0 \u00e0 9, le second avec un jeton de 10 \u00e0 19, et ainsi de suite. En cons\u00e9quence, chaque instance de coordinateur devient le ma\u00eetre du groupe de transactions. <\/p>\n<p>Ainsi, les verrouillages peuvent \u00eatre r\u00e9alis\u00e9s sous forme de simple HashMap dans la m\u00e9moire du coordinateur.<\/p>\n<h2>Pannes des coordinateurs<\/h2>\n<p>\n\u00c9tant donn\u00e9 qu'un coordinateur ne g\u00e8re qu'un groupe de transactions, il est tr\u00e8s important de d\u00e9tecter rapidement toute d\u00e9faillance pour que la nouvelle tentative d'ex\u00e9cuter une transaction soit r\u00e9alis\u00e9e dans le d\u00e9lai imparti. Pour cela, nous avons appliqu\u00e9 un protocole de heartbeat en quorum enti\u00e8rement connect\u00e9 :<\/p>\n<p>Dans chaque centre de donn\u00e9es, au moins deux n\u0153uds coordinateurs sont plac\u00e9s. P\u00e9riodiquement, chaque coordinateur envoie un message heartbeat aux autres coordinateurs, leur rapportant son bon fonctionnement ainsi que les messages heartbeat qu'il a re\u00e7us des autres coordinateurs du cluster. <\/p>\n<p><img decoding=\"async\" alt=\"NewSQL = NoSQL+ACID\" src=\"\/wp-content\/uploads\/2019\/09\/5c337e53815cad0a2071b160d98d42fa.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nEn recevant des informations similaires des autres dans leurs messages heartbeat, chaque coordinateur d\u00e9termine quelles n\u0153uds du cluster fonctionnent et lesquelles ne le font pas, en suivant le principe du quorum : si le n\u0153ud X a re\u00e7u la confirmation de la plupart des n\u0153uds du cluster concernant la bonne r\u00e9ception des messages du n\u0153ud Y, cela signifie que Y est op\u00e9rationnel. Et inversement, d\u00e8s que la plupart rapportent la perte de messages du n\u0153ud Y, cela signifie que Y a \u00e9chou\u00e9. Fait int\u00e9ressant, si le quorum informe le n\u0153ud X qu'il ne re\u00e7oit plus de messages de celui-ci, le n\u0153ud X se consid\u00e9rera alors comme d\u00e9faillant.<\/p>\n<p>Les messages de c\u0153ur battant sont envoy\u00e9s \u00e0 une fr\u00e9quence \u00e9lev\u00e9e, environ 20 fois par seconde, avec une p\u00e9riode de 50 ms. En Java, il est difficile de garantir la r\u00e9ponse de l'application dans les 50 ms en raison de la dur\u00e9e comparable des pauses caus\u00e9es par le ramasse-miettes. Nous avons r\u00e9ussi \u00e0 atteindre ce temps de r\u00e9ponse en utilisant le ramasse-miettes G1, qui permet de d\u00e9finir un objectif pour la dur\u00e9e des pauses GC. Cependant, parfois, assez rarement, les pauses du ramasse-miettes d\u00e9passent les 50 ms, ce qui peut entra\u00eener une d\u00e9tection erron\u00e9e de d\u00e9faillance. Pour \u00e9viter cela, le coordinateur ne signale pas la d\u00e9faillance d'un n\u0153ud distant apr\u00e8s la perte du premier message de c\u0153ur battant, seulement si plusieurs sont perdus cons\u00e9cutivement. Ainsi, nous avons r\u00e9ussi \u00e0 d\u00e9tecter la d\u00e9faillance d'un n\u0153ud coordinateur en 200 ms. <\/p>\n<p>Mais il ne suffit pas de comprendre rapidement quel n\u0153ud a cess\u00e9 de fonctionner. Il faut agir. <\/p>\n<h2>Redondance<\/h2>\n<p>\nLe sch\u00e9ma classique pr\u00e9voit qu'en cas de d\u00e9faillance du ma\u00eetre, de nouvelles \u00e9lections soient lanc\u00e9es \u00e0 l'aide de l'un des<noindex><a rel=\"nofollow\" href=\"https:\/\/ru.wikipedia.org\/wiki\/%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_Raft\"> algorithmes \u00e0 la mode.<\/a><\/noindex> <noindex><a rel=\"nofollow\" href=\"https:\/\/ru.wikipedia.org\/wiki\/%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_%D0%9F%D0%B0%D0%BA%D1%81%D0%BE%D1%81\">universels.<\/a><\/noindex> Cependant, de tels algorithmes ont des probl\u00e8mes bien connus de convergence dans le temps et de dur\u00e9e du processus \u00e9lectoral lui-m\u00eame. Nous avons r\u00e9ussi \u00e0 \u00e9viter ces d\u00e9lais suppl\u00e9mentaires gr\u00e2ce \u00e0 un sch\u00e9ma de remplacement des coordinateurs dans un r\u00e9seau compl\u00e8tement connect\u00e9 :<\/p>\n<p><img decoding=\"async\" alt=\"NewSQL = NoSQL+ACID\" src=\"\/wp-content\/uploads\/2019\/09\/5e2db258fa2200026390a73a414dfe81.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nSupposons que nous souhaitons effectuer une transaction dans le groupe 50. D\u00e9terminons \u00e0 l'avance le sch\u00e9ma de remplacement, c'est-\u00e0-dire quels n\u0153uds ex\u00e9cuteront les transactions du groupe 50 en cas de d\u00e9faillance du coordinateur principal. Notre but est de maintenir le fonctionnement du syst\u00e8me en cas de d\u00e9faillance d'un centre de donn\u00e9es. Nous allons d\u00e9cider que le premier remplacement sera un n\u0153ud d'un autre centre de donn\u00e9es, et le deuxi\u00e8me remplacement sera un n\u0153ud d'un troisi\u00e8me. Ce sch\u00e9ma est choisi une fois et ne change pas tant que la topologie du cluster ne change pas, c'est-\u00e0-dire tant que de nouveaux n\u0153uds ne rejoignent pas (ce qui se produit tr\u00e8s rarement). L'ordre de s\u00e9lection d'un nouveau ma\u00eetre actif en cas de d\u00e9faillance de l'ancien sera toujours le suivant : le premier remplacement deviendra le ma\u00eetre actif, et s'il cesse \u00e9galement de fonctionner, le deuxi\u00e8me remplacement prendra le relais. <\/p>\n<p>Ce sch\u00e9ma est plus fiable qu'un algorithme universel, car pour activer un nouveau ma\u00eetre, il suffit de d\u00e9terminer le fait de la d\u00e9faillance de l'ancien.<\/p>\n<p>Mais comment les clients sauront-ils quel ma\u00eetre est actif en ce moment ? Il est impossible d'envoyer des informations \u00e0 des milliers de clients en 50 ms. Il peut arriver qu'un client envoie une demande d'ouverture de transaction sans savoir que ce ma\u00eetre n'est plus actif, et la demande pourrait se bloquer \u00e0 cause d'un timeout. Pour \u00e9viter cela, les clients envoient sp\u00e9culativement une demande d'ouverture de transaction simultan\u00e9ment au ma\u00eetre du groupe et \u00e0 ses deux r\u00e9serves, mais seule la r\u00e9ponse du ma\u00eetre actif sera trait\u00e9e. Toute communication ult\u00e9rieure dans le cadre de la transaction sera uniquement avec le ma\u00eetre actif.<\/p>\n<p>Les ma\u00eetres de r\u00e9serve placent les demandes re\u00e7ues pour des transactions qui ne leur appartiennent pas dans une file d'attente de transactions non r\u00e9alis\u00e9es, o\u00f9 elles restent pendant un certain temps. Si le ma\u00eetre actif meurt, un nouveau ma\u00eetre traite les demandes d'ouverture de transactions de sa file d'attente et r\u00e9pond au client. Si le client a d\u00e9j\u00e0 r\u00e9ussi \u00e0 ouvrir une transaction avec l'ancien ma\u00eetre, la seconde r\u00e9ponse est ignor\u00e9e (et, de toute \u00e9vidence, cette transaction ne sera pas finalis\u00e9e et devra \u00eatre r\u00e9p\u00e9t\u00e9e par le client).<\/p>\n<h2>Comment fonctionne une transaction<\/h2>\n<p>\nSupposons qu'un client a envoy\u00e9 une demande d'ouverture de transaction au coordinateur pour une certaine entit\u00e9 avec une certaine cl\u00e9 primaire. Le coordinateur bloque cette entit\u00e9 et l'ajoute \u00e0 la table des verrouillages en m\u00e9moire. Si n\u00e9cessaire, le coordinateur lit cette entit\u00e9 depuis le stockage et sauvegarde les donn\u00e9es obtenues dans l'\u00e9tat de la transaction en m\u00e9moire du coordinateur.<\/p>\n<p><img decoding=\"async\" alt=\"NewSQL = NoSQL+ACID\" src=\"\/wp-content\/uploads\/2019\/09\/246b4a5c68a4cc886b87af784c16b9dc.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nLorsque le client souhaite modifier des donn\u00e9es dans la transaction, il envoie une demande de modification d'entit\u00e9 au coordinateur, qui place les nouvelles donn\u00e9es dans la table d'\u00e9tat des transactions en m\u00e9moire. \u00c0 ce stade, l'enregistrement est termin\u00e9 \u2014 aucune \u00e9criture dans le stockage n'est effectu\u00e9e.<\/p>\n<p><img decoding=\"async\" alt=\"NewSQL = NoSQL+ACID\" src=\"\/wp-content\/uploads\/2019\/09\/4400a91af30eb71134978ff27474f745.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nLorsque le client demande ses propres donn\u00e9es modifi\u00e9es dans le cadre d'une transaction active, le coordinateur agit comme suit : <\/p>\n<ul>\n<li>si l'ID est d\u00e9j\u00e0 pr\u00e9sent dans la transaction, les donn\u00e9es sont prises de la m\u00e9moire ; <\/li>\n<li>si l'ID est absent en m\u00e9moire, les donn\u00e9es manquantes sont lues des n\u0153uds de stockage, combin\u00e9es avec celles d\u00e9j\u00e0 pr\u00e9sentes en m\u00e9moire, et le r\u00e9sultat est renvoy\u00e9 au client. <\/li>\n<\/ul>\n<p>\nAinsi, le client peut lire ses propres modifications, tandis que les autres clients ne voient pas ces modifications, car elles ne sont stock\u00e9es que dans la m\u00e9moire du coordinateur, elles ne sont pas encore pr\u00e9sentes dans les n\u0153uds Cassandra.<\/p>\n<p><img decoding=\"async\" alt=\"NewSQL = NoSQL+ACID\" src=\"\/wp-content\/uploads\/2019\/09\/c93fd7e8393f431a976f1f56b95e227c.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nLorsque le client envoie un commit, l'\u00e9tat en m\u00e9moire du service est enregistr\u00e9 par le coordinateur dans un batch enregistr\u00e9, qui est ensuite envoy\u00e9 aux syst\u00e8mes de stockage Cassandra sous forme de batch enregistr\u00e9. Les syst\u00e8mes de stockage effectuent tout le n\u00e9cessaire pour que ce batch soit appliqu\u00e9 de mani\u00e8re atomique (enti\u00e8rement) et renvoient une r\u00e9ponse au coordinateur, qui lib\u00e8re alors les verrous et confirme le succ\u00e8s de la transaction au client.<\/p>\n<p><img decoding=\"async\" alt=\"NewSQL = NoSQL+ACID\" src=\"\/wp-content\/uploads\/2019\/09\/ab20487ba53ed88e2f560a2249b5d6f9.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nEt pour annuler, il suffit au coordinateur de lib\u00e9rer la m\u00e9moire occup\u00e9e par l'\u00e9tat de la transaction.<\/p>\n<p>\u00c0 la suite des am\u00e9liorations d\u00e9crites ci-dessus, nous avons mis en \u0153uvre les principes ACID :<\/p>\n<ul>\n<li><b>Atomicit\u00e9<\/b>. Cela garantit qu'aucune transaction ne sera enregistr\u00e9e partiellement dans le syst\u00e8me, soit toutes ses sous-op\u00e9rations seront ex\u00e9cut\u00e9es, soit aucune d'entre elles ne le sera. Ce principe est respect\u00e9 chez nous gr\u00e2ce au batch enregistr\u00e9 dans Cassandra.<\/li>\n<li><b>Coh\u00e9rence<\/b>. Chaque transaction r\u00e9ussie enregistre par d\u00e9finition uniquement des r\u00e9sultats valides. Si, apr\u00e8s l'ouverture de la transaction et l'ex\u00e9cution d'une partie des op\u00e9rations, il est constat\u00e9 que le r\u00e9sultat est invalide, un rollback est effectu\u00e9.<\/li>\n<li><b>Isolation<\/b>. Lors de l'ex\u00e9cution de la transaction, les transactions parall\u00e8les ne doivent pas affecter son r\u00e9sultat. Les transactions concurrentes sont isol\u00e9es par le biais de verrous pessimistes sur le coordinateur. Pour les lectures en dehors de la transaction, le principe d'isolation est respect\u00e9 au niveau Read Committed.<\/li>\n<li><b>R\u00e9silience<\/b>. Ind\u00e9pendamment des probl\u00e8mes sur les niveaux inf\u00e9rieurs \u2014 coupure de courant, d\u00e9faillance mat\u00e9rielle \u2014 les modifications apport\u00e9es par une transaction ayant \u00e9t\u00e9 correctement finalis\u00e9e doivent rester enregistr\u00e9es apr\u00e8s la reprise des op\u00e9rations. <\/li>\n<\/ul>\n<p><\/p>\n<h2>Lecture par index<\/h2>\n<p>\nPrenons une table simple : <\/p>\n<pre><code>CREATE TABLE photos (\nid bigint primary key,\nowner bigint,\nmodified timestamp,\n\u2026)<\/code><\/pre>\n<p>\nElle a un ID (cl\u00e9 primaire), un propri\u00e9taire et une date de modification. Nous devons ex\u00e9cuter une requ\u00eate tr\u00e8s simple \u2014 s\u00e9lectionner des donn\u00e9es par propri\u00e9taire avec une date de modification \u00ab au cours des derni\u00e8res 24 heures \u00bb. <\/p>\n<pre><code>SELECT *\nWHERE owner=?\nAND modified&gt;?<\/code><\/pre>\n<p>\nPour que ce type de requ\u00eate soit ex\u00e9cut\u00e9 rapidement, dans une base de donn\u00e9es SQL classique, il est n\u00e9cessaire de cr\u00e9er un index sur les colonnes (owner, modified). Nous pouvons faire cela assez facilement, car nous avons maintenant des garanties ACID !<\/p>\n<h2>Index dans C*One<\/h2>\n<p>\nIl existe une table source avec des photos, o\u00f9 l'ID de l'enregistrement est la cl\u00e9 primaire. <\/p>\n<p><img decoding=\"async\" alt=\"NewSQL = NoSQL+ACID\" src=\"\/wp-content\/uploads\/2019\/09\/9f5d7c5f66882666a7ae8219f710beeb.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nPour l'index, C*One cr\u00e9e une nouvelle table qui est une copie de la table d'origine. La cl\u00e9 co\u00efncide avec l'expression index\u00e9e, tout en incluant \u00e9galement la cl\u00e9 primaire de l'enregistrement de la table d'origine :<\/p>\n<p><img decoding=\"async\" alt=\"NewSQL = NoSQL+ACID\" src=\"\/wp-content\/uploads\/2019\/09\/ce6df1c8407bca7ddc61fd88141455e6.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nD\u00e9sormais, la requ\u00eate \u00ab propri\u00e9taire pour les derni\u00e8res 24 heures \u00bb peut \u00eatre r\u00e9\u00e9crite comme un select d'une autre table :<\/p>\n<pre><code>SELECT * FROM i1_test\nWHERE owner=?\nAND modified &gt; ?<\/code><\/pre>\n<p>\nLa coh\u00e9rence des donn\u00e9es de la table d'origine photos et de l'index i1 est automatiquement maintenue par le coordonnateur. Sur la base du seul sch\u00e9ma de donn\u00e9es, lors de la r\u00e9ception d'une modification, le coordonnateur g\u00e9n\u00e8re et m\u00e9morise la modification non seulement de la table principale, mais aussi des modifications des copies. Aucune action suppl\u00e9mentaire sur la table d'index n'est effectu\u00e9e, les journaux ne sont pas lus, les verrous ne sont pas utilis\u00e9s. En d'autres termes, l'ajout d'index consomme presque aucun ressource et n'a pratiquement aucun impact sur la vitesse d'application des modifications.<\/p>\n<p>Avec ACID, nous avons r\u00e9ussi \u00e0 mettre en \u0153uvre des index \u00ab comme en SQL \u00bb. Ils offrent la coh\u00e9rence, peuvent \u00e9voluer, fonctionnent rapidement, peuvent \u00eatre compos\u00e9s et int\u00e9gr\u00e9s dans le langage de requ\u00eates CQL. Aucun changement dans le code applicatif n'est n\u00e9cessaire pour soutenir les index. C'est aussi simple qu'en SQL. Et surtout, les index n'affectent pas la vitesse d'ex\u00e9cution des modifications de la table de transactions d'origine.<\/p>\n<h2>Ce qui a \u00e9t\u00e9 r\u00e9alis\u00e9<\/h2>\n<p>\nNous avons d\u00e9velopp\u00e9 C*One il y a trois ans et l'avons mis en service. <\/p>\n<p>Qu'avons-nous obtenu en fin de compte ? \u00c9valuons cela \u00e0 travers le sous-syst\u00e8me de traitement et de stockage des photographies, l'un des types de donn\u00e9es les plus importants dans le r\u00e9seau social. Il ne s'agit pas des fichiers photos eux-m\u00eames, mais de toutes sortes de m\u00e9tadonn\u00e9es. Actuellement, il y a environ 20 milliards de ces enregistrements dans \u00ab Odnoklassniki \u00bb, le syst\u00e8me traite 80 000 requ\u00eates de lecture par seconde, jusqu'\u00e0 8 000 transactions ACID par seconde, li\u00e9es \u00e0 la modification des donn\u00e9es. <\/p>\n<p>Lorsque nous utilisions SQL avec un facteur de r\u00e9plication = 1 (mais en RAID 10), les m\u00e9tadonn\u00e9es des photos \u00e9taient stock\u00e9es sur un cluster hautement disponible de 32 machines avec Microsoft SQL Server (plus 11 de sauvegarde). De plus, 10 serveurs \u00e9taient r\u00e9serv\u00e9s pour le stockage des sauvegardes. Au total, 50 machines co\u00fbteuses. Pendant ce temps, le syst\u00e8me fonctionnait \u00e0 charge nominale, sans marge.<\/p>\n<p>Apr\u00e8s la migration vers le nouveau syst\u00e8me, nous avons obtenu un facteur de r\u00e9plication = 3 \u2014 une copie dans chaque centre de donn\u00e9es. Le syst\u00e8me se compose de 63 n\u0153uds de stockage Cassandra et de 6 machines de coordonnateurs, soit 69 serveurs au total. Mais ces machines sont beaucoup moins ch\u00e8res, leur co\u00fbt total repr\u00e9sente environ 30 % du co\u00fbt du syst\u00e8me SQL. Pendant ce temps, la charge reste \u00e0 un niveau de 30 %.<\/p>\n<p>Avec l'introduction de C*One, les latences ont \u00e9galement diminu\u00e9 : dans SQL, l'op\u00e9ration d'\u00e9criture prenait environ 4,5 ms. Avec C*One, cela prend environ 1,6 ms. La dur\u00e9e des transactions est en moyenne inf\u00e9rieure \u00e0 40 ms, le commit s'effectue en 2 ms, et la dur\u00e9e de lecture et d'\u00e9criture est en moyenne de 2 ms. Le 99\u00e8me percentile est \u00e0 seulement 3-3,1 ms, et le nombre de timeouts a diminu\u00e9 de 100 fois, tout cela gr\u00e2ce \u00e0 l'utilisation g\u00e9n\u00e9ralis\u00e9e de sp\u00e9culations. <\/p>\n<p>\u00c0 ce jour, la plupart des n\u0153uds SQL Server ont \u00e9t\u00e9 retir\u00e9s de l'exploitation, et les nouveaux produits sont d\u00e9velopp\u00e9s uniquement avec C*One. Nous avons adapt\u00e9 C*One pour qu'il fonctionne dans notre cloud. <noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/company\/odnoklassniki\/blog\/346868\/\">one-cloud<\/a><\/noindex>, ce qui a permis d'acc\u00e9l\u00e9rer le d\u00e9ploiement de nouveaux clusters, de simplifier la configuration et d'automatiser l'exploitation. Sans le code source, cela aurait \u00e9t\u00e9 beaucoup plus compliqu\u00e9 et bricol\u00e9. <\/p>\n<p>Nous travaillons actuellement \u00e0 la migration de nos autres entrep\u00f4ts vers le cloud - mais c'est une toute autre histoire.<br \/>\n<br \/>Source : <a content=\"nofollow\" rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/company\/odnoklassniki\/blog\/417593\/\">habr.com<\/a><\/p>","protected":false,"gt_translate_keys":[{"key":"rendered","format":"html"}]},"excerpt":{"rendered":"<p>\u0414\u043e \u043d\u0435\u0434\u0430\u0432\u043d\u0435\u0433\u043e \u0432\u0440\u0435\u043c\u0435\u043d\u0438 \u0432 \u041e\u0434\u043d\u043e\u043a\u043b\u0430\u0441\u0441\u043d\u0438\u043a\u0430\u0445 \u043e\u043a\u043e\u043b\u043e 50 \u0422\u0411 \u0434\u0430\u043d\u043d\u044b\u0445, \u043e\u0431\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u0435\u043c\u044b\u0445 \u0432 \u0440\u0435\u0430\u043b\u044c\u043d\u043e\u043c \u0432\u0440\u0435\u043c\u0435\u043d\u0438, \u0445\u0440\u0430\u043d\u0438\u043b\u043e\u0441\u044c \u0432 SQL Server. \u0414\u043b\u044f \u0442\u0430\u043a\u043e\u0433\u043e \u043e\u0431\u044a\u0435\u043c\u0430 \u043e\u0431\u0435\u0441\u043f\u0435\u0447\u0438\u0442\u044c \u0431\u044b\u0441\u0442\u0440\u044b\u0439 \u0438 \u043d\u0430\u0434\u0435\u0436\u043d\u044b\u0439, \u0434\u0430 \u0435\u0449\u0435 \u0438 \u0443\u0441\u0442\u043e\u0439\u0447\u0438\u0432\u044b\u0439 \u043a \u043e\u0442\u043a\u0430\u0437\u0443 \u0426\u041e\u0414 \u0434\u043e\u0441\u0442\u0443\u043f, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044f SQL \u0421\u0423\u0411\u0414, \u043f\u0440\u0430\u043a\u0442\u0438\u0447\u0435\u0441\u043a\u0438 \u043d\u0435\u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e. \u041e\u0431\u044b\u0447\u043d\u043e \u0432 \u0442\u0430\u043a\u0438\u0445 \u0441\u043b\u0443\u0447\u0430\u044f\u0445 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044e\u0442 \u043e\u0434\u043d\u043e \u0438\u0437 NoSQL-\u0445\u0440\u0430\u043d\u0438\u043b\u0438\u0449, \u043d\u043e \u043d\u0435 \u0432\u0441\u0451 \u043c\u043e\u0436\u043d\u043e \u043f\u0435\u0440\u0435\u043d\u0435\u0441\u0442\u0438 \u0432 NoSQL: \u043d\u0435\u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0441\u0443\u0449\u043d\u043e\u0441\u0442\u0438 \u0442\u0440\u0435\u0431\u0443\u044e\u0442 [&hellip;]<\/p>\n","protected":false,"gt_translate_keys":[{"key":"rendered","format":"html"}]},"author":1,"featured_media":28484,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[688],"tags":[],"class_list":["post-37956","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.1 - aioseo.com -->\n\t<meta name=\"description\" content=\"\u0414\u043e \u043d\u0435\u0434\u0430\u0432\u043d\u0435\u0433\u043e \u0432\u0440\u0435\u043c\u0435\u043d\u0438 \u0432 \u041e\u0434\u043d\u043e\u043a\u043b\u0430\u0441\u0441\u043d\u0438\u043a\u0430\u0445 \u043e\u043a\u043e\u043b\u043e 50 \u0422\u0411 \u0434\u0430\u043d\u043d\u044b\u0445, \u043e\u0431\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u0435\u043c\u044b\u0445.\" \/>\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\/newsql-nosql-acid\" \/>\n\t<meta name=\"generator\" content=\"All in One SEO (AIOSEO) 5.0.2.1\" \/>\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\udd47NewSQL = NoSQL+ACID | ProHoster\" \/>\n\t\t<meta property=\"og:description\" content=\"\u0414\u043e \u043d\u0435\u0434\u0430\u0432\u043d\u0435\u0433\u043e \u0432\u0440\u0435\u043c\u0435\u043d\u0438 \u0432 \u041e\u0434\u043d\u043e\u043a\u043b\u0430\u0441\u0441\u043d\u0438\u043a\u0430\u0445 \u043e\u043a\u043e\u043b\u043e 50 \u0422\u0411 \u0434\u0430\u043d\u043d\u044b\u0445, \u043e\u0431\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u0435\u043c\u044b\u0445.\" \/>\n\t\t<meta property=\"og:url\" content=\"https:\/\/prohoster.info\/fr\/blog\/administrirovanie\/newsql-nosql-acid\" \/>\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=\"2019-10-31T19:20:47+00:00\" \/>\n\t\t<meta property=\"article:modified_time\" content=\"2019-10-31T19:20:47+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\udd47NewSQL = NoSQL+ACID | ProHoster","description":"Jusqu'\u00e0 r\u00e9cemment, il y avait environ 50 To de donn\u00e9es trait\u00e9es dans Odnoklassniki.","canonical_url":"https:\/\/prohoster.info\/fr\/blog\/administrirovanie\/newsql-nosql-acid","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\udd47NewSQL = NoSQL+ACID | ProHoster","og:description":"\u0414\u043e \u043d\u0435\u0434\u0430\u0432\u043d\u0435\u0433\u043e \u0432\u0440\u0435\u043c\u0435\u043d\u0438 \u0432 \u041e\u0434\u043d\u043e\u043a\u043b\u0430\u0441\u0441\u043d\u0438\u043a\u0430\u0445 \u043e\u043a\u043e\u043b\u043e 50 \u0422\u0411 \u0434\u0430\u043d\u043d\u044b\u0445, \u043e\u0431\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u0435\u043c\u044b\u0445.","og:url":"https:\/\/prohoster.info\/fr\/blog\/administrirovanie\/newsql-nosql-acid","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":"2019-10-31T19:20:47+00:00","article:modified_time":"2019-10-31T19:20:47+00:00","article:publisher":"https:\/\/www.facebook.com\/prohoster","article:author":"https:\/\/www.facebook.com\/prohoster"},"aioseo_meta_data":{"post_id":"37956","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":"2026-01-23 19:55:19","breadcrumb_settings":null,"limit_modified_date":false,"reviewed_by":null,"ai":null,"created":"2021-03-01 01:17:23","updated":"2026-01-23 19:55:19","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\/37956","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=37956"}],"version-history":[{"count":0,"href":"https:\/\/prohoster.info\/fr\/wp-json\/wp\/v2\/posts\/37956\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/prohoster.info\/fr\/wp-json\/wp\/v2\/media\/28484"}],"wp:attachment":[{"href":"https:\/\/prohoster.info\/fr\/wp-json\/wp\/v2\/media?parent=37956"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/prohoster.info\/fr\/wp-json\/wp\/v2\/categories?post=37956"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/prohoster.info\/fr\/wp-json\/wp\/v2\/tags?post=37956"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}