feat(Production): Complete production deployment infrastructure

- Add comprehensive health check system with multiple endpoints
- Add Prometheus metrics endpoint
- Add production logging configurations (5 strategies)
- Add complete deployment documentation suite:
  * QUICKSTART.md - 30-minute deployment guide
  * DEPLOYMENT_CHECKLIST.md - Printable verification checklist
  * DEPLOYMENT_WORKFLOW.md - Complete deployment lifecycle
  * PRODUCTION_DEPLOYMENT.md - Comprehensive technical reference
  * production-logging.md - Logging configuration guide
  * ANSIBLE_DEPLOYMENT.md - Infrastructure as Code automation
  * README.md - Navigation hub
  * DEPLOYMENT_SUMMARY.md - Executive summary
- Add deployment scripts and automation
- Add DEPLOYMENT_PLAN.md - Concrete plan for immediate deployment
- Update README with production-ready features

All production infrastructure is now complete and ready for deployment.
This commit is contained in:
2025-10-25 19:18:37 +02:00
parent caa85db796
commit fc3d7e6357
83016 changed files with 378904 additions and 20919 deletions

View File

@@ -0,0 +1,420 @@
# Safe vs Unsafe Migration Examples
This document provides examples of migrations that are safely reversible vs those that are not.
## ✅ Safe Rollback Examples (implement SafelyReversible)
### 1. Create New Table
```php
use App\Framework\Database\Migration\{Migration, SafelyReversible};
/**
* Safe: New empty table can be dropped without data loss
*/
final readonly class CreateSessionsTable implements Migration, SafelyReversible
{
public function up(ConnectionInterface $connection): void
{
$schema = new Schema($connection);
$schema->create('sessions', function ($table) {
$table->string('id', 255)->primary();
$table->text('data');
$table->timestamp('created_at');
});
$schema->execute();
}
public function down(ConnectionInterface $connection): void
{
// Safe: Table is new, no existing data
$schema = new Schema($connection);
$schema->dropIfExists('sessions');
$schema->execute();
}
}
```
### 2. Add Nullable Column
```php
/**
* Safe: Adding nullable column can be dropped without data loss
*/
final readonly class AddPhoneToUsersTable implements Migration, SafelyReversible
{
public function up(ConnectionInterface $connection): void
{
$schema = new Schema($connection);
$schema->table('users', function ($table) {
$table->string('phone', 20)->nullable();
});
$schema->execute();
}
public function down(ConnectionInterface $connection): void
{
// Safe: Column is nullable and likely empty or not critical
$schema = new Schema($connection);
$schema->table('users', function ($table) {
$table->dropColumn('phone');
});
$schema->execute();
}
}
```
### 3. Create Index
```php
/**
* Safe: Indexes don't affect data, only query performance
*/
final readonly class AddEmailIndexToUsers implements Migration, SafelyReversible
{
public function up(ConnectionInterface $connection): void
{
$schema = new Schema($connection);
$schema->table('users', function ($table) {
$table->index('email', 'idx_users_email');
});
$schema->execute();
}
public function down(ConnectionInterface $connection): void
{
// Safe: Dropping index doesn't lose data
$schema = new Schema($connection);
$schema->table('users', function ($table) {
$table->dropIndex('idx_users_email');
});
$schema->execute();
}
}
```
### 4. Rename Column (Data Preserved)
```php
/**
* Safe: Renaming preserves all data
*/
final readonly class RenameEmailColumnInUsers implements Migration, SafelyReversible
{
public function up(ConnectionInterface $connection): void
{
$schema = new Schema($connection);
$schema->table('users', function ($table) {
$table->renameColumn('email', 'email_address');
});
$schema->execute();
}
public function down(ConnectionInterface $connection): void
{
// Safe: Rename back, all data intact
$schema = new Schema($connection);
$schema->table('users', function ($table) {
$table->renameColumn('email_address', 'email');
});
$schema->execute();
}
}
```
### 5. Add Foreign Key
```php
/**
* Safe: Foreign keys are constraints, can be removed without data loss
*/
final readonly class AddUserIdForeignKey implements Migration, SafelyReversible
{
public function up(ConnectionInterface $connection): void
{
$schema = new Schema($connection);
$schema->table('orders', function ($table) {
$table->foreign('user_id')
->references('id')
->on('users')
->onDelete(ForeignKeyAction::CASCADE);
});
$schema->execute();
}
public function down(ConnectionInterface $connection): void
{
// Safe: Removing constraint doesn't delete data
$schema = new Schema($connection);
$schema->table('orders', function ($table) {
$table->dropForeign('user_id');
});
$schema->execute();
}
}
```
---
## ❌ Unsafe Rollback Examples (DO NOT implement SafelyReversible)
### 1. Drop Column with Data
```php
/**
* UNSAFE: Dropping column with data - NOT reversible
* DO NOT implement SafelyReversible!
*/
final readonly class RemoveLegacyIdColumn implements Migration
{
public function up(ConnectionInterface $connection): void
{
$schema = new Schema($connection);
$schema->table('users', function ($table) {
$table->dropColumn('legacy_id'); // Data is LOST!
});
$schema->execute();
}
// NO down() method - data cannot be recovered
}
```
**Why Unsafe?** Once the column is dropped, all data in that column is permanently lost. You cannot restore it via rollback.
**Fix-Forward Instead:**
```php
// If you need to restore it, create a new migration:
final readonly class RestoreLegacyIdColumn implements Migration, SafelyReversible
{
public function up(ConnectionInterface $connection): void
{
$schema = new Schema($connection);
$schema->table('users', function ($table) {
$table->integer('legacy_id')->nullable();
});
$schema->execute();
// Note: Original data is gone, you'll need to repopulate
}
}
```
### 2. Data Transformation
```php
/**
* UNSAFE: Data format transformation - original format lost
* DO NOT implement SafelyReversible!
*/
final readonly class MigratePreferencesToJsonb implements Migration
{
public function up(ConnectionInterface $connection): void
{
// Transform multiple columns into single JSONB column
$connection->execute("
UPDATE users
SET preferences = jsonb_build_object(
'theme', theme_preference,
'notifications', notification_preference,
'language', language_preference
)
");
$schema = new Schema($connection);
$schema->table('users', function ($table) {
$table->dropColumn('theme_preference', 'notification_preference', 'language_preference');
});
$schema->execute();
}
// NO down() - original column structure is gone
}
```
**Why Unsafe?** Once you've merged columns into JSONB and dropped the originals, you cannot perfectly restore the original schema structure.
### 3. Data Aggregation/Merge
```php
/**
* UNSAFE: Merging tables loses original structure
* DO NOT implement SafelyReversible!
*/
final readonly class MergeUserProfilesIntoUsers implements Migration
{
public function up(ConnectionInterface $connection): void
{
// Merge user_profiles data into users table
$connection->execute("
UPDATE users u
SET bio = up.bio,
avatar = up.avatar,
settings = up.settings
FROM user_profiles up
WHERE u.id = up.user_id
");
$schema = new Schema($connection);
$schema->dropIfExists('user_profiles');
$schema->execute();
}
// NO down() - user_profiles table structure is lost
}
```
**Why Unsafe?** The original `user_profiles` table schema and any additional data it contained is lost forever.
### 4. Change Column Type (Data Loss Risk)
```php
/**
* UNSAFE: Changing column type may lose precision/data
* DO NOT implement SafelyReversible!
*/
final readonly class ChangeUserAgeToInteger implements Migration
{
public function up(ConnectionInterface $connection): void
{
$schema = new Schema($connection);
$schema->table('users', function ($table) {
$table->changeColumn('age', 'integer'); // Was VARCHAR, now INT
});
$schema->execute();
}
// NO down() - original string format may have been lost during conversion
}
```
**Why Unsafe?** Converting from string to integer may lose data (e.g., "25 years" becomes 25, losing " years"). Converting back doesn't restore original format.
### 5. Truncate/Delete Data
```php
/**
* UNSAFE: Deleting data permanently
* DO NOT implement SafelyReversible!
*/
final readonly class CleanupOldSessions implements Migration
{
public function up(ConnectionInterface $connection): void
{
$connection->execute("
DELETE FROM sessions
WHERE created_at < NOW() - INTERVAL '90 days'
");
}
// NO down() - deleted data is gone forever
}
```
**Why Unsafe?** Deleted data cannot be recovered via rollback.
---
## Decision Matrix: SafelyReversible or Not?
| Operation | SafelyReversible? | Reason |
|-----------|-------------------|--------|
| Create table | ✅ Yes | New table can be dropped |
| Drop table (with data) | ❌ No | Data is lost |
| Add nullable column | ✅ Yes | Column likely empty |
| Drop column (with data) | ❌ No | Data is lost |
| Add NOT NULL column | ⚠️ Maybe | Only if default value is safe |
| Change column type | ❌ No | Data format may be lost |
| Rename column | ✅ Yes | Data is preserved |
| Create index | ✅ Yes | Only affects performance |
| Drop index | ✅ Yes | Only affects performance |
| Add foreign key | ✅ Yes | Only adds constraint |
| Drop foreign key | ✅ Yes | Only removes constraint |
| Data transformation | ❌ No | Original format lost |
| Merge tables | ❌ No | Original structure lost |
| Delete data | ❌ No | Data is gone |
| Aggregate data | ❌ No | Original granularity lost |
---
## Best Practices
### 1. Default to Forward-Only
```php
// Default: Only implement Migration interface
final readonly class MyMigration implements Migration
{
public function up(ConnectionInterface $connection): void
{
// Migration logic
}
// No down() unless CERTAIN it's safe
}
```
### 2. Document Why SafelyReversible
```php
/**
* This migration is safely reversible because:
* - Only creates a new empty table
* - No existing data is affected
* - Can be dropped without data loss
*/
final readonly class CreateCacheTable implements Migration, SafelyReversible
{
// ...
}
```
### 3. Use Fix-Forward for Errors
Instead of rollback:
```bash
# Instead of:
php console.php db:rollback 1
# Do:
php console.php make:migration FixUserTableColumnName
```
### 4. Test Rollback in Development
If implementing SafelyReversible:
```bash
# Test the full cycle in dev
php console.php db:migrate
php console.php db:rollback 1
php console.php db:migrate # Should work again
```
### 5. Production: Forward-Only Mindset
In production, even "safe" rollbacks should be avoided:
- Create fix-forward migration instead
- Test thoroughly in staging first
- Never rollback without backup
- Document why rollback was needed
---
## Summary
**Implement SafelyReversible when:**
- ✅ Creating new tables
- ✅ Adding nullable columns
- ✅ Creating/dropping indexes
- ✅ Renaming columns (data preserved)
- ✅ Adding/removing constraints
**DO NOT implement SafelyReversible when:**
- ❌ Dropping columns with data
- ❌ Transforming data formats
- ❌ Changing column types
- ❌ Merging/splitting tables
- ❌ Deleting data
- ❌ Any operation that loses information
**When in doubt:** Don't implement SafelyReversible. Use fix-forward migrations instead.