1"use strict";(self.webpackChunkrabbitmq_website=self.webpackChunkrabbitmq_website||[]).push([["17177"],{32421(e,n,t){t.r(n),t.d(n,{metadata:()=>i,default:()=>h,frontMatter:()=>o,contentTitle:()=>r,toc:()=>l,assets:()=>c});var i=JSON.parse('{"id":"metadata-store/everyday-operations","title":"Everyday Operations with Khepri","description":"Even though the metadata store doesn\u2019t store messages, its behavior will affect","source":"@site/versioned_docs/version-4.3/metadata-store/everyday-operations.md","sourceDirName":"metadata-store","slug":"/metadata-store/everyday-operations","permalink":"/docs/metadata-store/everyday-operations","draft":false,"unlisted":false,"editUrl":"https://github.com/rabbitmq/rabbitmq-website/tree/main/versioned_docs/version-4.3/metadata-store/everyday-operations.md","tags":[],"version":"4.3","frontMatter":{"title":"Everyday Operations with Khepri"},"sidebar":"docsSidebar","previous":{"title":"Clustering and Khepri","permalink":"/docs/metadata-store/clustering"},"next":{"title":"Failure recovery with Khepri","permalink":"/docs/metadata-store/failure-recovery"}}'),s=t(74848),a=t(28453);let o={title:"Everyday Operations with Khepri"},r="Everyday Operations with Khepri",c={},l=[{value:"Consistency Model and Visibility Guarantees",id:"consistency-model-and-visibility-guarantees",level:2},{value:"Example Scenarios",id:"example-scenarios",level:3},{value:"Workaround Strategies",id:"workaround-strategies",level:3},{value:"Client Resource Declaration on the Cluster Minority Side",id:"client-resource-declaration-on-the-cluster-minority-side",level:2}];function d(e){let n={a:"a",code:"code",h1:"h1",h2:"h2",h3:"h3",header:"header",li:"li",ol:"ol",p:"p",pre:"pre",strong:"strong",...(0,a.R)(),...e.components};return(0,s.jsxs)(s.Fragment,{children:[(0,s.jsx)(n.header,{children:(0,s.jsx)(n.h1,{id:"everyday-operations-with-khepri",children:"Everyday Operations with Khepri"})}),"\n",(0,s.jsx)(n.p,{children:"Even though the metadata store doesn\u2019t store messages, its behavior will affect\nday-to-day behavior and technical operations of a RabbitMQ that uses it, at\nleast as long as applications that use this cluster will want to authenticate\nand declare or delete resources such as queues or streams."}),"\n",(0,s.jsxs)(n.p,{children:["As already mentioned in ",(0,s.jsx)(n.a,{href:"./clustering#caveats",children:"clustering caveats"}),", Khepri is a\nRaft-based system, and just like in any Raft-based system, a quorum number of\ncluster members must be online and available for the metadata store to accept\nupdates (writes/deletes or cluster membership changes)."]}),"\n",(0,s.jsx)(n.h2,{id:"consistency-model-and-visibility-guarantees",children:"Consistency Model and Visibility Guarantees"}),"\n",(0,s.jsx)(n.p,{children:"Khepri has an important difference from Mnesia when it comes to schema\nmodifications such as queue or stream declarations, or binding declarations.\nThese changes won\u2019t be noticeable with many workloads but can affect some, in\nparticular, certain integration tests."}),"\n",(0,s.jsx)(n.h3,{id:"example-scenarios",children:"Example Scenarios"}),"\n",(0,s.jsx)(n.p,{children:"Consider two scenarios, A and B."}),"\n",(0,s.jsxs)("figure",{style:{width:"inherit"},children:[(0,s.jsxs)("div",{className:"figure",children:[(0,s.jsx)(n.p,{children:"There is only one client. The client performs the following steps:"}),(0,s.jsxs)(n.ol,{children:["\n",(0,s.jsx)(n.li,{children:"It declares a queue Q."}),"\n",(0,s.jsx)(n.li,{children:"It binds Q to an exchange X."}),"\n",(0,s.jsx)(n.li,{children:"It publishes a message M to the exchange X."}),"\n",(0,s.jsx)(n.li,{children:"It expects the message to be routed to queue Q."}),"\n",(0,s.jsx)(n.li,{children:"It consumes the message."}),"\n"]})]}),(0,s.jsx)("figcaption",{children:"Scenario A"})]}),"\n",(0,s.jsx)(n.p,{children:"In this scenario, there should be no observable difference in behavior.\nClient\u2019s expectations will be met."}),"\n",(0,s.jsxs)("figure",{style:{width:"inherit"},children:[(0,s.jsxs)("div",{className:"figure",children:[(0,s.jsx)(n.p,{children:"There are two clients, One and Two, connected to nodes R1 and R3, and using the\nsame virtual host. Node R2 has no client connections."}),(0,s.jsxs)(n.ol,{children:["\n",(0,s.jsx)(n.li,{children:"Client One declares a queue Q."}),"\n",(0,s.jsx)(n.li,{children:"It binds Q to an exchange X."}),"\n",(0,s.jsx)(n.li,{children:"It gets queue and binding declaration confirmations back."}),"\n",(0,s.jsx)(n.li,{children:"It notifies Client Two or Client Two implicitly finds out that it has\nfinished the steps above (for example, in an integration test)."}),"\n",(0,s.jsx)(n.li,{children:"Client Two publishes a message M to X."}),"\n",(0,s.jsx)(n.li,{children:"Clients One and Two expect the message to be routed to Q."}),"\n"]})]}),(0,s.jsx)("figcaption",{children:"Scenario B"})]}),"\n",(0,s.jsxs)(n.p,{children:["In this scenario, on step three Mnesia would return when ",(0,s.jsx)(n.strong,{children:"all"})," cluster nodes\nhave committed an update. Khepri, however, will return when ",(0,s.jsx)(n.strong,{children:"a majority"})," of\nnodes, including the node handling Client One\u2019s operations, have returned."]}),"\n",(0,s.jsxs)(n.p,{children:["This may include nodes R1 and R2 but not node R3, meaning that message M\npublished by Client Two connected to node R3 in the above example ",(0,s.jsx)(n.strong,{children:"is not\nguaranteed to be routed"}),"."]}),"\n",(0,s.jsxs)(n.p,{children:["Once all schema changes propagate to node R3, Client Two\u2019s subsequent\npublishes on node R3 ",(0,s.jsx)(n.strong,{children:"will be guaranteed"})," to be routed."]}),"\n",(0,s.jsx)(n.p,{children:"This trade-off of a Raft-based system that assumes that a
1write accepted by a\nmajority of nodes can be considered a success."}),"\n",(0,s.jsx)(n.h3,{id:"workaround-strategies",children:"Workaround Strategies"}),"\n",(0,s.jsxs)(n.p,{children:["To satisfy Client Two's expectations in scenario B, Khepri could perform\n",(0,s.jsx)(n.strong,{children:"consistent"})," (involving a majority of replicas) queries of bindings when\nrouting messages but that would have a ",(0,s.jsx)(n.strong,{children:"significant"})," impact on throughput of\ncertain protocols (such as MQTT) and exchange/destination types (anything that\nresembles a topic exchange in AMQP 0-9-1)."]}),"\n",(0,s.jsx)(n.p,{children:"Applications that rely on multiple connections that depend on a shared topology\nhave several coping strategies."}),"\n",(0,s.jsx)(n.p,{children:"If an application uses two or more connections to different nodes, it can\ndeclare its topology on boot and then injecting a short pause (1-2 seconds)\nbefore proceeding with other operations."}),"\n",(0,s.jsx)(n.p,{children:'Applications that rely on dynamic topologies can switch to use a "static" set\nof exchanges and bindings.'}),"\n",(0,s.jsx)(n.p,{children:"Application components that do not need to use a shared topology can each\nconfigure its own queues/streams/bindings."}),"\n",(0,s.jsx)(n.p,{children:"Test suites that use multiple connections to different nodes can choose to use\njust one connection or connect to the same node, or inject a pause, or await a\ncertain condition that indicates that the topology is in place."}),"\n",(0,s.jsx)(n.h2,{id:"client-resource-declaration-on-the-cluster-minority-side",children:"Client Resource Declaration on the Cluster Minority Side"}),"\n",(0,s.jsx)(n.p,{children:"The topology that defines how messages are routed is stored in the metadata\nstore. The way to declare a resource remains the same regardless of the active\nmetadata store backend."}),"\n",(0,s.jsx)(n.p,{children:"However, where it was possible to declare a queue with Mnesia even if the\nmajority of the cluster was either down or unreachable, the same operation with\nKhepri will timeout. This way the client can react appropriately to the issue,\ninstead of hoping for the best with the various network partition recovery\nstrategies that are implemented on top of Mnesia."}),"\n",(0,s.jsx)(n.p,{children:"Here is an example of the PerfTest tool trying to declare the exchange and\nqueue it needs in a cluster where only 1 out of 5 nodes is running:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-bash",children:"./scripts/PerfTest\n\n# => id: test-161339-979, starting consumer #0\n# => id: test-161339-979, starting consumer #0, channel #0\n# => Main thread caught exception: java.io.IOException\n# => 16:14:10.638 [com.rabbitmq.perf.PerfTest.main()] ERROR com.rabbitmq.perf.PerfTest - Main thread caught exception\n# => (...)\n"})}),"\n",(0,s.jsx)(n.p,{children:"At the same time, the RabbitMQ node logged the following messages:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{children:"[error] <0.1373.0> Error on AMQP connection <0.1373.0> (127.0.0.1:55165 -> 127.0.0.1:5672 - perf-test-consumer-0, vhost: '/', user: 'guest', state: running), channel 1:\n[error] <0.1373.0> operation exchange.declare caused a connection exception internal_error: \"failed to declare exchange 'direct' in vhost '/' because the operation timed out\"\n"})})]})}function h(e={}){let{wrapper:n}={...(0,a.R)(),...e.components};return n?(0,s.jsx)(n,{...e,children:(0,s.jsx)(d,{...e})}):d(e)}},28453(e,n,t){t.d(n,{R:()=>o,x:()=>r});var i=t(96540);let s={},a=i.createContext(s);function o(e){let n=i.useContext(a);return i.useMemo(function(){return"function"==typeof e?e(n):{...n,...e}},[n,e])}function r(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(s):e.components||s:o(e.components),i.createElement(a.Provider,{value:n},e.children)}}}]);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.