# Django-Syzygy: Safe Zero-Downtime Database Migrations

## Introduction

Django-Syzygy is a Django companion that makes database migrations safer in real production environments. It focuses on one specific, painful problem: migrations that are technically valid, but unsafe during rolling deployments.

Syzygy works with Django’s existing migration system and workflow. It doesn’t replace it, and it doesn’t require you to learn a new mental model. It quietly adds structure where Django is intentionally flexible, so deployments stop being a source of stress.

## What Makes It Different?

Consider a common change: removing a database field that’s no longer needed.

In development, this is trivial. In production, during a rolling deploy, it can break running instances that still expect that column to exist. Django doesn’t protect you from that class of failure.

Syzygy does.

**The natural workflow:**

1. You change your model (for example, remove a field)
    
2. Run `makemigrations` as usual
    
3. Syzygy detects a potentially unsafe change and asks for minimal input
    
4. It automatically splits the change into safe deployment stages
    

You keep using Django’s commands. Syzygy handles the staging.

## Why Django-Syzygy Exists

### The Problem

Django migrations are excellent at describing *what* should change, but they don’t encode *when* a change is safe to apply relative to your application code.

A classic failure mode looks like this:

**Without Syzygy:**

* You remove a field from a model
    
* A migration drops the column
    
* You deploy new code
    
* Old application instances are still running
    
* Migration runs → database column disappears → runtime errors
    

This is not a rare edge case. It’s a structural issue with rolling deployments.

**With Syzygy:**

* The migration is automatically split into deployment-safe stages
    
* Changes that old code can tolerate run first
    
* Code is deployed
    
* Breaking changes run only after old code is gone
    

The result is not “perfect safety,” but the elimination of an entire category of deployment-time failures.

## What Syzygy Actually Does

Syzygy integrates with Django’s migration autodetector and migration system:

* It analyzes generated migrations for deployment safety
    
* It assigns or infers a **stage** (pre-deploy or post-deploy)
    
* When necessary, it splits migrations automatically
    
* It enforces consistency via Django system checks
    

It does **not** replace Django’s migration engine or alter database behavior. All migrations are still standard Django migrations.

## How It Works in Practice

### Removing a Field

```python
# models.py
class MyModel(models.Model):
    name = models.CharField(max_length=100)
    # old_field removed
```

```bash
python manage.py makemigrations
```

Syzygy detects that removing a field is unsafe if old code is still running, and asks for a temporary default value so existing rows remain compatible.

It generates two migrations:

```python
# 0001_pre_deploy.py
operations = [
    syzygy.operations.AlterField(
        model_name='mymodel',
        name='old_field',
        field=models.CharField(default='safe_default'),
    ),
]
```

```python
# 0002_remove_field.py
operations = [
    migrations.RemoveField(
        model_name='mymodel',
        name='old_field',
    ),
]
```

The first migration keeps old application instances working.  
The second removes the field only after deployment.

### Adding a Field Without a Default

```python
class MyModel(models.Model):
    name = models.CharField(max_length=100)
    new_field = models.CharField(max_length=200)
```

Syzygy ensures the database can accept writes from old code:

```python
# 0001_add_field.py (pre-deploy)
operations = [
    migrations.AddField(
        model_name='mymodel',
        name='new_field',
        field=models.CharField(
            max_length=200,
            db_default='temp_default',
        ),
    ),
]
```

```python
# 0002_drop_db_default.py (post-deploy)
operations = [
    syzygy.operations.AlterField(
        model_name='mymodel',
        name='new_field',
        field=models.CharField(max_length=200),
        stage=syzygy.constants.Stage.POST_DEPLOY,
    ),
]
```

The database default exists only to bridge the deployment window. Syzygy removes it once the new code is fully live.

*(PostgreSQL is the recommended database; support for other backends depends on their DDL capabilities.)*

## Pre-Deploy vs Post-Deploy

Syzygy classifies operations based on one rule:

**Old code must survive pre-deploy changes.  
New code must survive post-deploy changes.**

Typical classification:

Pre-Deploy:

* Adding columns or tables
    
* Adding indexes
    
* Adding database defaults
    
* Creating new constraints
    

Post-Deploy:

* Removing or renaming fields
    
* Dropping constraints
    
* Destructive schema changes
    

If Syzygy can’t determine this safely, it fails early.

## Installation & Setup

```bash
pip install django-syzygy
```

```python
INSTALLED_APPS = [
    # ...
    'syzygy',
]
```

Optional configuration for edge cases:

```python
from syzygy import Stage

MIGRATION_THIRD_PARTY_STAGES_FALLBACK = Stage.PRE_DEPLOY

MIGRATION_STAGES_OVERRIDE = {
    'problematic_app.0001_bad_migration': Stage.POST_DEPLOY,
}
```

## Daily Workflow

### Development

Nothing changes:

```bash
python manage.py makemigrations
python manage.py check
python manage.py migrate
```

### Production Deployment

```bash
# Apply safe changes
python manage.py migrate --pre-deploy

# Deploy application code

# Apply remaining changes
python manage.py migrate
```

If you forget `--pre-deploy`, `migrate` still works. The flag enables phased deployments; it doesn’t break existing pipelines.

## CI/CD Integration

The most valuable part of Syzygy in CI is simple:

**Migration staging mistakes become CI failures instead of production incidents.**

```yaml
script:
  - python manage.py check
  - python manage.py makemigrations --dry-run --check
```

Syzygy’s system checks ensure migrations are internally consistent and safely staged.

## When Syzygy Complains

### “Cannot automatically determine stage”

This means a migration mixes safe and unsafe operations in a way that requires human intent.

You can:

* Split the migration
    
* Explicitly assign a stage
    
* Override it in settings
    

The important part: the failure happens early, not during deployment.

## Best Practices

Syzygy doesn’t replace good judgment:

* Review generated migrations
    
* Test migrations in staging
    
* Prefer additive changes over destructive ones
    
* Document complex transitions
    

Syzygy enforces structure; it doesn’t remove responsibility.

## Who This Is For

Django-Syzygy is most useful for:

* Teams running rolling or blue-green deployments
    
* Kubernetes/ ArgoCD / Docker environments
    
* Applications with real uptime requirements
    

It’s probably unnecessary for:

* Solo projects
    
* Development-only environments
    
* Apps that can afford downtime
    

## Conclusion

Django-Syzygy makes deployment-unsafe migrations explicit and enforceable. It doesn’t promise perfection. It removes an entire class of avoidable production failures by encoding deployment reality directly into Django migrations.

You keep Django’s workflow.  
You gain deployment safety.  
And migration anxiety becomes a lot rarer.

That’s the whole point.
