Skip to main content
This page documents the PersistentVolumeClaim (PVC) configurations used in the exchange platform. PVCs provide persistent storage for stateful services like databases and caches.

PostgreSQL PVC

Location: postgres-db/pvc.yml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: postgres-pvc
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 5Gi
  storageClassName: standard-rwo
  volumeMode: Filesystem

Configuration Details

Metadata:
  • Name: postgres-pvc - Referenced in PostgreSQL Deployment
Spec:
  • Access Mode: ReadWriteOnce - Single node read/write access
  • Storage: 5Gi - Five gigabytes of persistent storage
  • Storage Class: standard-rwo - Uses standard storage class with RWO support
  • Volume Mode: Filesystem - Mounted as a filesystem (default)

Usage in Deployment

From postgres-db/deployment.yml (reference/deployments.mdx:36-43):
volumeMounts:
  - mountPath: /var/lib/postgresql/data
    subPath: postgres-data
    name: postgres-storage
volumes:
  - name: postgres-storage
    persistentVolumeClaim:
      claimName: postgres-pvc
Mount Configuration:
  • Mount Path: /var/lib/postgresql/data - PostgreSQL data directory
  • SubPath: postgres-data - Isolates data within PVC
  • Volume Name: postgres-storage - Internal reference name

Redis PVC

Location: redis/pvc.yml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: redis-pvc
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 5Gi
  storageClassName: standard-rwo
  volumeMode: Filesystem

Configuration Details

Metadata:
  • Name: redis-pvc - Referenced in Redis Deployment
Spec:
  • Access Mode: ReadWriteOnce - Single node read/write access
  • Storage: 5Gi - Five gigabytes of persistent storage
  • Storage Class: standard-rwo - Uses standard storage class with RWO support
  • Volume Mode: Filesystem - Mounted as a filesystem

Usage in Deployment

From redis/deployment.yml (reference/deployments.mdx:20-27):
volumeMounts:
  - mountPath: /data
    subPath: redis-data
    name: redis-storage
volumes:
  - name: redis-storage
    persistentVolumeClaim:
      claimName: redis-pvc
Mount Configuration:
  • Mount Path: /data - Redis data directory
  • SubPath: redis-data - Isolates data within PVC
  • Volume Name: redis-storage - Internal reference name

Access Modes

ReadWriteOnce (RWO)

accessModes:
  - ReadWriteOnce
Characteristics:
  • Volume can be mounted as read-write by a single node
  • Most common access mode for databases
  • Best performance for single-instance stateful applications
  • Supported by most storage providers
Use Cases:
  • Databases (PostgreSQL, MySQL)
  • Key-value stores (Redis)
  • Single-instance applications
  • Local development

Alternative Access Modes

ReadOnlyMany (ROX):
accessModes:
  - ReadOnlyMany
  • Volume can be mounted read-only by many nodes
  • Use for shared read-only data
  • Static assets, configuration files
ReadWriteMany (RWX):
accessModes:
  - ReadWriteMany
  • Volume can be mounted read-write by many nodes
  • Requires NFS or similar shared filesystem
  • Use for shared writable data
  • More expensive and complex

Storage Classes

standard-rwo

storageClassName: standard-rwo
The exchange platform uses the standard-rwo storage class: Characteristics:
  • Type: Standard persistent disk (HDD or SSD depending on provider)
  • Access Mode: ReadWriteOnce support
  • Performance: Good for general workloads
  • Cost: Moderate pricing
  • Availability: Zonal (tied to specific zone)

Cloud Provider Storage Classes

Google Kubernetes Engine (GKE):
storageClassName: standard-rwo    # Standard persistent disk
storageClassName: premium-rwo      # SSD persistent disk
storageClassName: balanced-rwo     # Balanced persistent disk
Amazon EKS:
storageClassName: gp2              # General Purpose SSD
storageClassName: gp3              # Next-gen General Purpose SSD
storageClassName: io1              # Provisioned IOPS SSD
Azure AKS:
storageClassName: managed          # Standard HDD
storageClassName: managed-premium  # Premium SSD

List Available Storage Classes

kubectl get storageclass

# Example output:
NAME                 PROVISIONER             RECLAIMPOLICY   VOLUMEBINDINGMODE
standard (default)   kubernetes.io/gce-pd    Delete          Immediate
standard-rwo         kubernetes.io/gce-pd    Delete          WaitForFirstConsumer
premium-rwo          kubernetes.io/gce-pd    Delete          WaitForFirstConsumer

