|
| 1 | +--- |
| 2 | +title: lookup-cluster-mode |
| 3 | +target-version: x.x.x |
| 4 | +--- |
| 5 | + |
| 6 | +# Lookup Cluster Mode |
| 7 | + |
| 8 | +## Summary |
| 9 | + |
| 10 | +In production environment, server clusters are necessary to handle large amount of workloads with ensuring high availability, reliability, and scalability. |
| 11 | + |
| 12 | +Yorkie provides two cluster mode options to support production environment: Broadcast cluster mode and Lookup cluster mode. |
| 13 | + |
| 14 | +Broadcast cluster mode is based on broadcasting, pub/sub, and distributed lock between servers. This cluster mode can handle certain amount of workloads, but there are limitations like broadcast, pub/sub, lock overheads to synchronize workloads. Due to these limitations, broadcast cluster mode may be not enough for production mode. |
| 15 | + |
| 16 | +The root cause of these limitations is the fact that because workloads are distributed throughout all server clusters, therefore additional synchronization throughout all servers is needed. |
| 17 | + |
| 18 | +Lookup cluster mode’s main idea is to assign each server to process same workloads to avoid multiple servers accessing to same data, and put lookup system to route same workloads to same servers. |
| 19 | + |
| 20 | +Lookup cluster mode can reduce/remove additional overheads needed for workload synchronization in previous broadcast cluster mode, and become capable of handling large amount of workloads with ensuring high availability, reliability, and scalability. |
| 21 | + |
| 22 | +### Goals |
| 23 | + |
| 24 | +Provide lookup cluster mode on server when user deploys K8s manifest files to Kubernetes cluster. |
| 25 | + |
| 26 | +Provide various methods to deploy lookup cluster mode. |
| 27 | + |
| 28 | +- `Helm` chart to simply deploy cluster mode on K8s |
| 29 | +- `Kustomization` to customize manifest and deploy cluster mode on K8s. |
| 30 | + |
| 31 | +### Non-Goals |
| 32 | + |
| 33 | +Additional configuration for specific CSP(Cloud Service Provider) is not provided. |
| 34 | + |
| 35 | +Only manifests, configurations, and guides for deploying lookup cluster mode in K8s environment are provided. |
| 36 | + |
| 37 | +## Proposal Details |
| 38 | + |
| 39 | +> This is where we detail how to use the feature with snippet or API and describe |
| 40 | +the internal implementation. |
| 41 | +> |
| 42 | +
|
| 43 | +### How to use |
| 44 | + |
| 45 | +There are two ways to deploy lookup cluster mode on K8s environment. |
| 46 | + |
| 47 | +1. Helm Chart |
| 48 | + |
| 49 | + User can deploy lookup cluster mode with following command: |
| 50 | + |
| 51 | + ```bash |
| 52 | + # Add Helm chart repositories |
| 53 | + $ helm repo add stable https://charts.helm.sh/stable |
| 54 | + |
| 55 | + # Install Yorkie Lookup cluster mode Chart |
| 56 | + $ helm install stable/yorkie |
| 57 | + ``` |
| 58 | + |
| 59 | +2. Manual Deploy (Kustomization) |
| 60 | + |
| 61 | + User can manually deploy lookup cluster mode with following command: |
| 62 | + |
| 63 | + ```bash |
| 64 | + # If you have not installed istioctl then you must install istioctl. |
| 65 | + $ curl -L https://istio.io/downloadIstio | sh - |
| 66 | + |
| 67 | + # Copy istioctl in path. |
| 68 | + $ cp ~/.istioctl/bin/istioctl ~/bin |
| 69 | + |
| 70 | + # Deploy Istio System with IstioOperator |
| 71 | + $ istioctl install -f cluster/istio/istio-operator.yaml |
| 72 | + |
| 73 | + # Set auto envoy sidecar injetion in namespace |
| 74 | + $ kubectl label namespace yorkie istio-injection=enabled |
| 75 | + |
| 76 | + # Deploy Istio configuration files |
| 77 | + $ kubectl create -f cluster/istio |
| 78 | + |
| 79 | + # Create Namespace |
| 80 | + $ kubectl create namespace yorkie |
| 81 | + |
| 82 | + # Deploying Yorkie, MongoDB |
| 83 | + $ kubectl create -k apps/yorkie |
| 84 | + ``` |
| 85 | + |
| 86 | + |
| 87 | +### How does it work? |
| 88 | + |
| 89 | +**System Design & Architecture** |
| 90 | + |
| 91 | +The architecture design of lookup cluster mode is as follows: |
| 92 | + |
| 93 | + |
| 94 | + |
| 95 | +- `Yorkies`: Yorkies is router(proxy) responsible for two tasks. |
| 96 | + - Routing based on request: Yorkies receives requests from client and route to server based on computed request’s hash key and ring hash algorithm. |
| 97 | + - Stream aggregation: Yorkies recieves two watch API streams from different servers and aggregate into one stream. |
| 98 | +- `Yorkie service registry`: Service registry is responsible for storing metadata and configuration settings of yorkie servers just like mongoDB’s config servers. |
| 99 | +- `Yorkie Service(s)`: Services to process specific workloads. There can be two type of service structure. |
| 100 | + - Single Server: Single Yorkie server process workload. |
| 101 | + - Cluster Server(Broadcast Mode): Yorkie cluster process heavy workloads. Broadcast based cluster mode can be reused to build cluster. |
| 102 | +- `mongo`: mongoDB is responsible for sharding and persisting data. There are several components in mongo system. |
| 103 | + - `mongos`: mongos is router responsible for routing data to correct shard based on data’s hash key. |
| 104 | + - `shard`: mongod is actual database responsible for persisting incomding data. |
| 105 | + - `config servers`: Config servers are responsible for storing metadata and configuration settings for the shard. `mongos` use this information for routing. |
| 106 | + |
| 107 | +**Workload Unit** |
| 108 | + |
| 109 | +Lookup cluster mode uses `document` as workload unit to assign each server to process same workloads. This assigns and seperates `document`’s state and data to each server, which remove needs for share and sync states and data between server cluster. |
| 110 | + |
| 111 | +**Server Mapping Strategy** |
| 112 | + |
| 113 | +Lookup cluster mode uses [consistent hashing](https://en.wikipedia.org/wiki/Consistent_hashing) for server mapping strategy. |
| 114 | + |
| 115 | +Consistent hashing is a technique used to map a range of input values (such as request metadata) to a corresponding range of output values (such as server IDs). |
| 116 | + |
| 117 | +The basic idea of consistent hashing is to assign each server to a point on a circular ring or continuum of hash values. When a request arrives, its hash value is computed, and the server responsible for that hash value is determined by finding the next server on the ring, starting from the point where the hash value falls. |
| 118 | + |
| 119 | +This ensures nearby hash values to be assigned to the same server, providing a degree of consistency in server assignments. |
| 120 | + |
| 121 | + |
| 122 | + |
| 123 | +As you can see above, computed hash value is mapped to closest server in clockwise direction, therefore `k0(keyo)` is mapped to `s0(server 0)`. Also, even when `s0` fails, `k0` can be mapped to `s4(server 4)`, this mechanism is helpful when server scale out/scale in, or failover. |
| 124 | + |
| 125 | +**LookUp Strategy** |
| 126 | + |
| 127 | +Lookup cluster mode uses server side discovery for lookup strategy. |
| 128 | + |
| 129 | + |
| 130 | + |
| 131 | +In server side discovery, there is proxy server(load balancer) in front of services. After services register themselves to service registry, proxy server can get service locations from service registry. Now when client connect to proxy server, proxy server route to proper service based on service registry’s information. |
| 132 | + |
| 133 | +**API Compatiability** |
| 134 | + |
| 135 | +Lookup cluster mode supports API compatiability. |
| 136 | + |
| 137 | +All APIs in Yorkie successfully compatiable with cluster mode except one API; `watchDocuments()`, which is responsible for mutli-document. This is because multiple document on distributed servers are sending `watchDocuments()` streams individually. |
| 138 | +
|
| 139 | +To support API compatiability with `watchDocuments()`, stream aggregator is introduced to aggreagte two `watchDocuments()` streams into one single stream. |
| 140 | +
|
| 141 | +… |
| 142 | +
|
| 143 | +**Server Addition & Removal Strategy** |
| 144 | +
|
| 145 | +Lookup cluster mode uses [consistent hashing](https://en.wikipedia.org/wiki/Consistent_hashing)(ring hash) to handle server addition & removal strategy. |
| 146 | +
|
| 147 | +Ring hash keeps previous hash key mapping the same and successfully add/remove servers. |
| 148 | +
|
| 149 | +… |
| 150 | +
|
| 151 | +**Lookup Cluster Mode Implementation (K8s & Istio)** |
| 152 | +
|
| 153 | +Below is internal implementation architecture for lookup cluster mode based on K8s and Istio. |
| 154 | +
|
| 155 | + |
| 156 | +
|
| 157 | +Yorkies and Service Registry can be simply implemented by using Istio's `Ingress Gateway(envoy)`, `envoy sidecar injection` as `Yorkies`, and `Istio Pilot` for service registry. When deploying Yorkie cluster pods, Istio automatically register pods and configure traffic routes. |
| 158 | +
|
| 159 | +`Gateway`, `VirtualService`, and `DestinationRule` are used to configure `envoy` inside ingress gateway and envoy sidecar. |
| 160 | +
|
| 161 | +Especially, `consistentHash` in `DestinationRule` is used to implement ring hash based lookup system. |
| 162 | +
|
| 163 | +```yaml |
| 164 | +apiVersion: networking.istio.io/v1alpha3 |
| 165 | +kind: DestinationRule |
| 166 | +metadata: |
| 167 | + name: yorkie |
| 168 | +spec: |
| 169 | + host: yorkie |
| 170 | + trafficPolicy: |
| 171 | + portLevelSettings: |
| 172 | + - port: |
| 173 | + number: 11101 |
| 174 | + loadBalancer: |
| 175 | + consistentHash: |
| 176 | + httpHeaderName: "x-api-key" |
| 177 | +``` |
| 178 | +
|
| 179 | +In this configuration, HTTP header is for hash function parameter. This routes workload based on http header’s value. For example, using `x-api-key` seperates workloads in `project` unit. |
| 180 | +
|
| 181 | +Also, `Istio`’s `grpc-web` [appProtocol](https://github.com/istio/istio/pull/10064) is used to configure `envoy`’s `gRPC-web` filter for `HTTP` → `gRPC` protocol conversion needed for web SDK. |
| 182 | +
|
| 183 | +```yaml |
| 184 | +apiVersion: v1 |
| 185 | +kind: Service |
| 186 | +metadata: |
| 187 | + name: yorkie |
| 188 | + namespace: yorkie |
| 189 | + labels: |
| 190 | + app: yorkie |
| 191 | +spec: |
| 192 | + clusterIP: None |
| 193 | + selector: |
| 194 | + app: yorkie |
| 195 | + ports: |
| 196 | + - port: 11101 |
| 197 | + name: yorkie-sdk-port |
| 198 | + appProtocol: grpc-web |
| 199 | +``` |
| 200 | +
|
| 201 | +Also, `corsPolicy` in `VirtualService` is configured to configure `CORS` for `gRPC-web` to be accessed externally. |
| 202 | +
|
| 203 | +```yaml |
| 204 | +apiVersion: networking.istio.io/v1alpha3 |
| 205 | +kind: VirtualService |
| 206 | +metadata: |
| 207 | + name: yorkie |
| 208 | +spec: |
| 209 | + hosts: |
| 210 | + - "*" |
| 211 | + gateways: |
| 212 | + - yorkie-gateway |
| 213 | + http: |
| 214 | + - name: yorkie-service |
| 215 | + match: |
| 216 | + - uri: |
| 217 | + prefix: "/yorkie.v1.YorkieService" |
| 218 | + route: |
| 219 | + - destination: |
| 220 | + host: yorkie |
| 221 | + port: |
| 222 | + number: 11101 |
| 223 | + corsPolicy: |
| 224 | + allowOrigin: |
| 225 | + - "*" |
| 226 | + allowMethods: |
| 227 | + - POST |
| 228 | + - GET |
| 229 | + - OPTIONS |
| 230 | + - PUT |
| 231 | + - DELETE |
| 232 | + allowHeaders: |
| 233 | + - grpc-timeout |
| 234 | + - content-type |
| 235 | + - keep-alive |
| 236 | + - user-agent |
| 237 | + - cache-control |
| 238 | + - content-type |
| 239 | + - content-transfer-encoding |
| 240 | + - custom-header-1 |
| 241 | + - x-accept-content-transfer-encoding |
| 242 | + - x-accept-response-streaming |
| 243 | + - x-user-agent |
| 244 | + - x-grpc-web |
| 245 | + - authorization |
| 246 | + - x-api-key |
| 247 | + maxAge: 1728s |
| 248 | + exposeHeaders: |
| 249 | + - custom-header-1 |
| 250 | + - grpc-status |
| 251 | + - grpc-message |
| 252 | + allowCredentials: true |
| 253 | +``` |
| 254 | +
|
| 255 | +When using Istio’s `ringHash` in [ConsistentHashLB](https://istio.io/latest/docs/reference/config/networking/destination-rule/#LoadBalancerSettings-ConsistentHashLB), Istio performs consistent hashing for both server endpoints and request for us. |
| 256 | +
|
| 257 | +Internally, Istio uses [Ketama](https://github.com/RJ/ketama) hashing for endpoint hashing, and [xxHash](https://github.com/Cyan4973/xxHash) for HTTP header hashing. Both two algorithms are well-known algorithms for evenly distributing hash values. |
| 258 | +
|
| 259 | +**Stream Aggregation** |
| 260 | +
|
| 261 | +`aggegator server` written in go is used to perform stream aggregation. This can be done by following code: |
| 262 | +
|
| 263 | +```go |
| 264 | +func aggregateStreams(stream1 yorkie.WatchDocumentsResponse, stream2 yorkie.WatchDocumentsResponse, stream myservice.MyService_AggregateStreamsServer) { |
| 265 | + for { |
| 266 | + select { |
| 267 | + case res1, ok := <-stream1.RecvCh(): |
| 268 | + if ok { |
| 269 | + // Send the response from stream1 to the client |
| 270 | + } else { |
| 271 | + // Stream1 has closed |
| 272 | + } |
| 273 | + case res2, ok := <-stream2.RecvCh(): |
| 274 | + if ok { |
| 275 | + // Send the response from stream2 to the client |
| 276 | + } else { |
| 277 | + // Stream2 has closed |
| 278 | + } |
| 279 | + } |
| 280 | + } |
| 281 | +} |
| 282 | +
|
| 283 | +func (s *server) AggregateStreams(stream yorkie.AggregateStreamsServer) error { |
| 284 | + stream1, err := s.client.Stream1(context.Background(), &yorkie.WatchDocumentsRequest{}) |
| 285 | + if err != nil { |
| 286 | + // Handle error |
| 287 | + } |
| 288 | +
|
| 289 | + stream2, err := s.client.WatchDocuments(context.Background(), &yorkie.WatchDocumentsRequest{}) |
| 290 | + if err != nil { |
| 291 | + // Handle error |
| 292 | + } |
| 293 | +
|
| 294 | + go aggregateStreams(stream1, stream2, stream) |
| 295 | +
|
| 296 | + // Wait for both streams to close |
| 297 | + <-stream1.Done() |
| 298 | + <-stream2.Done() |
| 299 | +
|
| 300 | + return nil |
| 301 | +} |
| 302 | +``` |
| 303 | +
|
| 304 | +After that, aggreagator servers is registered to `K8s`, or `Istio`’s `ServiceEntry`, and `Istio`’s `envoy sidecar`, or `Egress Gateway` is used to send stream response to aggreagtor server. |
| 305 | +
|
| 306 | +```yaml |
| 307 | +apiVersion: networking.istio.io/v1alpha3 |
| 308 | +kind: VirtualService |
| 309 | +metadata: |
| 310 | + name: yorkie-stream-aggregator |
| 311 | +spec: |
| 312 | + hosts: |
| 313 | + - stream-aggregator.yorkie.dev |
| 314 | + gateways: |
| 315 | + - istio-egressgateway |
| 316 | + http: |
| 317 | + - name: grpc-web |
| 318 | + match: |
| 319 | + - port: 11101 |
| 320 | + protocol: grpc-web |
| 321 | + route: |
| 322 | + - destination: |
| 323 | + host: yorkie-stream-aggregator |
| 324 | + port: |
| 325 | + number: 11101 |
| 326 | +``` |
0 commit comments