Skip to content

Commit fa87c16

Browse files
committed
Add support for sharding
1 parent f864775 commit fa87c16

File tree

8 files changed

+328
-5
lines changed

8 files changed

+328
-5
lines changed

charts/meilisearch/Chart.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ apiVersion: v1
22
appVersion: "v1.18.0"
33
description: A Helm chart for the Meilisearch search engine
44
name: meilisearch
5-
version: 0.17.1
5+
version: 0.18.0
66
icon: https://raw.githubusercontent.com/meilisearch/integration-guides/main/assets/logos/logo.svg
77
home: https://github.com/meilisearch/meilisearch-kubernetes/tree/main/charts/meilisearch
88
maintainers:

charts/meilisearch/README.md

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,115 @@ For production deployment, the `environment.MEILI_MASTER_KEY` is required. If `M
5050

5151
You can also use `auth.existingMasterKeySecret` to use an existing secret that has the key `MEILI_MASTER_KEY`
5252

53+
## Horizontal Scaling with Sharding
54+
55+
This chart supports Meilisearch's sharding feature for horizontal scaling across multiple replicas. Sharding is available exclusively in **Meilisearch Enterprise Edition v1.19+**.
56+
57+
### Prerequisites
58+
59+
1. **Meilisearch Enterprise Edition**: Version 1.19 or higher
60+
2. **License**: Required for production use (free for non-production environments under Business Source License 1.1)
61+
3. **Master Key**: All instances must share the same `MEILI_MASTER_KEY`
62+
4. **Primary Key**: Indexes must have a `primaryKey` configured before adding documents
63+
64+
### Enabling Sharding
65+
66+
To enable sharding in your Helm deployment:
67+
68+
```yaml
69+
sharding:
70+
enabled: true
71+
replicaCount: 3 # Number of nodes in the sharded cluster
72+
network:
73+
experimentalFeatures: true
74+
auth:
75+
# Option 1: Let Helm generate random API keys (development)
76+
searchApiKey: ""
77+
writeApiKey: ""
78+
79+
# Option 2: Use existing secret (recommended for production)
80+
existingSecret: "my-sharding-keys-secret"
81+
# The secret should contain 'searchApiKey' and 'writeApiKey' keys by default
82+
83+
# If your existing secret uses different key names:
84+
existingSecretSearchApiKey: "customSearchKeyName"
85+
existingSecretWriteApiKey: "customWriteKeyName"
86+
87+
persistence:
88+
enabled: true
89+
storageClass: "standard"
90+
size: 10Gi
91+
```
92+
93+
### How It Works
94+
95+
When sharding is enabled:
96+
97+
1. A **headless service** is created for StatefulSet pod-to-pod communication
98+
2. Each pod gets a unique **persistent volume** for its data partition
99+
3. A **network configurator sidecar** automatically configures the network topology
100+
4. Documents sent to any node are automatically distributed across all shards
101+
5. Each node indexes only its assigned subset of documents using consistent hashing
102+
103+
### Searching Sharded Indexes
104+
105+
To search across a sharded index, use [Meilisearch federated search](https://www.meilisearch.com/docs/learn/multi_search/performing_federated_search):
106+
107+
```bash
108+
curl -X POST 'http://meilisearch:7700/multi-search' \
109+
-H 'Authorization: Bearer YOUR_API_KEY' \
110+
-H 'Content-Type: application/json' \
111+
-d '{
112+
"federation": {},
113+
"queries": [
114+
{
115+
"indexUid": "movies",
116+
"q": "star wars",
117+
"federationOptions": { "remote": "node-0" }
118+
},
119+
{
120+
"indexUid": "movies",
121+
"q": "star wars",
122+
"federationOptions": { "remote": "node-1" }
123+
},
124+
{
125+
"indexUid": "movies",
126+
"q": "star wars",
127+
"federationOptions": { "remote": "node-2" }
128+
}
129+
]
130+
}'
131+
```
132+
133+
### Architecture
134+
135+
```
136+
┌─────────────────────────────────────────┐
137+
│ Load Balancer / Ingress │
138+
└────────────────┬────────────────────────┘
139+
140+
┌──────────┼──────────┐
141+
▼ ▼ ▼
142+
┌──────┐ ┌──────┐ ┌──────┐
143+
│Node-0│ │Node-1│ │Node-2│
144+
└───┬──┘ └───┬──┘ └───┬──┘
145+
│ │ │
146+
┌───▼──┐ ┌───▼──┐ ┌───▼──┐
147+
│ PVC │ │ PVC │ │ PVC │
148+
│ 10Gi │ │ 10Gi │ │ 10Gi │
149+
└──────┘ └──────┘ └──────┘
150+
```
151+
152+
### Important Notes
153+
154+
- **Persistence**: When sharding is enabled with persistence, each pod gets its own PVC (using volumeClaimTemplates)
155+
- **Scaling**: Changing the number of replicas after initial deployment requires careful data rebalancing
156+
- **Network Configuration**: Automatically handled by the sidecar container
157+
- **API Keys**: The `searchApiKey` and `writeApiKey` are used for secure inter-node communication
158+
- **Experimental**: Sharding is an experimental feature as of Meilisearch v1.19
159+
160+
For more information, see the [official Meilisearch sharding documentation](https://www.meilisearch.com/blog/horizontal-scaling-with-sharding).
161+
53162
## Values
54163

55164
| Key | Type | Default | Description |

charts/meilisearch/templates/configmap.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,6 @@ data:
1111
{{- if $.Values.serviceMonitor.enabled }}
1212
MEILI_EXPERIMENTAL_ENABLE_METRICS: "true"
1313
{{- end }}
14+
{{- if and .Values.sharding.enabled .Values.sharding.network.experimentalFeatures }}
15+
MEILI_EXPERIMENTAL_ENABLE_SHARDING: "true"
16+
{{- end }}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{{- if .Values.sharding.enabled }}
2+
apiVersion: v1
3+
kind: Service
4+
metadata:
5+
name: {{ include "meilisearch.fullname" . }}-headless
6+
labels:
7+
{{- include "meilisearch.labels" . | nindent 4 }}
8+
spec:
9+
clusterIP: None
10+
publishNotReadyAddresses: true
11+
ports:
12+
- port: {{ .Values.service.port }}
13+
targetPort: http
14+
protocol: TCP
15+
name: http
16+
selector:
17+
{{- include "meilisearch.selectorLabels" . | nindent 4 }}
18+
{{- end }}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
{{- if .Values.sharding.enabled }}
2+
apiVersion: v1
3+
kind: ConfigMap
4+
metadata:
5+
name: {{ include "meilisearch.fullname" . }}-network-config
6+
labels:
7+
{{- include "meilisearch.labels" . | nindent 4 }}
8+
data:
9+
configure-network.sh: |
10+
#!/bin/sh
11+
set -e
12+
13+
# Extract pod ordinal from hostname
14+
POD_ORDINAL=${HOSTNAME##*-}
15+
POD_NAME="node-${POD_ORDINAL}"
16+
17+
# Wait for Meilisearch to be ready
18+
echo "Waiting for Meilisearch to be ready..."
19+
until curl -sf http://localhost:{{ .Values.service.port }}/health > /dev/null 2>&1; do
20+
echo "Waiting for Meilisearch..."
21+
sleep 2
22+
done
23+
echo "Meilisearch is ready!"
24+
25+
# Build the remotes JSON object
26+
REMOTES="{"
27+
REPLICA_COUNT={{ .Values.sharding.replicaCount }}
28+
FIRST=true
29+
30+
for i in $(seq 0 $((REPLICA_COUNT - 1))); do
31+
if [ "$i" != "$POD_ORDINAL" ]; then
32+
if [ "$FIRST" = false ]; then
33+
REMOTES="${REMOTES},"
34+
fi
35+
FIRST=false
36+
NODE_NAME="node-${i}"
37+
NODE_URL="http://{{ include "meilisearch.fullname" . }}-${i}.{{ include "meilisearch.fullname" . }}-headless.${NAMESPACE}.svc.cluster.local:{{ .Values.service.port }}"
38+
REMOTES="${REMOTES}\"${NODE_NAME}\":{\"url\":\"${NODE_URL}\",\"searchApiKey\":\"${SEARCH_API_KEY}\",\"writeApiKey\":\"${WRITE_API_KEY}\"}"
39+
fi
40+
done
41+
REMOTES="${REMOTES}}"
42+
43+
echo "Configuring network for ${POD_NAME}..."
44+
45+
# Step 1: Enable sharding
46+
curl -X PATCH "http://localhost:{{ .Values.service.port }}/experimental-features/network" \
47+
-H "Authorization: Bearer ${MEILI_MASTER_KEY}" \
48+
-H "Content-Type: application/json" \
49+
-d '{"sharding": true}' || echo "Warning: Failed to enable sharding"
50+
51+
# Step 2: Configure network topology
52+
curl -X PATCH "http://localhost:{{ .Values.service.port }}/experimental-features/network" \
53+
-H "Authorization: Bearer ${MEILI_MASTER_KEY}" \
54+
-H "Content-Type: application/json" \
55+
-d "{\"self\":\"${POD_NAME}\",\"remotes\":${REMOTES}}" || echo "Warning: Failed to configure network"
56+
57+
echo "Network configuration completed for ${POD_NAME}"
58+
{{- end }}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{{- if and .Values.sharding.enabled (not .Values.sharding.network.auth.existingSecret) -}}
2+
apiVersion: v1
3+
kind: Secret
4+
metadata:
5+
name: {{ include "meilisearch.fullname" . }}-sharding-keys
6+
labels:
7+
{{- include "meilisearch.labels" . | nindent 4 }}
8+
type: Opaque
9+
data:
10+
{{- if .Values.sharding.network.auth.searchApiKey }}
11+
searchApiKey: {{ .Values.sharding.network.auth.searchApiKey | b64enc | quote }}
12+
{{- else }}
13+
searchApiKey: {{ randAlphaNum 32 | b64enc | quote }}
14+
{{- end }}
15+
{{- if .Values.sharding.network.auth.writeApiKey }}
16+
writeApiKey: {{ .Values.sharding.network.auth.writeApiKey | b64enc | quote }}
17+
{{- else }}
18+
writeApiKey: {{ randAlphaNum 32 | b64enc | quote }}
19+
{{- end }}
20+
{{- end }}

charts/meilisearch/templates/statefulset.yaml

Lines changed: 90 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ metadata:
55
labels:
66
{{- include "meilisearch.labels" . | nindent 4 }}
77
spec:
8-
replicas: {{ .Values.replicaCount | default 1 }}
9-
serviceName: {{ template "meilisearch.fullname" . }}
8+
replicas: {{ if .Values.sharding.enabled }}{{ .Values.sharding.replicaCount }}{{ else }}{{ .Values.replicaCount | default 1 }}{{ end }}
9+
serviceName: {{ if .Values.sharding.enabled }}{{ template "meilisearch.fullname" . }}-headless{{ else }}{{ template "meilisearch.fullname" . }}{{ end }}
1010
selector:
1111
matchLabels:
1212
{{- include "meilisearch.selectorLabels" . | nindent 6 }}
@@ -39,19 +39,27 @@ spec:
3939
- name: tmp
4040
emptyDir: {}
4141
{{- if .Values.persistence.enabled }}
42+
{{- if not .Values.sharding.enabled }}
4243
- name: {{ .Values.persistence.volume.name }}
4344
persistentVolumeClaim:
4445
claimName: {{ if .Values.persistence.existingClaim }}{{ .Values.persistence.existingClaim }}{{- else }}{{ include "meilisearch.fullname" . }}{{- end }}
46+
{{- end }}
4547
{{- else }}
4648
- name: {{ .Values.persistence.volume.name }}
4749
emptyDir: {}
4850
{{- end }}
51+
{{- if .Values.sharding.enabled }}
52+
- name: network-config
53+
configMap:
54+
name: {{ include "meilisearch.fullname" . }}-network-config
55+
defaultMode: 0755
56+
{{- end }}
4957
{{- if .Values.volumes }}
5058
{{ toYaml .Values.volumes | indent 8 }}
5159
{{- end }}
5260

53-
{{ if .Values.initContainers }}
5461
initContainers:
62+
{{- if .Values.initContainers }}
5563
{{ toYaml .Values.initContainers | nindent 8 }}
5664
{{- end }}
5765

@@ -111,6 +119,63 @@ spec:
111119
timeoutSeconds: {{ .Values.readinessProbe.timeoutSeconds }}
112120
resources:
113121
{{ toYaml .Values.resources | indent 12 }}
122+
{{- if .Values.sharding.enabled }}
123+
lifecycle:
124+
postStart:
125+
exec:
126+
command:
127+
- /bin/sh
128+
- -c
129+
- |
130+
# Wait a bit for Meilisearch to fully initialize
131+
sleep 5
132+
{{- end }}
133+
{{- if .Values.sharding.enabled }}
134+
- name: network-configurator
135+
image: curlimages/curl:latest
136+
command:
137+
- /bin/sh
138+
- -c
139+
- |
140+
# Wait for main container to be ready
141+
sleep 10
142+
# Run the network configuration script
143+
/scripts/configure-network.sh
144+
# Keep the container running
145+
echo "Network configuration completed. Sleeping..."
146+
sleep infinity
147+
env:
148+
- name: NAMESPACE
149+
valueFrom:
150+
fieldRef:
151+
fieldPath: metadata.namespace
152+
- name: MEILI_MASTER_KEY
153+
valueFrom:
154+
secretKeyRef:
155+
name: {{ template "secretMasterKeyName" . }}
156+
key: MEILI_MASTER_KEY
157+
- name: SEARCH_API_KEY
158+
valueFrom:
159+
secretKeyRef:
160+
name: {{ if .Values.sharding.network.auth.existingSecret }}{{ .Values.sharding.network.auth.existingSecret }}{{ else }}{{ include "meilisearch.fullname" . }}-sharding-keys{{ end }}
161+
key: {{ if .Values.sharding.network.auth.existingSecretSearchApiKey }}{{ .Values.sharding.network.auth.existingSecretSearchApiKey }}{{ else }}searchApiKey{{ end }}
162+
- name: WRITE_API_KEY
163+
valueFrom:
164+
secretKeyRef:
165+
name: {{ if .Values.sharding.network.auth.existingSecret }}{{ .Values.sharding.network.auth.existingSecret }}{{ else }}{{ include "meilisearch.fullname" . }}-sharding-keys{{ end }}
166+
key: {{ if .Values.sharding.network.auth.existingSecretWriteApiKey }}{{ .Values.sharding.network.auth.existingSecretWriteApiKey }}{{ else }}writeApiKey{{ end }}
167+
volumeMounts:
168+
- name: network-config
169+
mountPath: /scripts
170+
securityContext:
171+
runAsNonRoot: true
172+
runAsUser: 100
173+
capabilities:
174+
drop:
175+
- ALL
176+
allowPrivilegeEscalation: false
177+
readOnlyRootFilesystem: true
178+
{{- end }}
114179
{{- if .Values.containers }}
115180
{{ toYaml .Values.containers | nindent 8 }}
116181
{{- end }}
@@ -126,3 +191,25 @@ spec:
126191
tolerations:
127192
{{ toYaml . | indent 8 }}
128193
{{- end }}
194+
{{- if and .Values.sharding.enabled .Values.persistence.enabled }}
195+
volumeClaimTemplates:
196+
- metadata:
197+
name: {{ .Values.persistence.volume.name }}
198+
{{- with .Values.persistence.annotations }}
199+
annotations:
200+
{{- toYaml . | nindent 10 }}
201+
{{- end }}
202+
spec:
203+
accessModes:
204+
- {{ .Values.persistence.accessMode | quote }}
205+
{{- if .Values.persistence.storageClass }}
206+
{{- if (eq "-" .Values.persistence.storageClass) }}
207+
storageClassName: ""
208+
{{- else }}
209+
storageClassName: {{ .Values.persistence.storageClass | quote }}
210+
{{- end }}
211+
{{- end }}
212+
resources:
213+
requests:
214+
storage: {{ .Values.persistence.size | quote }}
215+
{{- end }}

0 commit comments

Comments
 (0)