Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
12129cc
feat(values): enable PDB by default, add updateStrategy, auto-calc ma…
dimoschi Mar 20, 2026
f32ae71
feat(pdb): auto-enable for multi-node, auto-calculate maxUnavailable …
dimoschi Mar 20, 2026
8062508
feat(statefulset): add explicit updateStrategy with auto-calculated m…
dimoschi Mar 20, 2026
e6444bd
feat(statefulset): add built-in soft pod anti-affinity for multi-node…
dimoschi Mar 20, 2026
91ae0ca
docs: regenerate schema and documentation for HA guardrails
dimoschi Mar 20, 2026
643bbac
test: remove hardcoded chart version from test suites
dimoschi Mar 20, 2026
055714c
chore: bump chart version to 1.1.0
dimoschi Mar 20, 2026
fbe1f41
fix(schema): add type annotation for affinity as nullable object
dimoschi Mar 20, 2026
24e1986
fix(schema): use unquoted null in @schema type annotations
dimoschi Mar 20, 2026
d733e33
fix(schema): set affinity default to {}, add type annotation, unquote…
dimoschi Mar 20, 2026
f85ade8
fix(schema): use 0 sentinel for auto-calculated maxUnavailable, set a…
dimoschi Mar 20, 2026
dcca488
fix: use -1 sentinel for auto-calculated maxUnavailable, validate upd…
dimoschi Mar 20, 2026
2781846
test: add coverage for maxUnavailable -1, 0, and positive values
dimoschi Mar 20, 2026
76d70b9
fix: use -1 sentinel for maxUnavailable, add schema enum and if/then/…
dimoschi Mar 20, 2026
9ba208b
feat: use "auto" sentinel for maxUnavailable, add updateStrategy sche…
dimoschi Mar 20, 2026
22accbb
docs: set maxUnavailable type to string/int in helm-docs
dimoschi Mar 20, 2026
51875fa
docs: clarify affinity comment to say "when unset or empty"
dimoschi Mar 20, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions Changelog.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,24 @@
# Changelog

## 1.1.0 ![AppVersion: 30.1](https://img.shields.io/static/v1?label=AppVersion&message=30.1&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm)

**Release date:** 2026-03-20

### Added

- PodDisruptionBudget is now enabled by default for multi-node clusters (replicaCount > 1), automatically skipped for single-replica deployments
- Auto-calculated `maxUnavailable` for both PDB and StatefulSet `updateStrategy`, based on Raft fault tolerance (`floor(replicaCount/2)`), with user override support and quorum validation
- Explicit `updateStrategy` on StatefulSet with `RollingUpdate` default and `OnDelete` support
- Built-in soft pod anti-affinity (`preferredDuringSchedulingIgnoredDuringExecution`) on `kubernetes.io/hostname` for multi-node clusters, automatically spreading replicas across nodes
- Template-level validation that rejects `maxUnavailable` values exceeding Raft fault tolerance

### Changed

- `pdb.enabled` default changed from `false` to `true`
- `pdb.maxUnavailable` default changed from `1` to `"auto"` (auto-calculate as `floor(replicaCount/2)`)
- `updateStrategy.rollingUpdate.maxUnavailable` added with default `"auto"` (auto-calculate)
- Built-in soft pod anti-affinity is now injected when `affinity` is empty (`{}`)

## 1.0.0 ![AppVersion: 30.1](https://img.shields.io/static/v1?label=AppVersion&message=30.1&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm)

**Release date:** 2026-03-19
Expand Down
6 changes: 2 additions & 4 deletions Chart.yaml
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
apiVersion: v2
name: typesense
description: >-
Deploy Typesense search engine on Kubernetes with Raft-based HA clustering,
Prometheus metrics, and Gateway API support. This chart is not officially
maintained by or affiliated with the Typesense project.
Deploy Typesense search engine on Kubernetes with Raft-based HA clustering, Prometheus metrics, and Gateway API support. This chart is not officially maintained by or affiliated with the Typesense project.
type: application
version: 1.0.0
version: 1.1.0
appVersion: "30.1"
icon: https://typesense.org/typesense-logo.svg
home: https://github.com/hackthebox/typesense-helm
Expand Down
11 changes: 7 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# typesense