Volume Binding Mode

Storage classes define when volumes are provisioned:

WaitForFirstConsumer

volumeBindingMode: WaitForFirstConsumer
Behavior:
  • Volume provisioned when pod is scheduled
  • Ensures volume is created in same zone as pod
  • Prevents zone mismatch issues
  • Recommended for production

Immediate

volumeBindingMode: Immediate
Behavior:
  • Volume provisioned immediately when PVC is created
  • May be in different zone than pod
  • Can cause pod scheduling failures
  • Faster for testing

Storage Sizing

Current Configuration

Both PVCs request 5Gi:
resources:
  requests:
    storage: 5Gi

Sizing Recommendations

PostgreSQL:
  • Small Database: 5-10Gi (current setting)
  • Medium Database: 20-50Gi
  • Large Database: 100Gi+
  • Formula: (Rows × Row Size × 1.5) + Indexes + WAL
Redis:
  • Cache Only: 1-5Gi (current setting)
  • Persistence: 5-20Gi
  • Large Dataset: 50Gi+
  • Formula: Dataset Size × 2 (for snapshots)

Resize PVC

Expand storage without downtime:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: postgres-pvc
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 10Gi  # Increased from 5Gi
  storageClassName: standard-rwo
  volumeMode: Filesystem
# Apply updated PVC
kubectl apply -f pvc.yml

# Check resize status
kubectl describe pvc postgres-pvc

# Note: Some storage classes require pod restart
kubectl rollout restart deployment exchange-postgres-deployment

Volume Mode

Filesystem

volumeMode: Filesystem
Characteristics:
  • Default mode
  • Volume formatted with filesystem (ext4, xfs, etc.)
  • Mounted as directory in container
  • Standard for most applications
Use Cases:
  • Databases
  • Application data
  • Log files
  • Configuration storage

Block

volumeMode: Block
Characteristics:
  • Raw block device (no filesystem)
  • Higher performance
  • Application manages data layout
  • Less common
Use Cases:
  • Custom databases
  • Performance-critical applications
  • Direct disk access requirements

Reclaim Policy

Defined in StorageClass, determines what happens to PV after PVC deletion:

Delete (Default)

reclaimPolicy: Delete
Behavior:
  • PersistentVolume deleted when PVC is deleted
  • Underlying storage (disk) also deleted
  • Data is permanently lost
  • Cost-effective for ephemeral data
Warning: Deleting PVC deletes all data!

Retain

reclaimPolicy: Retain
Behavior:
  • PersistentVolume kept when PVC is deleted
  • Underlying storage preserved
  • Manual cleanup required
  • Data can be recovered

SubPath Usage

Both PVCs use subPath in volume mounts: PostgreSQL:
subPath: postgres-data
Redis:
subPath: redis-data

Why Use SubPath?

  1. Isolation: Separates data from mount point metadata
  2. PostgreSQL Requirement: PostgreSQL won’t start if data dir contains lost+found
  3. Clean Structure: Organizes data within volume
  4. Multiple Mounts: Allow multiple subPaths from same PVC (if needed)

Directory Structure

Without subPath:
/var/lib/postgresql/data/
├── lost+found/        ← PostgreSQL fails here
├── base/
├── global/
└── pg_wal/
With subPath:
PVC Root:
├── lost+found/
└── postgres-data/     ← Mount point
    ├── base/
    ├── global/
    └── pg_wal/

PVC Lifecycle

Creation

# Create PVC
kubectl apply -f pvc.yml

# Check status
kubectl get pvc

# Example output:
NAME           STATUS   VOLUME                                     CAPACITY   ACCESS MODES
postgres-pvc   Bound    pvc-abc123-def456-ghi789                   5Gi        RWO
redis-pvc      Bound    pvc-xyz789-uvw456-rst123                   5Gi        RWO
Status Values:
  • Pending: Waiting for provisioning
  • Bound: Successfully bound to PersistentVolume
  • Lost: PV reference lost

Binding

PVC binds to PersistentVolume:
1. Create PVC

2. StorageClass provisioner creates PV

3. PVC binds to PV

4. Pod mounts PVC

Expansion

# Edit PVC to increase storage
kubectl edit pvc postgres-pvc

# Or apply updated manifest
kubectl apply -f pvc.yml

# Monitor expansion
kubectl describe pvc postgres-pvc | grep -A5 Conditions

Deletion

# Delete PVC (CAUTION: Data loss!)
kubectl delete pvc postgres-pvc

