This project demonstrates how to run a PostgreSQL database using CloudNativePG and accelerate read performance with DragonflyDB in Kubernetes.
A simple FastAPI application implements the cache-aside pattern, using Postgres as the system of record and DragonflyDB as a Redis-compatible cache.
- A running Kubernetes cluster
- kubectl
- Helm
postgres.yaml: Namespace, CloudNativePG cluster, and app DB credentialsapp.yaml: FastAPI deployment + serviceDockerfile,main.py,requirements.txt: app source
helm repo add cnpg https://cloudnative-pg.github.io/charts
helm repo update
helm install cnpg \
--namespace cnpg-system \
--create-namespace \
cnpg/cloudnative-pgApply the CloudNativePG cluster manifest:
kubectl apply -f postgresql-cluster.yamlWait for the Postgres pods to get ready. This step can take upto 5 minutes:
kubectl -n demo get podsexport VERSION=v1.36.0 # DragonflyDB version that you want to install
helm upgrade --install dragonfly \
oci://ghcr.io/dragonflydb/dragonfly/helm/dragonfly \
--version "$VERSION" \
--namespace demo \
--create-namespaceThe Kubernetes cluster must be able to pull the application image from a container registry.
You can either build and push the image yourself, or use a prebuilt image provided for this demo.
Option A: Build and push your own image (recommended)
Set the image name to a registry accessible by your cluster (Docker Hub, GHCR, ECR, etc.):
export APP_IMAGE=<YOUR_REGISTRY/REPO>:demo-apiBuild and push the image:
docker build -t "$APP_IMAGE" .
docker push "$APP_IMAGE"Update the image reference in app.yaml:
sed -i.bak "s|<YOUR_REGISTRY/REPO>:demo-api|${APP_IMAGE}|g" app.yamlOption B: Use the prebuilt demo image
To skip the build step, you can use the prebuilt image:
majid0079/cnpg-dragonflydb-sampleapp:demo-api
Update app.yaml to reference above mentioned image instead of <YOUR_REGISTRY/REPO>:demo-api.
kubectl apply -f app.yamlWait for app pods to get ready:
kubectl -n demo get podsPort-forward the app service:
kubectl -n demo port-forward svc/demo-api 8080:80In another terminal, create a todo:
curl -X POST localhost:8080/todos -H 'Content-Type: application/json' -d '{"title":"learn cnpg + dragonfly"}'List todos (first response from DB, next from cache):
curl localhost:8080/todosOutput:
→ curl localhost:8080/todos
{"source":"db(postgres)","items":[{"id":2,"title":"learn cnpg + dragonfly"},{"id":1,"title":"learn cnpg + dragonfly"}]}%
→ curl localhost:8080/todos
{"source":"cache(dragonfly)","items":[{"id":2,"title":"learn cnpg + dragonfly"},{"id":1,"title":"learn cnpg + dragonfly"}]}%
→ curl localhost:8080/todos
{"source":"cache(dragonfly)","items":[{"id":2,"title":"learn cnpg + dragonfly"},{"id":1,"title":"learn cnpg + dragonfly"}]}%- When you call the GET endpoint for the first time, the data is fetched from Postgres (
db(postgres)). - Subsequent calls are served from DragonflyDB (
cache(dragonfly)) until the cache entry expires. - Once the TTL (30 seconds by default, defined in
main.py) expires, the next request falls back to Postgres and refreshes the cache.
- CloudNativePG creates a
pg-rwservice for read/write connections. The app uses that by default. - DragonflyDB is Redis-compatible, so the app uses the
redisclient.
kubectl delete -f app.yaml
kubectl delete -f postgresql-cluster.yaml
helm uninstall cnpg -n cnpg-system
helm uninstall dragonfly -n demo