Writing Skills
writing-skills
效率与协作技能,来自 obra/superpowers,查看原文获取完整用途和操作说明。
state-management【部署与运维】Manages application state across deployment stages including database migrations, cache invalidation, and data consistency during rolling updates.
独立 GitHub 快照 · 自动分类 · 以原作者文档为准
name: state-management compatibility: opencode completeness: 95 content-types:
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.
Use this skill when:
Avoid this skill for:
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.
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.
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.
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.
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).
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.
Implement schema changes that are safe during blue-green or canary deployments where old and new versions coexist.
# ❌ 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:
# ✅ 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
Manage cache invalidation during deployments to prevent stale or corrupted cache entries.
# ❌ 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 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"; }
Validate that state transitions during deployment are safe and reversible.
# ✅ 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
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
This skill adheres to the 5 Laws of Elegant Defense:
expand, dual_write, read_new, contract) describe the actual state changeinvalidate_version() vs invalidate_all_versions() — scope is explicit| 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 |
When applying this skill, your output should contain:
Skill Version: 1.0.0
Created: 2026-05-15
Maturity: stable
Completeness: 95%
Authoritative documentation links for this skill's domain. The model follows markdown links at load time to resolve external references and inline content.