# Check PV status after deletion
kubectl get pv

PVC Operations

View PVCs

# List all PVCs
kubectl get pvc

# Detailed PVC information
kubectl describe pvc postgres-pvc

# PVC in YAML format
kubectl get pvc postgres-pvc -o yaml

View Bound PersistentVolumes

# List all PVs
kubectl get pv

# Find PV bound to specific PVC
kubectl get pv -o json | jq '.items[] | select(.spec.claimRef.name=="postgres-pvc")'

Check Storage Usage

# Execute into pod
kubectl exec -it <postgres-pod> -- bash

# Check disk usage
df -h /var/lib/postgresql/data

# Example output:
Filesystem      Size  Used Avail Use% Mounted on
/dev/sdb        5.0G  2.1G  2.9G  42% /var/lib/postgresql/data

Backup PVC Data

# Create backup pod with PVC mounted
kubectl run backup --image=busybox --rm -it --restart=Never \
  --overrides='{
    "spec": {
      "containers": [{
        "name": "backup",
        "image": "busybox",
        "command": ["tar", "czf", "/backup/postgres-backup.tar.gz", "-C", "/data", "."],
        "volumeMounts": [{
          "name": "data",
          "mountPath": "/data"
        }, {
          "name": "backup",
          "mountPath": "/backup"
        }]
      }],
      "volumes": [{
        "name": "data",
        "persistentVolumeClaim": {
          "claimName": "postgres-pvc"
        }
      }, {
        "name": "backup",
        "emptyDir": {}
      }]
    }
  }'

Troubleshooting

PVC Stuck in Pending

# Check PVC events
kubectl describe pvc postgres-pvc

# Common causes:
# 1. No available storage class
kubectl get storageclass

# 2. Insufficient quota
kubectl get resourcequota

# 3. Zone mismatch (check node zones)
kubectl get nodes -o wide

Pod Can’t Mount PVC

# Check pod events
kubectl describe pod <postgres-pod>

# Common issues:
# 1. PVC not bound
kubectl get pvc postgres-pvc

# 2. Access mode mismatch
kubectl get pvc postgres-pvc -o jsonpath='{.spec.accessModes}'

# 3. PVC in use by another pod
kubectl get pods -o json | jq '.items[] | select(.spec.volumes[].persistentVolumeClaim.claimName=="postgres-pvc") | .metadata.name'

Data Loss After Deletion

Prevention:
# Add finalizer to prevent accidental deletion
kubectl patch pvc postgres-pvc -p '{"metadata":{"finalizers":["kubernetes.io/pvc-protection"]}}'

# Create snapshots regularly (if supported)
kubectl create -f volumesnapshot.yml
Recovery (if using Retain policy):
# Find orphaned PV
kubectl get pv | grep Released

# Remove claimRef to make PV available
kubectl patch pv <pv-name> -p '{"spec":{"claimRef":null}}'

# Create new PVC with same name
kubectl apply -f pvc.yml

Best Practices

  1. Size Appropriately: Estimate growth, add buffer (2x recommended)
  2. Use SubPath: Especially for PostgreSQL to avoid lost+found issues
  3. Enable Backups: Regular snapshots or external backups
  4. Monitor Usage: Alert when storage reaches 80% capacity
  5. Test Restore: Regularly test backup/restore procedures
  6. Use Retain Policy: For production data, consider Retain reclaim policy
  7. Document Dependencies: Track which pods use which PVCs
  8. Plan for Expansion: Choose storage class that supports expansion

Storage Costs

Cost Comparison (Example: GCP)

Storage TypeCost per GB/monthPerformance
standard-rwo$0.04Standard HDD
balanced-rwo$0.10Balanced SSD
premium-rwo$0.17Premium SSD
Current Usage:
  • PostgreSQL: 5Gi × 0.04=0.04 = 0.20/month
  • Redis: 5Gi × 0.04=0.04 = 0.20/month
  • Total: $0.40/month

Performance Optimization

Storage Class Selection

For Databases:
storageClassName: premium-rwo  # SSD for better IOPS
For Logs/Archives:
storageClassName: standard-rwo  # HDD for cost savings

IOPS Tuning

For cloud providers with provisioned IOPS:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: postgres-pvc
  annotations:
    volume.beta.kubernetes.io/storage-provisioner: kubernetes.io/aws-ebs
    volume.beta.kubernetes.io/iops: "1000"
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 50Gi
  storageClassName: io1

Build docs developers (and LLMs) love