blob: 9dfe2260a9e519b3d1c45281eb04142e08aada58 [file] [log] [blame]
"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[8442],{3905:(e,t,r)=>{r.d(t,{Zo:()=>d,kt:()=>m});var n=r(67294);function a(e,t,r){return t in e?Object.defineProperty(e,t,{value:r,enumerable:!0,configurable:!0,writable:!0}):e[t]=r,e}function i(e,t){var r=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),r.push.apply(r,n)}return r}function o(e){for(var t=1;t<arguments.length;t++){var r=null!=arguments[t]?arguments[t]:{};t%2?i(Object(r),!0).forEach((function(t){a(e,t,r[t])})):Object.getOwnPropertyDescriptors?Object.defineProperties(e,Object.getOwnPropertyDescriptors(r)):i(Object(r)).forEach((function(t){Object.defineProperty(e,t,Object.getOwnPropertyDescriptor(r,t))}))}return e}function s(e,t){if(null==e)return{};var r,n,a=function(e,t){if(null==e)return{};var r,n,a={},i=Object.keys(e);for(n=0;n<i.length;n++)r=i[n],t.indexOf(r)>=0||(a[r]=e[r]);return a}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(n=0;n<i.length;n++)r=i[n],t.indexOf(r)>=0||Object.prototype.propertyIsEnumerable.call(e,r)&&(a[r]=e[r])}return a}var u=n.createContext({}),l=function(e){var t=n.useContext(u),r=t;return e&&(r="function"==typeof e?e(t):o(o({},t),e)),r},d=function(e){var t=l(e.components);return n.createElement(u.Provider,{value:t},e.children)},p="mdxType",c={inlineCode:"code",wrapper:function(e){var t=e.children;return n.createElement(n.Fragment,{},t)}},h=n.forwardRef((function(e,t){var r=e.components,a=e.mdxType,i=e.originalType,u=e.parentName,d=s(e,["components","mdxType","originalType","parentName"]),p=l(r),h=a,m=p["".concat(u,".").concat(h)]||p[h]||c[h]||i;return r?n.createElement(m,o(o({ref:t},d),{},{components:r})):n.createElement(m,o({ref:t},d))}));function m(e,t){var r=arguments,a=t&&t.mdxType;if("string"==typeof e||a){var i=r.length,o=new Array(i);o[0]=h;var s={};for(var u in t)hasOwnProperty.call(t,u)&&(s[u]=t[u]);s.originalType=e,s[p]="string"==typeof e?e:a,o[1]=s;for(var l=2;l<i;l++)o[l]=r[l];return n.createElement.apply(null,o)}return n.createElement.apply(null,r)}h.displayName="MDXCreateElement"},89654:(e,t,r)=>{r.r(t),r.d(t,{assets:()=>d,contentTitle:()=>u,default:()=>m,frontMatter:()=>s,metadata:()=>l,toc:()=>p});var n=r(87462),a=r(63366),i=(r(67294),r(3905)),o=["components"],s={id:"router",title:"Router Process"},u=void 0,l={unversionedId:"design/router",id:"design/router",title:"Router Process",description:"\x3c!--",source:"@site/docs/27.0.0/design/router.md",sourceDirName:"design",slug:"/design/router",permalink:"/docs/27.0.0/design/router",draft:!1,tags:[],version:"current",frontMatter:{id:"router",title:"Router Process"}},d={},p=[{value:"Configuration",id:"configuration",level:3},{value:"HTTP endpoints",id:"http-endpoints",level:3},{value:"Running",id:"running",level:3},{value:"Router as management proxy",id:"router-as-management-proxy",level:3},{value:"Enabling the management proxy",id:"enabling-the-management-proxy",level:4},{value:"Management proxy routing",id:"management-proxy-routing",level:4},{value:"Router strategies",id:"router-strategies",level:3},{value:"timeBoundary",id:"timeboundary",level:4},{value:"priority",id:"priority",level:4},{value:"manual",id:"manual",level:4},{value:"JavaScript",id:"javascript",level:4},{value:"Routing of SQL queries using strategies",id:"routing-of-sql-queries-using-strategies",level:3},{value:"Avatica query balancing",id:"avatica-query-balancing",level:3},{value:"Rendezvous hash balancer",id:"rendezvous-hash-balancer",level:4},{value:"Consistent hash balancer",id:"consistent-hash-balancer",level:4},{value:"Example production configuration",id:"example-production-configuration",level:3}],c={toc:p},h="wrapper";function m(e){var t=e.components,r=(0,a.Z)(e,o);return(0,i.kt)(h,(0,n.Z)({},c,r,{components:t,mdxType:"MDXLayout"}),(0,i.kt)("p",null,"The Apache Druid Router process can be used to route queries to different Broker processes. By default, the broker routes queries based on how ",(0,i.kt)("a",{parentName:"p",href:"/docs/27.0.0/operations/rule-configuration"},"Rules")," are set up. For example, if 1 month of recent data is loaded into a ",(0,i.kt)("inlineCode",{parentName:"p"},"hot")," cluster, queries that fall within the recent month can be routed to a dedicated set of brokers. Queries outside this range are routed to another set of brokers. This set up provides query isolation such that queries for more important data are not impacted by queries for less important data."),(0,i.kt)("p",null,"For query routing purposes, you should only ever need the Router process if you have a Druid cluster well into the terabyte range."),(0,i.kt)("p",null,"In addition to query routing, the Router also runs the ",(0,i.kt)("a",{parentName:"p",href:"/docs/27.0.0/operations/web-console"},"web console"),", a management UI for datasources, segments, tasks, data processes (Historicals and MiddleManagers), and coordinator dynamic configuration. The user can also run SQL and native Druid queries within the console."),(0,i.kt)("h3",{id:"configuration"},"Configuration"),(0,i.kt)("p",null,"For Apache Druid Router Process Configuration, see ",(0,i.kt)("a",{parentName:"p",href:"/docs/27.0.0/configuration/#router"},"Router Configuration"),"."),(0,i.kt)("p",null,"For basic tuning guidance for the Router process, see ",(0,i.kt)("a",{parentName:"p",href:"/docs/27.0.0/operations/basic-cluster-tuning#router"},"Basic cluster tuning"),"."),(0,i.kt)("h3",{id:"http-endpoints"},"HTTP endpoints"),(0,i.kt)("p",null,"For a list of API endpoints supported by the Router, see ",(0,i.kt)("a",{parentName:"p",href:"/docs/27.0.0/api-reference/legacy-metadata-api#datasource-information"},"Legacy metadata API reference"),"."),(0,i.kt)("h3",{id:"running"},"Running"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre"},"org.apache.druid.cli.Main server router\n")),(0,i.kt)("h3",{id:"router-as-management-proxy"},"Router as management proxy"),(0,i.kt)("p",null,"The Router can be configured to forward requests to the active Coordinator or Overlord process. This may be useful for\nsetting up a highly available cluster in situations where the HTTP redirect mechanism of the inactive -> active\nCoordinator/Overlord does not function correctly (servers are behind a load balancer, the hostname used in the redirect\nis only resolvable internally, etc.)."),(0,i.kt)("h4",{id:"enabling-the-management-proxy"},"Enabling the management proxy"),(0,i.kt)("p",null,"To enable this functionality, set the following in the Router's runtime.properties:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre"},"druid.router.managementProxy.enabled=true\n")),(0,i.kt)("h4",{id:"management-proxy-routing"},"Management proxy routing"),(0,i.kt)("p",null,"The management proxy supports implicit and explicit routes. Implicit routes are those where the destination can be\ndetermined from the original request path based on Druid API path conventions. For the Coordinator the convention is\n",(0,i.kt)("inlineCode",{parentName:"p"},"/druid/coordinator/*")," and for the Overlord the convention is ",(0,i.kt)("inlineCode",{parentName:"p"},"/druid/indexer/*"),". These are convenient because they mean\nthat using the management proxy does not require modifying the API request other than issuing the request to the Router\ninstead of the Coordinator or Overlord. Most Druid API requests can be routed implicitly."),(0,i.kt)("p",null,"Explicit routes are those where the request to the Router contains a path prefix indicating which process the request\nshould be routed to. For the Coordinator this prefix is ",(0,i.kt)("inlineCode",{parentName:"p"},"/proxy/coordinator")," and for the Overlord it is ",(0,i.kt)("inlineCode",{parentName:"p"},"/proxy/overlord"),".\nThis is required for API calls with an ambiguous destination. For example, the ",(0,i.kt)("inlineCode",{parentName:"p"},"/status")," API is present on all Druid\nprocesses, so explicit routing needs to be used to indicate the proxy destination."),(0,i.kt)("p",null,"This is summarized in the table below:"),(0,i.kt)("table",null,(0,i.kt)("thead",{parentName:"table"},(0,i.kt)("tr",{parentName:"thead"},(0,i.kt)("th",{parentName:"tr",align:null},"Request Route"),(0,i.kt)("th",{parentName:"tr",align:null},"Destination"),(0,i.kt)("th",{parentName:"tr",align:null},"Rewritten Route"),(0,i.kt)("th",{parentName:"tr",align:null},"Example"))),(0,i.kt)("tbody",{parentName:"table"},(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"/druid/coordinator/*")),(0,i.kt)("td",{parentName:"tr",align:null},"Coordinator"),(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"/druid/coordinator/*")),(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"router:8888/druid/coordinator/v1/datasources")," -> ",(0,i.kt)("inlineCode",{parentName:"td"},"coordinator:8081/druid/coordinator/v1/datasources"))),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"/druid/indexer/*")),(0,i.kt)("td",{parentName:"tr",align:null},"Overlord"),(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"/druid/indexer/*")),(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"router:8888/druid/indexer/v1/task")," -> ",(0,i.kt)("inlineCode",{parentName:"td"},"overlord:8090/druid/indexer/v1/task"))),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"/proxy/coordinator/*")),(0,i.kt)("td",{parentName:"tr",align:null},"Coordinator"),(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"/*")),(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"router:8888/proxy/coordinator/status")," -> ",(0,i.kt)("inlineCode",{parentName:"td"},"coordinator:8081/status"))),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"/proxy/overlord/*")),(0,i.kt)("td",{parentName:"tr",align:null},"Overlord"),(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"/*")),(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"router:8888/proxy/overlord/druid/indexer/v1/isLeader")," -> ",(0,i.kt)("inlineCode",{parentName:"td"},"overlord:8090/druid/indexer/v1/isLeader"))))),(0,i.kt)("h3",{id:"router-strategies"},"Router strategies"),(0,i.kt)("p",null,"The Router has a configurable list of strategies for how it selects which Brokers to route queries to. The order of the strategies matter because as soon as a strategy condition is matched, a Broker is selected."),(0,i.kt)("h4",{id:"timeboundary"},"timeBoundary"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-json"},'{\n "type":"timeBoundary"\n}\n')),(0,i.kt)("p",null,"Including this strategy means all timeBoundary queries are always routed to the highest priority Broker."),(0,i.kt)("h4",{id:"priority"},"priority"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-json"},'{\n "type":"priority",\n "minPriority":0,\n "maxPriority":1\n}\n')),(0,i.kt)("p",null,"Queries with a priority set to less than minPriority are routed to the lowest priority Broker. Queries with priority set to greater than maxPriority are routed to the highest priority Broker. By default, minPriority is 0 and maxPriority is 1. Using these default values, if a query with priority 0 (the default query priority is 0) is sent, the query skips the priority selection logic."),(0,i.kt)("h4",{id:"manual"},"manual"),(0,i.kt)("p",null,"This strategy reads the parameter ",(0,i.kt)("inlineCode",{parentName:"p"},"brokerService")," from the query context and routes the query to that broker service. If no valid ",(0,i.kt)("inlineCode",{parentName:"p"},"brokerService")," is specified in the query context, the field ",(0,i.kt)("inlineCode",{parentName:"p"},"defaultManualBrokerService")," is used to determine target broker service given the value is valid and non-null. A value is considered valid if it is present in ",(0,i.kt)("inlineCode",{parentName:"p"},"druid.router.tierToBrokerMap"),"\nThis strategy can route both Native and SQL queries (when enabled)."),(0,i.kt)("p",null,(0,i.kt)("em",{parentName:"p"},"Example"),': A strategy that routes queries to the Broker "druid:broker-hot" if no valid ',(0,i.kt)("inlineCode",{parentName:"p"},"brokerService")," is found in the query context."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-json"},'{\n "type": "manual",\n "defaultManualBrokerService": "druid:broker-hot"\n}\n')),(0,i.kt)("h4",{id:"javascript"},"JavaScript"),(0,i.kt)("p",null,"Allows defining arbitrary routing rules using a JavaScript function. The function is passed the configuration and the query to be executed, and returns the tier it should be routed to, or null for the default tier."),(0,i.kt)("p",null,(0,i.kt)("em",{parentName:"p"},"Example"),": a function that sends queries containing more than three aggregators to the lowest priority Broker."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-json"},'{\n "type" : "javascript",\n "function" : "function (config, query) { if (query.getAggregatorSpecs && query.getAggregatorSpecs().size() >= 3) { var size = config.getTierToBrokerMap().values().size(); if (size > 0) { return config.getTierToBrokerMap().values().toArray()[size-1] } else { return config.getDefaultBrokerServiceName() } } else { return null } }"\n}\n')),(0,i.kt)("admonition",{type:"info"},(0,i.kt)("p",{parentName:"admonition"}," JavaScript-based functionality is disabled by default. Please refer to the Druid ",(0,i.kt)("a",{parentName:"p",href:"/docs/27.0.0/development/javascript"},"JavaScript programming guide")," for guidelines about using Druid's JavaScript functionality, including instructions on how to enable it.")),(0,i.kt)("h3",{id:"routing-of-sql-queries-using-strategies"},"Routing of SQL queries using strategies"),(0,i.kt)("p",null,"To enable routing of SQL queries using strategies, set ",(0,i.kt)("inlineCode",{parentName:"p"},"druid.router.sql.enable")," to ",(0,i.kt)("inlineCode",{parentName:"p"},"true"),". The broker service for a\ngiven SQL query is resolved using only the provided Router strategies. If not resolved using any of the strategies, the\nRouter uses the ",(0,i.kt)("inlineCode",{parentName:"p"},"defaultBrokerServiceName"),". This behavior is slightly different from native queries where the Router\nfirst tries to resolve the broker service using strategies, then load rules and finally using the ",(0,i.kt)("inlineCode",{parentName:"p"},"defaultBrokerServiceName"),"\nif still not resolved. When ",(0,i.kt)("inlineCode",{parentName:"p"},"druid.router.sql.enable")," is set to ",(0,i.kt)("inlineCode",{parentName:"p"},"false")," (default value), the Router uses the\n",(0,i.kt)("inlineCode",{parentName:"p"},"defaultBrokerServiceName"),"."),(0,i.kt)("p",null,"Setting ",(0,i.kt)("inlineCode",{parentName:"p"},"druid.router.sql.enable")," does not affect either Avatica JDBC requests or native queries.\nDruid always routes native queries using the strategies and load rules as documented.\nDruid always routes Avatica JDBC requests based on connection ID."),(0,i.kt)("h3",{id:"avatica-query-balancing"},"Avatica query balancing"),(0,i.kt)("p",null,"All Avatica JDBC requests with a given connection ID must be routed to the same Broker, since Druid Brokers do not share connection state with each other."),(0,i.kt)("p",null,"To accomplish this, Druid provides two built-in balancers that use rendezvous hashing and consistent hashing of a request's connection ID respectively to assign requests to Brokers."),(0,i.kt)("p",null,"Note that when multiple Routers are used, all Routers should have identical balancer configuration to ensure that they make the same routing decisions."),(0,i.kt)("h4",{id:"rendezvous-hash-balancer"},"Rendezvous hash balancer"),(0,i.kt)("p",null,"This balancer uses ",(0,i.kt)("a",{parentName:"p",href:"https://en.wikipedia.org/wiki/Rendezvous_hashing"},"Rendezvous Hashing")," on an Avatica request's connection ID to assign the request to a Broker."),(0,i.kt)("p",null,"To use this balancer, specify the following property:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre"},"druid.router.avatica.balancer.type=rendezvousHash\n")),(0,i.kt)("p",null,"If no ",(0,i.kt)("inlineCode",{parentName:"p"},"druid.router.avatica.balancer")," property is set, the Router will also default to using the Rendezvous Hash Balancer."),(0,i.kt)("h4",{id:"consistent-hash-balancer"},"Consistent hash balancer"),(0,i.kt)("p",null,"This balancer uses ",(0,i.kt)("a",{parentName:"p",href:"https://en.wikipedia.org/wiki/Consistent_hashing"},"Consistent Hashing")," on an Avatica request's connection ID to assign the request to a Broker."),(0,i.kt)("p",null,"To use this balancer, specify the following property:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre"},"druid.router.avatica.balancer.type=consistentHash\n")),(0,i.kt)("p",null,"This is a non-default implementation that is provided for experimentation purposes. The consistent hasher has longer setup times on initialization and when the set of Brokers changes, but has a faster Broker assignment time than the rendezvous hasher when tested with 5 Brokers. Benchmarks for both implementations have been provided in ",(0,i.kt)("inlineCode",{parentName:"p"},"ConsistentHasherBenchmark")," and ",(0,i.kt)("inlineCode",{parentName:"p"},"RendezvousHasherBenchmark"),". The consistent hasher also requires locking, while the rendezvous hasher does not."),(0,i.kt)("h3",{id:"example-production-configuration"},"Example production configuration"),(0,i.kt)("p",null,"In this example, we have two tiers in our production cluster: ",(0,i.kt)("inlineCode",{parentName:"p"},"hot")," and ",(0,i.kt)("inlineCode",{parentName:"p"},"_default_tier"),". Queries for the ",(0,i.kt)("inlineCode",{parentName:"p"},"hot")," tier are routed through the ",(0,i.kt)("inlineCode",{parentName:"p"},"broker-hot")," set of Brokers, and queries for the ",(0,i.kt)("inlineCode",{parentName:"p"},"_default_tier")," are routed through the ",(0,i.kt)("inlineCode",{parentName:"p"},"broker-cold")," set of Brokers. If any exceptions or network problems occur, queries are routed to the ",(0,i.kt)("inlineCode",{parentName:"p"},"broker-cold")," set of brokers. In our example, we are running with a c3.2xlarge EC2 instance. We assume a ",(0,i.kt)("inlineCode",{parentName:"p"},"common.runtime.properties")," already exists."),(0,i.kt)("p",null,"JVM settings:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre"},"-server\n-Xmx13g\n-Xms13g\n-XX:NewSize=256m\n-XX:MaxNewSize=256m\n-XX:+UseConcMarkSweepGC\n-XX:+PrintGCDetails\n-XX:+PrintGCTimeStamps\n-XX:+UseLargePages\n-XX:+HeapDumpOnOutOfMemoryError\n-XX:HeapDumpPath=/mnt/galaxy/deploy/current/\n-Duser.timezone=UTC\n-Dfile.encoding=UTF-8\n-Djava.io.tmpdir=/mnt/tmp\n\n-Dcom.sun.management.jmxremote.port=17071\n-Dcom.sun.management.jmxremote.authenticate=false\n-Dcom.sun.management.jmxremote.ssl=false\n")),(0,i.kt)("p",null,"Runtime.properties:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre"},'druid.host=#{IP_ADDR}:8080\ndruid.plaintextPort=8080\ndruid.service=druid/router\n\ndruid.router.defaultBrokerServiceName=druid:broker-cold\ndruid.router.coordinatorServiceName=druid:coordinator\ndruid.router.tierToBrokerMap={"hot":"druid:broker-hot","_default_tier":"druid:broker-cold"}\ndruid.router.http.numConnections=50\ndruid.router.http.readTimeout=PT5M\n\n# Number of threads used by the Router proxy http client\ndruid.router.http.numMaxThreads=100\n\ndruid.server.http.numThreads=100\n')))}m.isMDXComponent=!0}}]);