Installs into .claude/skills of the current project.
Are you the author of State Management?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/paulpas-state-management)
---
name: state-management
compatibility: opencode
completeness: 95
content-types:
- guidance
- examples
- do-dont
- config
description: Manages application state across deployment stages including database
migrations, cache invalidation, and data consistency during rolling updates.
license: MIT
maturity: stable
metadata:
completeness: 95
content-types:
- code
- guidance
- config
- do-dont
domain: cncf
exampleCount: 3
maturity: stable
output-format: code
related-skills: deployment-philosophy,blue-green-deployment,rollback-strategy,deployment-orchestration
role: implementation
scope: infrastructure
triggers: state management, database migration, deployment state, data migration,
cache invalidation, schema migration, data consistency, backward compatible migration
archetypes:
- tactical
anti_triggers:
- brainstorming
- vague ideation
- non-containerized architecture
response_profile:
verbosity: low
directive_strength: high
abstraction_level: operational
version: "1.0.0"
---
---
# State Management
Manages application state — database schemas, cached data, and persisted state — across deployment stages. Ensures data consistency when old and new versions of a service coexist during deployment transitions.
## TL;DR Checklist
- [ ] Design all schema changes to be backward-compatible with both old and new versions
- [ ] Run read migrations before deploying new code (backfill old readers)
- [ ] Deploy new code that reads and writes new format
- [ ] Run write migrations to convert remaining old-format data
- [ ] Remove old code references only after all data is migrated
- [ ] Invalidate caches at the right transition points
---
## When to Use
Use this skill when:
- **Database schema changes accompany deployments** — Adding columns, tables, or indexes that both old and new code versions may access
- **Data format changes are required** — New code needs data in a different format than old code
- **Cache consistency must be maintained** — Deploying new code that interprets cached data differently
- **Stateful services are being deployed** — Services with local state, queues, or persisted configuration
---
## When NOT to Use
Avoid this skill for:
- **Purely additive schema changes with no code change** — Adding an index or column that no code reads/writes yet needs no state management
- **Stateless microservices with no persistent data** — No database, no cache, no local state = no state management needed
- **Full database replacement** — Migrating to a completely new database system is a data migration project, not a deployment state pattern
---
## Core Workflow
### 1. Classify the State Change
Determine what type of state change is happening: schema addition, schema modification, data format change, or cache invalidation. Each type requires a different migration sequence.
**Checkpoint:** You know whether the change is additive, transitional, or destructive.
### 2. Design Backward-Compatible Schema
Ensure the new schema works with both old and new code versions. The old code must be able to read and write without errors.
**Checkpoint:** Both old and new code can coexist against the new schema without data loss or corruption.
### 3. Execute Read Migration
Add new columns or tables without changing existing ones. Old code ignores new columns (they're NULL/default), new code uses them.
**Checkpoint:** Database is safe for both versions to read and write simultaneously.
### 4. Deploy New Code
Deploy the new version that reads and writes the new schema. Both old and new code are now running against the new schema.
**Checkpoint:** New code works correctly with the new schema.
### 5. Execute Write Migration
Convert any remaining old-format data to the new format. This can happen gradually since new code is already writing new format.
**Checkpoint:** All data is in the new format (or old format is no longer needed).
### 6. Clean Up
Remove old columns, tables, or code references. This is the final cleanup that breaks backward compatibility.
**Checkpoint:** No old code is still running. All old schema elements are removed.
---
## Implementation Patterns
### Pattern 1: Backward-Compatible Schema Migration
Implement schema changes that are safe during blue-green or canary deployments where old and new versions coexist.
#### ❌ BAD — Breaking Schema Change During Deployment
```bash
# ❌ BAD: Modifying a column in a way that breaks old code
# Old code is still running and tries to read/write shipping_address — will crash
psql -h db-primary -d app_db -c "ALTER TABLE orders DROP COLUMN shipping_address"
psql -h db-primary -d app_db -c "ALTER TABLE orders ADD COLUMN shipping_street VARCHAR(255)"
psql -h db-primary -d app_db -c "ALTER TABLE orders ADD COLUMN shipping_city VARCHAR(100)"
# Problem: old code is still running and tries to read/write shipping_address
# Problem: old code's INSERT/SELECT statements fail immediately
# Problem: during blue-green, old version is serving users who hit these errors
# Problem: this is a breaking change — requires all instances to restart simultaneously
```
**What's wrong:**
- Old code crashes immediately when it tries to access the dropped column
- During blue-green deployment, old version serves users who hit errors
- No graceful transition period
- Requires downtime for all instances to restart
#### ✅ GOOD — Additive Migration with Dual-Write
```bash
# ✅ GOOD: Backward-compatible schema migration using expand/contract pattern
# Phase 1 (Expand): Add new columns, write to both old and new
# Phase 2 (Contract): Read from new, drop old columns
# --- Configuration ---
DB_HOST="db-primary"
DB_NAME="app_db"
DB_USER="app_user"
TABLE="orders"
DRY_RUN=false
# --- Phase 1: Add new column (backward-compatible — old code ignores it) ---
phase_add_column() {
local table=$1 new_column=$2 column_type=$3
local null_clause="NULL" # Nullable for backward compatibility
if [[ "$DRY_RUN" == true ]]; then
echo "[DRY RUN] ALTER TABLE $table ADD COLUMN $new_column $column_type $null_clause"
else
echo "Phase 1: Adding $new_column to $table as nullable"
psql -h "$DB_HOST" -d "$DB_NAME" -U "$DB_USER" -c \
"ALTER TABLE $table ADD COLUMN IF NOT EXISTS $new_column $column_type $null_clause"
fi
}
# --- Phase 2: Set up dual-write (app-level, not SQL) ---
# This is an application code change, not a database migration.
# The new code version writes to BOTH old_column and new_column:
#
# INSERT INTO orders (old_column, new_column) VALUES (?, ?);
# -- Old code writes: INSERT INTO orders (old_column) VALUES (?);
# -- New code writes: INSERT INTO orders (old_column, new_column) VALUES (?, ?);
#
# This ensures both columns stay in sync during the transition.
phase_dual_write() {
cat <<EOF
Phase 2: Enable dual-write at the application level
- Old code writes to old_column only (unchanged)
- New code writes to BOTH old_column and new_column
- Both columns stay in sync via application logic
- Deploy new code that reads from new_column
EOF
}
# --- Phase 3: Deploy new code (reads from new column) ---
phase_read_new() {
echo "Phase 3: Deploy new code that reads from new_column"
echo " - Old code continues reading old_column (unchanged)"
echo " - New code reads from new_column"
echo " - Both columns contain the same data (dual-write ensured this)"
echo " - Deploy with rolling update"
# kubectl set image deployment/orders app=orders:v2.0.0
# kubectl rollout status deployment/orders --timeout=120s
}
# --- Phase 4: Drop old column (contraction — only after old code is gone) ---
phase_drop_old() {
local table=$1 old_column=$2 new_column=$3
# Verification: ensure new column has data for all rows
echo "Verifying $new_column has data for all rows..."
local null_count
null_count=$(psql -h "$DB_HOST" -d "$DB_NAME" -U "$DB_USER" -t \
-c "SELECT COUNT(*) FROM $table WHERE $new_column IS NULL" | tr -d ' ')
if [[ "$null_count" != "0" ]]; then
echo "ERROR: $new_column has $null_count NULL values — cannot drop $old_column yet"
echo "Run backfill first: UPDATE $table SET $new_column = <computed_value> WHERE $new_column IS NULL"
return 1
fi
echo "Verification passed. Dropping $old_column..."
if [[ "$DRY_RUN" != true ]]; then
psql -h "$DB_HOST" -d "$DB_NAME" -U "$DB_USER" -c \
"ALTER TABLE $table DROP COLUMN IF EXISTS $old_column"
fi
}
# --- Full migration execution ---
run_migration() {
echo "=== Backward-Compatible Schema Migration ==="
echo "Table: $TABLE"
echo "DRY_RUN: $DRY_RUN"
echo "---"
# Step 1: Add new column (non-destructive)
phase_add_column "$TABLE" "shipping_street" "VARCHAR(255)"
phase_add_column "$TABLE" "shipping_city" "VARCHAR(100)"
echo ""
# Step 2: Deploy dual-write application code
phase_dual_write
echo ""
# Step 3: Deploy new code
phase_read_new
echo ""
# Step 4: After confirming new code works, drop old column
phase_drop_old "$TABLE" "shipping_address" "shipping_city"
echo "=== Migration Complete ==="
}
# --- Usage ---
# run_migration # Execute with actual DB changes
# DRY_RUN=true run_migration # Preview without executing
```
### Pattern 2: Cache Invalidation Strategy
Manage cache invalidation during deployments to prevent stale or corrupted cache entries.
#### ❌ BAD — No Cache Invalidation
```bash
# ❌ BAD: Deploying new code that interprets cache differently, without invalidation
# Old cache entries: {"order:{id}": order_data}
# New cache entries: {"v2:order:{id}": order_data_with_new_fields}
# Old entries are never invalidated — new code reads stale, old-format data
# Result: new code sees corrupted or incomplete data from old cache entries
# Deploy new code without cleaning cache — users see mixed old/new data formats
kubectl set image deployment/orders app=orders:v2.0.0
kubectl rollout status deployment/orders
# New code runs but reads stale v1-format entries from cache
```
#### ✅ GOOD — Versioned Cache with Clean Invalidation
```bash
# ✅ GOOD: Versioned cache keys with safe invalidation during deployment
# Version-prefixed keys prevent old/new format collisions
# --- Configuration ---
REDIS_HOST="redis-master"
REDIS_PORT="6379"
CACHE_NAMESPACE="app"
CACHE_VERSION="v2"
# --- Versioned cache key generation ---
# Old format: "app:order:{order_id}"
# New format: "v2:order:{hash_of_resource_id}"
make_cache_key() {
local resource_type=$1 resource_id=$2
local key_hash
key_hash=$(echo -n "${CACHE_NAMESPACE}:${resource_type}:${resource_id}" \
| sha256sum | cut -c1-16)
echo "${CACHE_VERSION}:${resource_type}:${key_hash}"
}
# --- Cache invalidation functions ---
# Invalidate entries for a specific resource type at current version
invalidate_version() {
local resource_type=$1
local pattern="${CACHE_VERSION}:${resource_type}:*"
echo "Invalidating cache pattern: $pattern"
local count
count=$(redis-cli -h "$REDIS_HOST" -p "$REDIS_PORT" KEYS "$pattern" | wc -l)
redis-cli -h "$REDIS_HOST" -p "$REDIS_PORT" DEL $(redis-cli -h "$REDIS_HOST" -p "$REDIS_PORT" KEYS "$pattern") 2>/dev/null
echo " Invalidated $count keys for $resource_type@$CACHE_VERSION"
}
# Invalidate ALL cache entries for a resource type, regardless of version
invalidate_all_versions() {
local resource_type=$1
local pattern="*:${resource_type}:*"
echo "Invalidating ALL versions for resource type: $resource_type"
local count
count=$(redis-cli -h "$REDIS_HOST" -p "$REDIS_PORT" KEYS "$pattern" | wc -l)
redis-cli -h "$REDIS_HOST" -p "$REDIS_PORT" DEL $(redis-cli -h "$REDIS_HOST" -p "$REDIS_PORT" KEYS "$pattern") 2>/dev/null
echo " Invalidated $count keys (all versions)"
}
# --- Safe deployment with cache invalidation ---
deploy_with_cache_invalidation() {
local changed_resource_types=$@
local cache_version_changed=false
echo "=== Cache Invalidation Before Deployment ==="
for resource_type in $changed_resource_types; do
if [[ "$cache_version_changed" == true ]]; then
invalidate_all_versions "$resource_type"
else
invalidate_version "$resource_type"
fi
done
echo ""
echo "Cache invalidated. Proceeding with deployment..."
# kubectl set image deployment/app app=app:v2.0.0
# kubectl rollout status deployment/app
}
# --- Usage ---
# deploy_with_cache_invalidation "order" "user" "product"
# deploy_with_cache_invalidation "order" "user" "product" # If cache format changed:
# deploy_with_cache_invalidation() { deploy_with_cache_invalidation "order" "user"; invalidate_all_versions "order"; invalidate_all_versions "user"; }
```
### Pattern 3: State Transition Validator
Validate that state transitions during deployment are safe and reversible.
#### ✅ GOOD — File-Based State Transition Validation
```bash
# ✅ GOOD: Validate state transitions using file-based state management
# Each deployment has a state file that tracks current and target states
# --- State file configuration ---
STATE_DIR="/tmp/state-machine"
STATE_FILE="$STATE_DIR/state.json"
# --- Initialize state machine ---
init_state() {
local initial_state=$1
mkdir -p "$STATE_DIR"
jq -n --arg s "$initial_state" --arg t "t '$initial_state" --argjson step 0 \
'{current_state: $s, target_state: null, step: $step, updated_at: now}' \
> "$STATE_FILE"
}
# --- Allowed transitions (JSON) ---
# Define which state transitions are permitted per service
TRANSITIONS_FILE="/tmp/allowed-transitions.json"
# {
# "database": {
# "migrating": ["ready", "rollback"],
# "ready": ["migrating"],
# "rollback": ["ready"]
# },
# "cache": {
# "invalidating": ["ready"],
# "ready": ["invalidating"]
# }
# }
# --- Validate transition ---
validate_transition() {
local service=$1 from_state=$2 to_state=$3
# Check if this transition is allowed
local allowed
allowed=$(jq -r --arg svc "$service" --arg from "$from_state" \
'.[$svc][$from] // [] | map(select(. == $to_state)) | length' \
"$TRANSITIONS_FILE")
if [[ "$allowed" -eq 0 ]]; then
echo "❌ BLOCKED: $service $from_state → $to_state is not allowed"
echo " Allowed transitions from $from_state:"
jq -r --arg svc "$service" --arg from "$from_state" \
'.[$svc][$from] // [] | .[] | " - \(.)"' "$TRANSITIONS_FILE"
return 1
fi
echo "✅ ALLOWED: $service $from_state → $to_state"
return 0
}
# --- Execute transition with validation ---
execute_transition() {
local service=$1 from_state=$2 to_state=$3 action_fn=$4
# Validate the transition
if ! validate_transition "$service" "$from_state" "$to_state"; then
return 1
fi
# Execute the action
echo "Executing: $service $from_state → $to_state"
if $action_fn; then
# Update state file
local state
state=$(cat "$STATE_FILE")
jq --arg svc "$service" --arg from "$from_state" --arg to "$to_state" \
--argjson step $(( $(echo "$state" | jq -r '.step') + 1 )) \
'.current_state = $to | .target_state = null | .step = $step | .updated_at = now' \
"$STATE_FILE" > "${STATE_FILE}.tmp"
mv "${STATE_FILE}.tmp" "$STATE_FILE"
echo "✅ Transition complete for $service"
return 0
else
echo "❌ Action failed for $service"
return 1
fi
}
# --- State queries ---
get_current_state() {
local service=$1
if [[ -f "$STATE_FILE" ]]; then
jq -r --arg s "$service" '.current_state // "unknown"' "$STATE_FILE"
else
echo "unknown"
fi
}
get_transition_history() {
if [[ -f "$STATE_FILE" ]]; then
echo "=== Deployment State History ==="
jq -r '"\(.updated_at) | \(.current_state) (step \(.step))"' "$STATE_FILE"
else
echo "No state history found"
fi
}
# --- Usage ---
# init_state "ready"
# execute_transition "database" "ready" "migrating" "psql -c 'ALTER TABLE ...'"
# execute_transition "cache" "ready" "invalidating" "redis-cli FLUSHDB"
# get_transition_history
```
---
## Configuration Examples
### Migration Configuration
```yaml
migration:
strategy: backward_compatible
phases:
- name: expand
actions: [add_column, add_index, add_table]
safety: additive_only
- name: dual_write
actions: [dual_write, backfill]
safety: write_both
- name: read_new
actions: [deploy_new_code, read_new_column]
safety: read_new
- name: contract
actions: [drop_old_column, drop_old_table, remove_dual_write]
safety: remove_old
cache:
invalidate_before_deploy: true
invalidate_on_format_change: true
version_prefix: true
```
---
## Constraints
### MUST DO
- **Always design additive-first schema changes** — Add columns/tables before removing old ones
- **Run read migrations before deploying new code** — New code must be able to read both old and new format during transition
- **Dual-write during deployment** — New code must write to both old and new formats simultaneously
- **Validate data completeness before dropping old schema** — Never drop a column without verifying the new one has all data
- **Invalidate cache before deploying code that changes cache format** — Stale cache entries are a deployment risk
- **Test rollback of schema changes** — Schema rollback is harder than code rollback; test it first
### MUST NOT DO
- **Never drop or modify existing columns during deployment** — Old code will crash; use additive-only changes
- **Never deploy new code that reads a different cache format without invalidation** — Stale cache = corrupted data
- **Never skip the dual-write phase** — Going straight from old to new format creates a data gap
- **Never remove old code while old schema is still in use** — Both must be retired in the correct order
- **Never run a destructive migration on production without a tested rollback** — Destructive = irreversible by definition
---
## Philosophy Alignment
This skill adheres to the **5 Laws of Elegant Defense**:
### Early Exit
- Schema changes that aren't additive exit early — the migration is rejected before execution
- Cache invalidation fails fast — if invalidation can't complete, deployment is blocked
### Parse Don't Validate
- Migration steps are parsed from a declarative configuration
- Internal validation trusts parsed migration order, never re-validates sequencing
### Atomic Predictability
- Each migration phase is independently verifiable: expand, dual_write, read_new, contract
- Cache key generation is deterministic — same inputs always produce same keys
### Fail Fast
- Any validation failure blocks the transition — no partial state changes
- Cache version mismatch is treated as a cache miss, not a data error
### Intentional Naming
- Migration phases (`expand`, `dual_write`, `read_new`, `contract`) describe the actual state change
- `invalidate_version()` vs `invalidate_all_versions()` — scope is explicit
---
## Related Skills
| Skill Name | When to Use | Relationship |
|------------|------------|--------------|
| `deployment-philosophy` | Use **before** this skill to assess if state changes are involved in your deployment | Prerequisite: change risk assessment |
| `blue-green-deployment` | Use **alongside** when deploying with database schema changes | Complementary: schema + deployment coordination |
| `rollback-strategy` | Use **alongside** for data-aware rollback procedures | Complementary: rollback must handle state changes |
| `deployment-orchestration` | Use **alongside** for multi-service deployments with shared data | Complementary: coordinates data + code deployment |
---
## Output Template
When applying this skill, your output should contain:
1. **State Change Classification** — Type of state change (schema, cache, data format)
2. **Migration Steps** — Phase-by-phase migration plan with SQL/config
3. **Compatibility Matrix** — Which code versions can read/write which schema
4. **Cache Invalidation Plan** — What to invalidate and when
5. **Rollback Plan** — How to reverse each migration step
---
**Skill Version:** 1.0.0
**Created:** 2026-05-15
**Maturity:** stable
**Completeness:** 95%
---
## Live References
> Authoritative documentation links for this skill's domain. The model follows markdown links at load time to resolve external references and inline content.
- [Primary Documentation](https://kubernetes.io/docs/concepts/architecture/controlling-state/)
- [API Reference or Getting Started](https://kubernetes.io/docs/concepts/configuration/organize-cluster-access-kubeconfig/)
- [Configuration Guide](https://kubernetes.io/docs/tasks/administer-cluster/backup-and-restore-etcd)
- [Best Practices](https://kubernetes.io/docs/concepts/storage/persistent-volumes/)
- [Common Patterns or Tutorials](https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/)