Skip to content

Commit af5123b

Browse files
committed
Add lookup cluster mode design document
1 parent a7dfa3c commit af5123b

5 files changed

+326
-0
lines changed

design/lookup-cluster-mode.md

+326
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,326 @@
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+
![Lookup Cluster Mode Architecture](media/lookup-cluster-mode-architecture.jpg)
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+
![Ring Hash Example](media/ring-hash-example.jpg)
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+
![Server Side Discovery](media/server-side-discovery.png)
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+
![K8s Istio Implementation Internal Architecture](media/k8s-istio-implementation-internal-architecture.jpg)
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+
```
Loading
Loading

design/media/ring-hash-example.jpg

275 KB
Loading
137 KB
Loading

0 commit comments

Comments
 (0)