| Server IP : 35.80.110.71 / Your IP : 216.73.216.221 Web Server : Apache/2.4.58 (Ubuntu) System : Linux ip-172-31-21-44 6.17.0-1019-aws #19~24.04.1-Ubuntu SMP Tue Jun 23 18:53:06 UTC 2026 x86_64 User : ubuntu ( 1000) PHP Version : 8.3.31 Disable Function : NONE MySQL : OFF | cURL : ON | WGET : ON | Perl : ON | Python : OFF | Sudo : ON | Pkexec : OFF Directory : /var/www/splitstream/current/database/migrations/ |
Upload File : |
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
// assignments is PARTITIONED BY HASH (experiment_id) into 16 children.
// PK is composite (experiment_id, unit_id) — sticky lookup is a
// single index seek into the correct partition. Per-partition
// secondary index on (unit_id) supports GET /v1/assignments/:unit_id;
// that endpoint scans all 16 partitions but each is an index seek,
// not a heap scan. Documented in docs/architecture/scaling.
//
// Laravel's schema builder doesn't support PARTITION BY directly, so
// we run raw SQL.
DB::statement(<<<'SQL'
CREATE TABLE assignments (
id CHAR(26) NOT NULL,
experiment_id CHAR(26) NOT NULL,
unit_id VARCHAR(256) NOT NULL,
variant_id CHAR(26) NOT NULL,
assignment_reason VARCHAR(32) NOT NULL DEFAULT 'bucketed',
assigned_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
PRIMARY KEY (experiment_id, unit_id)
) PARTITION BY HASH (experiment_id);
SQL);
// Create 16 hash partitions
for ($i = 0; $i < 16; $i++) {
DB::statement(sprintf(
'CREATE TABLE assignments_p%d PARTITION OF assignments FOR VALUES WITH (MODULUS 16, REMAINDER %d);',
$i,
$i
));
DB::statement(sprintf(
'CREATE INDEX assignments_p%d_unit_idx ON assignments_p%d (unit_id);',
$i,
$i
));
}
// Mutex group claim — written on first call into any experiment in
// the group; SELECT FOR UPDATE on read to handle simultaneous-call
// race. Once written, never updated — INSERT-only at the DB grant
// level (defense in depth).
Schema::create('mutex_group_assignments', function (Blueprint $table) {
$table->char('mutex_group_id', 26);
$table->string('unit_id', 256);
$table->char('claiming_experiment_id', 26);
$table->timestamp('claimed_at')->useCurrent();
$table->primary(['mutex_group_id', 'unit_id']);
$table->foreign('mutex_group_id')->references('id')->on('mutex_groups')->cascadeOnDelete();
$table->foreign('claiming_experiment_id')->references('id')->on('experiments')->cascadeOnDelete();
});
}
public function down(): void
{
Schema::dropIfExists('mutex_group_assignments');
DB::statement('DROP TABLE IF EXISTS assignments CASCADE;');
}
};