![Version: 1.0.0](https://img.shields.io/badge/Version-1.0.0-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 30.1](https://img.shields.io/badge/AppVersion-30.1-informational?style=flat-square)
![Version: 1.1.0](https://img.shields.io/badge/Version-1.1.0-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 30.1](https://img.shields.io/badge/AppVersion-30.1-informational?style=flat-square)

[![Artifact Hub](https://img.shields.io/endpoint?url=https://artifacthub.io/badge/repository/typesense)](https://artifacthub.io/packages/search?repo=typesense)

Expand Down Expand Up @@ -217,7 +217,7 @@ storage:

| Key | Type | Default | Description |
|-----|------|---------|-------------|
| affinity | object | `{}` | Affinity rules for pod scheduling |
| affinity | object | `{}` | Affinity rules for pod scheduling. When unset or empty and replicaCount > 1, a soft pod anti-affinity on kubernetes.io/hostname is automatically applied. Set to a non-empty affinity object to override this default behavior. |
| extraArgs | list | `[]` | Extra command-line arguments for Typesense server (e.g., ["--filter-by-max-ops=200"]) |
| extraEnv | list | `[]` | Extra environment variables for the Typesense container |
| fullnameOverride | string | `""` | Override the full name of the release (optional) |
Expand Down Expand Up @@ -247,8 +247,8 @@ storage:
| metrics.serviceMonitor.labels | object | `{}` | Additional labels for ServiceMonitor |
| nameOverride | string | `""` | Override the name of the release (optional) |
| nodeSelector | object | `{}` | Node selector to schedule pods on specific nodes (optional) |
| pdb.enabled | bool | `false` | Enable PodDisruptionBudget for Typesense StatefulSet |
| pdb.maxUnavailable | int | `1` | Maximum number of pods that can be unavailable during disruption |
| pdb.enabled | bool | `true` | Enable PodDisruptionBudget for Typesense StatefulSet. Automatically skipped when replicaCount is 1. |
| pdb.maxUnavailable | string/int | `"auto"` | Maximum number of pods that can be unavailable during disruption. Set to "auto" (default) to auto-calculate as floor(replicaCount/2), preserving Raft quorum. Set to 0 to block all voluntary disruptions. Any positive value is used directly and must not exceed floor(replicaCount/2). |
| podAnnotations | object | `{}` | Additional annotations to add to the Typesense pod(s) |
| podLabels | object | `{}` | Additional labels to add to the Typesense pod(s) |
| podSecurityContext.fsGroup | int | `2000` | Group ID for the filesystem of the Typesense container |
Expand Down Expand Up @@ -295,6 +295,9 @@ storage:
| typesense.logging.slowRequestsTimeMs | string | `nil` | Threshold in ms for slow request logging (-1 disables). Unset uses Typesense default (-1) |
| typesense.snapshots.intervalSeconds | string | `nil` | Replication log snapshot frequency in seconds. Unset uses Typesense default (3600) |
| typesense.threadPoolSize | string | `nil` | Concurrent request handler threads. Unset uses Typesense default (NUM_CORES * 8) |
| updateStrategy | object | `{"rollingUpdate":{"maxUnavailable":"auto"},"type":"RollingUpdate"}` | StatefulSet update strategy configuration. When type is RollingUpdate, rollingUpdate.maxUnavailable is required. When type is OnDelete, rollingUpdate is optional and ignored. |
| updateStrategy.rollingUpdate.maxUnavailable | string/int | `"auto"` | Maximum number of pods that can be unavailable during a rolling update. Set to "auto" (default) to auto-calculate as floor(replicaCount/2), preserving Raft quorum. Set to 0 to block all voluntary pod replacements. Any positive value is used directly and must not exceed floor(replicaCount/2). Ignored when updateStrategy.type is OnDelete. |
| updateStrategy.type | string | `"RollingUpdate"` | StatefulSet update strategy type. Use RollingUpdate (default) for zero-downtime upgrades or OnDelete for manual pod-by-pod control. |

## Upgrading

Expand Down
15 changes: 13 additions & 2 deletions templates/pdb.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,15 @@
{{- if .Values.pdb.enabled -}}
{{- if and .Values.pdb.enabled (gt (int .Values.replicaCount) 1) -}}
{{- $replicaCount := int .Values.replicaCount -}}
{{- $maxFault := div $replicaCount 2 -}}
{{- $maxUnavailable := .Values.pdb.maxUnavailable -}}
{{- if and (kindIs "string" $maxUnavailable) (eq $maxUnavailable "auto") }}
{{- $maxUnavailable = $maxFault -}}
{{- else }}
{{- $maxUnavailable = int $maxUnavailable -}}
{{- if gt $maxUnavailable $maxFault }}
{{- fail (printf "pdb.maxUnavailable (%d) exceeds Raft fault tolerance for replicaCount %d (max: floor(%d/2) = %d)" $maxUnavailable $replicaCount $replicaCount $maxFault) }}
{{- end }}
{{- end }}
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
Expand All @@ -7,7 +18,7 @@ metadata:
labels:
{{- include "typesense.labels" . | nindent 4 }}
spec:
maxUnavailable: {{ .Values.pdb.maxUnavailable }}
maxUnavailable: {{ $maxUnavailable }}
selector:
matchLabels:
{{- include "typesense.selectorLabels" . | nindent 6 }}
Expand Down
38 changes: 36 additions & 2 deletions templates/statefulset.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,30 @@ spec:
serviceName: {{ include "typesense.fullname" . }}-headless-svc
podManagementPolicy: Parallel
replicas: {{ .Values.replicaCount }}
updateStrategy:
{{- if not (or (eq .Values.updateStrategy.type "RollingUpdate") (eq .Values.updateStrategy.type "OnDelete")) }}
{{- fail (printf "updateStrategy.type must be RollingUpdate or OnDelete, got %q" .Values.updateStrategy.type) }}
{{- end }}
type: {{ .Values.updateStrategy.type }}
Comment on lines +16 to +20
Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updateStrategy.type is rendered directly into the StatefulSet spec, but the template only handles RollingUpdate explicitly; any other value will produce an invalid manifest (and silently omit the rollingUpdate block). Consider adding template-level validation (e.g., fail on unknown values) and/or a schema enum for updateStrategy.type to ensure only RollingUpdate and OnDelete are accepted.

Suggested change
updateStrategy:
type: {{ .Values.updateStrategy.type }}
updateStrategy:
{{- if or (eq .Values.updateStrategy.type "RollingUpdate") (eq .Values.updateStrategy.type "OnDelete") }}
type: {{ .Values.updateStrategy.type }}
{{- else }}
{{- fail (printf "updateStrategy.type must be either 'RollingUpdate' or 'OnDelete', got %q" .Values.updateStrategy.type) }}
{{- end }}

Copilot uses AI. Check for mistakes.
{{- if eq .Values.updateStrategy.type "RollingUpdate" }}
{{- $replicaCount := int .Values.replicaCount }}
{{- $maxFault := div $replicaCount 2 }}
{{- $maxUnavailable := .Values.updateStrategy.rollingUpdate.maxUnavailable }}
{{- if and (kindIs "string" $maxUnavailable) (eq $maxUnavailable "auto") }}
{{- if gt $replicaCount 1 }}
{{- $maxUnavailable = $maxFault }}
{{- else }}
{{- $maxUnavailable = 1 }}
{{- end }}
Comment on lines +26 to +30
Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The docs for updateStrategy.rollingUpdate.maxUnavailable say the auto-calculated value is floor(replicaCount/2), but the template special-cases replicaCount: 1 to emit maxUnavailable: 1. Please update the value descriptions (in values.yaml and generated schema/docs) to reflect the single-replica behavior, or adjust the template so the documented formula is consistently applied.

Suggested change
{{- if gt $replicaCount 1 }}
{{- $maxUnavailable = $maxFault }}
{{- else }}
{{- $maxUnavailable = 1 }}
{{- end }}
{{- $maxUnavailable = $maxFault }}

Copilot uses AI. Check for mistakes.
{{- else }}
{{- $maxUnavailable = int $maxUnavailable }}
{{- if and (gt $replicaCount 1) (gt $maxUnavailable $maxFault) }}
{{- fail (printf "updateStrategy.rollingUpdate.maxUnavailable (%d) exceeds Raft fault tolerance for replicaCount %d (max: floor(%d/2) = %d)" $maxUnavailable $replicaCount $replicaCount $maxFault) }}
{{- end }}
{{- end }}
rollingUpdate:
maxUnavailable: {{ $maxUnavailable }}
{{- end }}
selector:
matchLabels:
{{- include "typesense.selectorLabels" . | nindent 6 }}
Expand Down Expand Up @@ -155,9 +179,19 @@ spec:
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.affinity }}
{{- if .Values.affinity }}
affinity:
{{- toYaml . | nindent 8 }}
{{- toYaml .Values.affinity | nindent 8 }}
{{- else if gt (int .Values.replicaCount) 1 }}
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchLabels:
{{- include "typesense.selectorLabels" . | nindent 20 }}
topologyKey: kubernetes.io/hostname
{{- end }}
{{- with .Values.topologySpreadConstraints }}
topologySpreadConstraints:
Expand Down
2 changes: 0 additions & 2 deletions tests/config_test.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
suite: Configuration
chart:
version: 1.0.0
release:
name: foo
namespace: bar
Expand Down
2 changes: 0 additions & 2 deletions tests/external_secret_test.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
suite: Secrets
chart:
version: 1.0.0
release:
name: foo
namespace: bar
Expand Down
2 changes: 0 additions & 2 deletions tests/ingress_test.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
suite: Networking
chart:
version: 1.0.0
release:
name: foo
namespace: bar
Expand Down
107 changes: 94 additions & 13 deletions tests/pdb_test.yaml
Original file line number Diff line number Diff line change
@@ -1,21 +1,11 @@
suite: PodDisruptionBudget
chart:
version: 1.0.0
release:
name: foo
namespace: bar
templates:
- pdb.yaml
tests:
- it: should not create PDB by default
asserts:
- notFailedTemplate: {}
- hasDocuments:
count: 0
- it: should create PDB when enabled
set:
pdb:
enabled: true
- it: should create PDB by default with auto-calculated maxUnavailable
asserts:
- notFailedTemplate: {}
- hasDocuments:
Expand All @@ -28,16 +18,107 @@ tests:
- equal:
path: .spec.maxUnavailable
value: 1
- it: should allow overriding maxUnavailable

- it: should not create PDB when replicaCount is 1
set:
replicaCount: 1
asserts:
- notFailedTemplate: {}
- hasDocuments:
count: 0

- it: should not create PDB when replicaCount is 1 even if pdb.enabled is true
set:
replicaCount: 1
pdb:
enabled: true
maxUnavailable: 2
asserts:
- notFailedTemplate: {}
- hasDocuments:
count: 0

- it: should not create PDB when disabled
set:
pdb:
enabled: false
asserts:
- notFailedTemplate: {}
- hasDocuments:
count: 0

- it: should auto-calculate maxUnavailable as floor(replicaCount/2) for 5 replicas
set:
replicaCount: 5
asserts:
- notFailedTemplate: {}
- equal:
path: .spec.maxUnavailable
value: 2

- it: should auto-calculate maxUnavailable as floor(replicaCount/2) for 7 replicas
set:
replicaCount: 7
asserts:
- notFailedTemplate: {}
- equal:
path: .spec.maxUnavailable
value: 3

- it: should auto-calculate when maxUnavailable is auto
set:
replicaCount: 5
pdb:
enabled: true
maxUnavailable: auto
asserts:
- notFailedTemplate: {}
- equal:
path: .spec.maxUnavailable
value: 2

- it: should allow maxUnavailable 0 to block all voluntary disruptions
set:
pdb:
enabled: true
maxUnavailable: 0
asserts:
- notFailedTemplate: {}
- equal:
path: .spec.maxUnavailable
value: 0

- it: should allow overriding maxUnavailable within quorum bounds
set:
replicaCount: 5
pdb:
enabled: true
maxUnavailable: 1
asserts:
- notFailedTemplate: {}
- equal:
path: .spec.maxUnavailable
value: 1

- it: should reject maxUnavailable exceeding Raft fault tolerance
set:
replicaCount: 3
pdb:
enabled: true
maxUnavailable: 2
asserts:
- failedTemplate:
errorMessage: "pdb.maxUnavailable (2) exceeds Raft fault tolerance for replicaCount 3 (max: floor(3/2) = 1)"

- it: should reject maxUnavailable exceeding Raft fault tolerance for 5 replicas
set:
replicaCount: 5
pdb:
enabled: true
maxUnavailable: 3
asserts:
- failedTemplate:
errorMessage: "pdb.maxUnavailable (3) exceeds Raft fault tolerance for replicaCount 5 (max: floor(5/2) = 2)"

- it: should use selector labels matching the StatefulSet
set:
pdb:
Expand Down
2 changes: 0 additions & 2 deletions tests/service_test.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
suite: Networking
chart:
version: 1.0.0
release:
name: foo
namespace: bar
Expand Down
2 changes: 0 additions & 2 deletions tests/serviceaccount_test.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
suite: ServiceAccount
chart:
version: 1.0.0
release:
name: foo
namespace: bar
Expand Down
2 changes: 0 additions & 2 deletions tests/servicemonitor_test.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
suite: ServiceMonitor
chart:
version: 1.0.0
release:
name: foo
namespace: bar
Expand Down
Loading
Loading