- 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.
269 lines
11 KiB
PHP
269 lines
11 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
require_once __DIR__ . '/../../src/Framework/Database/Schema/SchemaCompiler.php';
|
|
require_once __DIR__ . '/../../src/Framework/Database/Schema/Blueprint.php';
|
|
require_once __DIR__ . '/../../src/Framework/Database/Schema/PartitionType.php';
|
|
require_once __DIR__ . '/../../src/Framework/Database/Schema/Partition.php';
|
|
require_once __DIR__ . '/../../src/Framework/Database/Schema/ColumnDefinition.php';
|
|
require_once __DIR__ . '/../../src/Framework/Database/Schema/IndexDefinition.php';
|
|
require_once __DIR__ . '/../../src/Framework/Database/Schema/IndexType.php';
|
|
require_once __DIR__ . '/../../src/Framework/Database/Schema/ForeignKeyDefinition.php';
|
|
require_once __DIR__ . '/../../src/Framework/Database/Schema/ForeignKeyAction.php';
|
|
require_once __DIR__ . '/../../src/Framework/Database/Schema/PostgreSQLSchemaCompiler.php';
|
|
require_once __DIR__ . '/../../src/Framework/Database/Schema/Commands/CreateTableCommand.php';
|
|
require_once __DIR__ . '/../../src/Framework/Database/Schema/Commands/DropTableCommand.php';
|
|
require_once __DIR__ . '/../../src/Framework/Database/Schema/Commands/RawCommand.php';
|
|
|
|
use App\Framework\Database\Schema\Blueprint;
|
|
use App\Framework\Database\Schema\PartitionType;
|
|
use App\Framework\Database\Schema\Partition;
|
|
use App\Framework\Database\Schema\PostgreSQLSchemaCompiler;
|
|
use App\Framework\Database\Schema\Commands\CreateTableCommand;
|
|
use App\Framework\Database\Schema\Commands\DropTableCommand;
|
|
|
|
echo "Testing PostgreSQL Table Partitioning\n";
|
|
echo "======================================\n\n";
|
|
|
|
try {
|
|
$pdo = new PDO(
|
|
'pgsql:host=db;dbname=michaelschiemer',
|
|
'postgres',
|
|
'StartSimple2024!'
|
|
);
|
|
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
|
|
|
echo "✅ Database connection established\n\n";
|
|
|
|
$compiler = new PostgreSQLSchemaCompiler();
|
|
|
|
// Cleanup
|
|
echo "Cleanup...\n";
|
|
$pdo->exec("DROP TABLE IF EXISTS sales_2024_q1 CASCADE");
|
|
$pdo->exec("DROP TABLE IF EXISTS sales_2024_q2 CASCADE");
|
|
$pdo->exec("DROP TABLE IF EXISTS sales_2024 CASCADE");
|
|
$pdo->exec("DROP TABLE IF EXISTS customers_eu CASCADE");
|
|
$pdo->exec("DROP TABLE IF EXISTS customers_us CASCADE");
|
|
$pdo->exec("DROP TABLE IF EXISTS customers_asia CASCADE");
|
|
$pdo->exec("DROP TABLE IF EXISTS customers CASCADE");
|
|
$pdo->exec("DROP TABLE IF EXISTS orders_p0 CASCADE");
|
|
$pdo->exec("DROP TABLE IF EXISTS orders_p1 CASCADE");
|
|
$pdo->exec("DROP TABLE IF EXISTS orders_p2 CASCADE");
|
|
$pdo->exec("DROP TABLE IF EXISTS orders_p3 CASCADE");
|
|
$pdo->exec("DROP TABLE IF EXISTS orders CASCADE");
|
|
echo "✅ Cleanup complete\n\n";
|
|
|
|
// Test 1: RANGE Partitioning (by date)
|
|
echo "Test 1: RANGE Partitioning\n";
|
|
echo "===========================\n";
|
|
|
|
$blueprint = new Blueprint('sales_2024');
|
|
$blueprint->bigInteger('id'); // Changed from bigIncrements to avoid auto PK
|
|
$blueprint->string('product_name');
|
|
$blueprint->decimal('amount', 10, 2);
|
|
$blueprint->date('sale_date');
|
|
$blueprint->primary('id', 'sale_date'); // PK must include partition key
|
|
$blueprint->partitionByRange('sale_date');
|
|
|
|
$createCommand = new CreateTableCommand('sales_2024', $blueprint);
|
|
$sql = $compiler->compile($createCommand);
|
|
|
|
echo "Creating partitioned table:\n";
|
|
echo $sql . "\n\n";
|
|
|
|
$pdo->exec($sql);
|
|
echo "✅ Partitioned table 'sales_2024' created\n\n";
|
|
|
|
// Create partitions
|
|
echo "Creating partitions for Q1 and Q2:\n";
|
|
|
|
$q1Partition = Partition::range('sales_2024_q1', "FOR VALUES FROM ('2024-01-01') TO ('2024-04-01')");
|
|
$q1Sql = $q1Partition->toSql('sales_2024');
|
|
echo $q1Sql . "\n";
|
|
$pdo->exec($q1Sql);
|
|
echo "✅ Partition Q1 created\n";
|
|
|
|
$q2Partition = Partition::range('sales_2024_q2', "FOR VALUES FROM ('2024-04-01') TO ('2024-07-01')");
|
|
$q2Sql = $q2Partition->toSql('sales_2024');
|
|
echo $q2Sql . "\n";
|
|
$pdo->exec($q2Sql);
|
|
echo "✅ Partition Q2 created\n\n";
|
|
|
|
// Insert test data
|
|
echo "Inserting test data:\n";
|
|
$pdo->exec("
|
|
INSERT INTO sales_2024 (id, product_name, amount, sale_date) VALUES
|
|
(1, 'Laptop', 1200.00, '2024-02-15'),
|
|
(2, 'Mouse', 25.00, '2024-03-10'),
|
|
(3, 'Keyboard', 75.00, '2024-05-20')
|
|
");
|
|
echo "✅ 3 records inserted (2 in Q1, 1 in Q2)\n\n";
|
|
|
|
// Verify data distribution
|
|
echo "Verifying partition data:\n";
|
|
$q1Count = $pdo->query("SELECT COUNT(*) FROM sales_2024_q1")->fetchColumn();
|
|
$q2Count = $pdo->query("SELECT COUNT(*) FROM sales_2024_q2")->fetchColumn();
|
|
echo " - Q1: {$q1Count} records\n";
|
|
echo " - Q2: {$q2Count} records\n";
|
|
echo ($q1Count == 2 && $q2Count == 1) ? "✅ Data correctly distributed\n\n" : "❌ Wrong distribution\n\n";
|
|
|
|
// Test 2: LIST Partitioning (by region)
|
|
echo "Test 2: LIST Partitioning\n";
|
|
echo "==========================\n";
|
|
|
|
$blueprint = new Blueprint('customers');
|
|
$blueprint->bigInteger('id');
|
|
$blueprint->string('name');
|
|
$blueprint->string('email');
|
|
$blueprint->string('region', 10);
|
|
$blueprint->primary('id', 'region'); // PK must include partition key
|
|
$blueprint->partitionByList('region');
|
|
|
|
$createCommand = new CreateTableCommand('customers', $blueprint);
|
|
$sql = $compiler->compile($createCommand);
|
|
|
|
echo "Creating list-partitioned table:\n";
|
|
echo $sql . "\n\n";
|
|
|
|
$pdo->exec($sql);
|
|
echo "✅ List-partitioned table 'customers' created\n\n";
|
|
|
|
// Create list partitions
|
|
echo "Creating regional partitions:\n";
|
|
|
|
$euPartition = Partition::list('customers_eu', "FOR VALUES IN ('DE', 'FR', 'IT', 'ES')");
|
|
$euSql = $euPartition->toSql('customers');
|
|
echo $euSql . "\n";
|
|
$pdo->exec($euSql);
|
|
echo "✅ EU partition created\n";
|
|
|
|
$usPartition = Partition::list('customers_us', "FOR VALUES IN ('US', 'CA', 'MX')");
|
|
$usSql = $usPartition->toSql('customers');
|
|
echo $usSql . "\n";
|
|
$pdo->exec($usSql);
|
|
echo "✅ US partition created\n";
|
|
|
|
$asiaPartition = Partition::list('customers_asia', "FOR VALUES IN ('JP', 'CN', 'IN', 'KR')");
|
|
$asiaSql = $asiaPartition->toSql('customers');
|
|
echo $asiaSql . "\n";
|
|
$pdo->exec($asiaSql);
|
|
echo "✅ ASIA partition created\n\n";
|
|
|
|
// Insert test data
|
|
echo "Inserting regional customers:\n";
|
|
$pdo->exec("
|
|
INSERT INTO customers (id, name, email, region) VALUES
|
|
(1, 'Hans Mueller', 'hans@example.de', 'DE'),
|
|
(2, 'Pierre Dupont', 'pierre@example.fr', 'FR'),
|
|
(3, 'John Smith', 'john@example.com', 'US'),
|
|
(4, 'Yuki Tanaka', 'yuki@example.jp', 'JP')
|
|
");
|
|
echo "✅ 4 customers inserted across regions\n\n";
|
|
|
|
// Verify regional distribution
|
|
echo "Verifying regional distribution:\n";
|
|
$euCount = $pdo->query("SELECT COUNT(*) FROM customers_eu")->fetchColumn();
|
|
$usCount = $pdo->query("SELECT COUNT(*) FROM customers_us")->fetchColumn();
|
|
$asiaCount = $pdo->query("SELECT COUNT(*) FROM customers_asia")->fetchColumn();
|
|
echo " - EU: {$euCount} customers\n";
|
|
echo " - US: {$usCount} customers\n";
|
|
echo " - ASIA: {$asiaCount} customers\n";
|
|
echo ($euCount == 2 && $usCount == 1 && $asiaCount == 1) ? "✅ Regional distribution correct\n\n" : "❌ Wrong distribution\n\n";
|
|
|
|
// Test 3: HASH Partitioning (for distributed load)
|
|
echo "Test 3: HASH Partitioning\n";
|
|
echo "==========================\n";
|
|
|
|
$blueprint = new Blueprint('orders');
|
|
$blueprint->bigInteger('id');
|
|
$blueprint->bigInteger('customer_id');
|
|
$blueprint->decimal('total', 10, 2);
|
|
$blueprint->timestamp('created_at');
|
|
$blueprint->primary('id', 'customer_id'); // PK must include partition key
|
|
$blueprint->partitionByHash('customer_id');
|
|
|
|
$createCommand = new CreateTableCommand('orders', $blueprint);
|
|
$sql = $compiler->compile($createCommand);
|
|
|
|
echo "Creating hash-partitioned table:\n";
|
|
echo $sql . "\n\n";
|
|
|
|
$pdo->exec($sql);
|
|
echo "✅ Hash-partitioned table 'orders' created\n\n";
|
|
|
|
// Create hash partitions (4 partitions, modulus 4)
|
|
echo "Creating 4 hash partitions:\n";
|
|
|
|
for ($i = 0; $i < 4; $i++) {
|
|
$partition = Partition::hash("orders_p{$i}", "FOR VALUES WITH (MODULUS 4, REMAINDER {$i})");
|
|
$partSql = $partition->toSql('orders');
|
|
echo $partSql . "\n";
|
|
$pdo->exec($partSql);
|
|
echo "✅ Partition p{$i} created\n";
|
|
}
|
|
echo "\n";
|
|
|
|
// Insert test data
|
|
echo "Inserting orders:\n";
|
|
for ($orderId = 1; $orderId <= 20; $orderId++) {
|
|
$customerId = ($orderId % 10) + 1; // Distribute across 10 customers
|
|
$pdo->exec("
|
|
INSERT INTO orders (id, customer_id, total, created_at)
|
|
VALUES ({$orderId}, {$customerId}, " . ($orderId * 50.00) . ", NOW())
|
|
");
|
|
}
|
|
echo "✅ 20 orders inserted (distributed by hash)\n\n";
|
|
|
|
// Verify hash distribution
|
|
echo "Verifying hash distribution:\n";
|
|
for ($i = 0; $i < 4; $i++) {
|
|
$count = $pdo->query("SELECT COUNT(*) FROM orders_p{$i}")->fetchColumn();
|
|
echo " - Partition p{$i}: {$count} orders\n";
|
|
}
|
|
$totalOrders = $pdo->query("SELECT COUNT(*) FROM orders")->fetchColumn();
|
|
echo ($totalOrders == 20) ? "✅ All orders accessible via parent table\n\n" : "❌ Missing orders\n\n";
|
|
|
|
// Test 4: Check partition metadata
|
|
echo "Test 4: Partition Metadata\n";
|
|
echo "===========================\n";
|
|
|
|
$stmt = $pdo->query("
|
|
SELECT
|
|
inhrelid::regclass AS partition_name,
|
|
inhparent::regclass AS parent_table
|
|
FROM pg_inherits
|
|
WHERE inhparent::regclass::text IN ('sales_2024', 'customers', 'orders')
|
|
ORDER BY parent_table, partition_name
|
|
");
|
|
|
|
$partitions = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|
echo "Found " . count($partitions) . " partitions:\n";
|
|
foreach ($partitions as $partition) {
|
|
echo " - {$partition['partition_name']} → {$partition['parent_table']}\n";
|
|
}
|
|
echo (count($partitions) == 9) ? "\n✅ All partitions registered\n\n" : "\n❌ Wrong partition count\n\n";
|
|
|
|
// Cleanup
|
|
echo "Cleanup...\n";
|
|
$pdo->exec("DROP TABLE IF EXISTS sales_2024 CASCADE");
|
|
$pdo->exec("DROP TABLE IF EXISTS customers CASCADE");
|
|
$pdo->exec("DROP TABLE IF EXISTS orders CASCADE");
|
|
echo "✅ Test data cleaned up\n";
|
|
|
|
echo "\n✅ All Table Partitioning tests passed!\n";
|
|
echo "\nSummary:\n";
|
|
echo "========\n";
|
|
echo "✅ RANGE partitioning works (by date)\n";
|
|
echo "✅ LIST partitioning works (by discrete values)\n";
|
|
echo "✅ HASH partitioning works (distributed load)\n";
|
|
echo "✅ Data correctly routed to partitions\n";
|
|
echo "✅ Parent table provides unified access\n";
|
|
echo "✅ Partition metadata correctly tracked\n";
|
|
|
|
} catch (\Exception $e) {
|
|
echo "❌ Error: " . $e->getMessage() . "\n";
|
|
echo "Stack trace:\n" . $e->getTraceAsString() . "\n";
|
|
exit(1);
|
|
}
|