100 Milyon Satırlık Duvar
Smart Tech Devs’deki kurumsal B2B SaaS platformlarında, tarihsel verileri takip etmek bir uyum gerekliliğidir. audit_logs, api_requests veya telemetry_events gibi tablolar, hızlı bir şekilde büyür. Bir PostgreSQL tablosu 100 milyon satıra ulaştığında, standart B-Tree indeksleri devasa hale gelir ve RAM’e sığmaz. Sorgu performansı milisaniyelerden saniyelere düşer.
Daha da kötüleşen bir durum, eski verileri temizlemek için gerekli olan işlemin zorluğudur. Büyük bir tabloda standart bir DELETE FROM audit_logs WHERE created_at komutunu çalıştırmak, devasa bir işlem kilidi tetikler, gelen eklemeleri engeller ve veritabanını ölü tuplar ile doldurur (bu da pahalı VACUUM işlemlerini gerektirir). Sonsuz ölçek için mimari tasarlamak istediğinizde, monoliti kırmanız gerekecek; burada devreye Table Partitioning giriyor.
Çözüm: Aralık Bölümlendirme
PostgreSQL’in yerel tablo bölümlendirmesi, tek bir devasa mantıksal tabloyu arka planda birden fazla daha küçük fiziksel tabloya bölmenizi sağlar. Zaman serisi verileri için, ay bazında Range Partitioning kullanıyoruz.
Larael uygulamasına göre hala AuditLog::all() sorgusunu kullanabilirsiniz. Ancak PostgreSQL sorguyu yakalar ve hemen belirli fiziksel tabloya yönlendirir (örneğin, audit_logs_2026_06). İki yıl öncesinden daha eski verileri silmeniz gerektiğinde, eski bölüm tablosunu basitçe kaldırabilirsiniz. Bu işlem 10 milisaniyede gerçekleşir, sıfır CPU kullanır ve sıfır tablo kilidi oluşturur.
Adım 1: Bölümlendirilmiş Migration’ı Tasarlamak
Laravel’in varsayılan Blueprint’i yerel bölümlendirmeyi desteklemediği için, migration’ımızda kök tabloyu ve ilk birkaç aylık bölümü oluşturmak için ham SQL kullanıyoruz.
use Illuminate\Database\Migrations\Migration;
use Illuminate\Support\Facades\DB;
class CreatePartitionedAuditLogsTable extends Migration
{
public function up(): void
{
// 1. Ana Tabloyu Oluştur (Mantıksal sargısı)
// Dikkat edin, standart bir birincil anahtar oluşturmuyoruz, çünkü
// bölüm anahtarları herhangi bir benzersiz indeksin parçası olmalıdır.
DB::statement('
CREATE TABLE audit_logs (
id UUID NOT NULL,
tenant_id BIGINT NOT NULL,
action VARCHAR(255) NOT NULL,
created_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL
) PARTITION BY RANGE (created_at);
');
// 2. Gelecek aylar için Fiziksel Bölümleri Oluştur
DB::statement("
CREATE TABLE audit_logs_2026_05
PARTITION OF audit_logs
FOR VALUES FROM ('2026-05-01 00:00:00') TO ('2026-06-01 00:00:00');
");
DB::statement("
CREATE TABLE audit_logs_2026_06
PARTITION OF audit_logs
FOR VALUES FROM ('2026-06-01 00:00:00') TO ('2026-07-01 00:00:00');
");
// 3. Ana tabloda indeksler oluşturun (PostgreSQL bunları otomatik olarak parçalara uygular)
DB::statement('CREATE INDEX audit_logs_tenant_idx ON audit_logs (tenant_id);');
}
public function down(): void
{
DB::statement('DROP TABLE IF EXISTS audit_logs CASCADE;');
}
}
Adım 2: Gelecek Bölümlerin Otomasyonu
Mevcut olmayan bir bölüme veri ekleyemeyeceğiniz için, bölümlerin oluşturulmasını otomatikleştirmeniz gerekir. Laravel’de, her ayın 25’inde çalışacak basit bir zamanlanmış komut kuruyoruz.
// app/Console/Commands/CreateNextMonthPartition.php
$nextMonth = now()->addMonth();
$tableName="audit_logs_" . $nextMonth->format('Y_m');
$start = $nextMonth->startOfMonth()->toDateTimeString();
$end = $nextMonth->addMonth()->startOfMonth()->toDateTimeString();
DB::statement("
CREATE TABLE IF NOT EXISTS {$tableName}
PARTITION OF audit_logs
FOR VALUES FROM ('{$start}') TO ('{$end}');
");
Mühendislik ROI’si
Table Partitioning, zaman serisi logları için nihai veritabanı ölçeklenebilirlik desenidir. Aktif indekslerinizin küçük ve RAM’de tam yüklü kalmasını sağlar, böylece son verilerin sorgulanması son derece hızlı hale gelir. Daha da önemlisi, 50 milyon eski kaydı silme operasyonunu, zararsız bir şekilde 10 milisaniyelik bir DROP TABLE komutuna dönüştürerek, SaaS’ınızın bakım penceresi çökmesi yaşamamasını garanti eder.
Kaynak: Orijinal Makale


