Laravel UUID v6.2.0

Binary UUID Storage (55% Space Savings)

webpatser@dev: ~/laravel-uuid/6.2.0 $ cat binary-storage.md

Binary UUID Storage - Laravel UUID v6.2.0

Save 55% database space by storing UUIDs as 16-byte binary data instead of 36-character strings. This guide shows you how to implement transparent binary UUID storage in Laravel.

💾

Storage Comparison

String UUID: 36 characters = 36 bytes
Binary UUID: 16 bytes = 16 bytes
Space Savings: 20 bytes per UUID (55.6% reduction)
Million records: ~20MB saved per UUID column

Basic Binary UUID Model

Use the HasBinaryUuids trait for automatic binary storage:

use Illuminate\Database\Eloquent\Model;
use Webpatser\LaravelUuid\HasBinaryUuids;
use Webpatser\LaravelUuid\BinaryUuidCast;
 
class User extends Model
{
use HasBinaryUuids; // Automatic binary UUID support
 
protected $casts = [
'id' => BinaryUuidCast::class, // Auto-converts binary ↔ UUID
'parent_id' => BinaryUuidCast::class, // Handles nullable columns
];
 
protected $fillable = ['name', 'email'];
}
 
// Usage - works exactly like string UUIDs
$user = User::create(['name' => 'John', 'email' => '[email protected]']);
echo $user->id; // Displays as string: "550e8400-e29b-41d4-a716-446655440000"
echo $user->getUuidAsString(); // Explicit string conversion
$user->setUuidFromString($uuid); // Set from string UUID

Database Migration

Create tables with binary UUID columns using our migration helpers:

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Webpatser\LaravelUuid\BinaryUuidMigrations;
 
return new class extends Migration
{
public function up()
{
Schema::create('users', function (Blueprint $table) {
// Creates optimal binary column for your database
BinaryUuidMigrations::addBinaryUuidPrimary($table);
 
// Add nullable binary UUID foreign key
BinaryUuidMigrations::addBinaryUuidColumn($table, 'parent_id', true);
 
$table->string('name');
$table->string('email')->unique();
$table->timestamps();
 
// Index for performance
$table->index('parent_id');
});
}
};

Database-Specific Implementation

Database
Column Type
Storage Size
MySQL/MariaDB BINARY(16) 16 bytes
PostgreSQL bytea 16 bytes
SQLite BLOB 16 bytes
SQL Server uniqueidentifier 16 bytes

Generated SQL Examples

Here's what the migration helpers generate for each database:

-- MySQL/MariaDB
CREATE TABLE users (
id BINARY(16) PRIMARY KEY, -- 16 bytes (55% savings!)
parent_id BINARY(16) NULL, -- 16 bytes
name VARCHAR(255) NOT NULL,
email VARCHAR(255) NOT NULL UNIQUE
);
 
-- PostgreSQL
CREATE TABLE users (
id bytea PRIMARY KEY, -- 16 bytes (55% savings!)
parent_id bytea NULL, -- 16 bytes
name VARCHAR(255) NOT NULL,
email VARCHAR(255) NOT NULL UNIQUE
);
 
-- SQLite
CREATE TABLE users (
id BLOB PRIMARY KEY, -- 16 bytes (55% savings!)
parent_id BLOB NULL, -- 16 bytes
name TEXT NOT NULL,
email TEXT NOT NULL UNIQUE
);
 
-- SQL Server
CREATE TABLE users (
id uniqueidentifier PRIMARY KEY, -- 16 bytes (native UUID support!)
parent_id uniqueidentifier NULL, -- 16 bytes
name NVARCHAR(255) NOT NULL,
email NVARCHAR(255) NOT NULL UNIQUE
);

Binary UUID Relationships

Binary UUIDs work seamlessly with Eloquent relationships:

class User extends Model
{
use HasBinaryUuids;
 
protected $casts = [
'id' => BinaryUuidCast::class,
];
 
public function posts()
{
return $this->hasMany(Post::class);
}
 
public function parent()
{
return $this->belongsTo(User::class, 'parent_id');
}
}
 
class Post extends Model
{
use HasBinaryUuids;
 
protected $casts = [
'id' => BinaryUuidCast::class,
'user_id' => BinaryUuidCast::class,
];
 
public function user()
{
return $this->belongsTo(User::class);
}
}
 
// Usage - exactly like string UUIDs
$user = User::create(['name' => 'John', 'email' => '[email protected]']);
$post = $user->posts()->create(['title' => 'Hello', 'content' => 'World']);
echo $post->user->name; // "John"

Route Model Binding

Binary UUIDs work transparently with Laravel's route model binding:

// routes/web.php
Route::get('/user/{user}', function (User $user) {
// Accepts string UUID in URL, finds binary UUID in database
return view('user.profile', compact('user'));
});
 
// URLs work with string UUIDs:
// /user/550e8400-e29b-41d4-a716-446655440000
 
// Laravel automatically:
// 1. Takes string UUID from URL
// 2. Converts to binary for database query
// 3. Returns User model if found

Direct Binary Operations

Use Str macros for direct binary UUID operations:

use Illuminate\Support\Str;
 
// Generate binary UUIDs directly
$binaryUuid = Str::fastBinaryUuid(); // 16 bytes v4
$orderedBinary = Str::fastBinaryOrderedUuid(); // 16 bytes v7
 
// Convert between formats
$stringUuid = '550e8400-e29b-41d4-a716-446655440000';
$binary = Str::uuidToBinary($stringUuid); // Convert to 16 bytes
$backToString = Str::binaryToUuid($binary); // Convert back to string
 
echo strlen($binary); // 16
echo strlen($stringUuid); // 36
echo $backToString; // "550e8400-e29b-41d4-a716-446655440000"

Migration from String UUIDs

Convert existing string UUID tables to binary storage:

use Illuminate\Database\Migrations\Migration;
use Webpatser\LaravelUuid\BinaryUuidMigrations;
 
return new class extends Migration
{
public function up()
{
// Get database-specific conversion SQL
$conversionSql = BinaryUuidMigrations::getConversionSql('users', 'id');
 
Schema::table('users', function (Blueprint $table) {
// Add new binary column
$table->binary('id_binary', 16)->nullable();
});
 
// Convert existing data
DB::statement($conversionSql);
 
Schema::table('users', function (Blueprint $table) {
// Drop old column, rename new one
$table->dropColumn('id');
$table->renameColumn('id_binary', 'id');
});
}
 
public function down()
{
// Reverse conversion available
$reverseConversionSql = BinaryUuidMigrations::getReverseConversionSql('users', 'id');
// ... implement reverse conversion
}
};

Performance Benefits

💾 Storage Benefits

  • • 55% smaller storage footprint
  • • Faster table scans
  • • Better memory utilization
  • • Reduced backup sizes

⚡ Query Benefits

  • • Faster index lookups
  • • Better clustering (V7 UUIDs)
  • • Improved join performance
  • • Reduced network traffic

Real-World Example: User System

// Complete user system with binary UUIDs
class User extends Model
{
use HasBinaryUuids;
 
protected $casts = [
'id' => BinaryUuidCast::class,
'parent_id' => BinaryUuidCast::class,
];
 
protected $fillable = ['name', 'email'];
 
// Use V7 UUIDs for better database performance
public function newUniqueId(): string
{
return (string) Uuid::v7();
}
 
public function children()
{
return $this->hasMany(User::class, 'parent_id');
}
 
public function posts()
{
return $this->hasMany(Post::class);
}
}
 
// Migration for complete user system
Schema::create('users', function (Blueprint $table) {
BinaryUuidMigrations::addBinaryUuidPrimary($table);
BinaryUuidMigrations::addBinaryUuidColumn($table, 'parent_id', true);
$table->string('name');
$table->string('email')->unique();
$table->timestamps();
 
$table->index('parent_id');
$table->foreign('parent_id')->references('id')->on('users');
});
 
// Storage comparison for 1 million users:
// String UUIDs: ~36MB for ID column alone
// Binary UUIDs: ~16MB for ID column (55% savings)
// Total savings: ~20MB per UUID column

Testing Binary UUIDs

// Test binary UUID functionality
use Tests\TestCase;
use Illuminate\Foundation\Testing\RefreshDatabase;
 
class BinaryUuidTest extends TestCase
{
use RefreshDatabase;
 
public function test_binary_uuid_creation()
{
$user = User::create(['name' => 'Test', 'email' => '[email protected]']);
 
// UUID displays as string
$this->assertTrue(Str::isUuid($user->id));
 
// But is stored as binary in database
$binaryId = DB::table('users')->where('id', $user->getUuidAsBinary())->first();
$this->assertNotNull($binaryId);
}
 
public function test_route_model_binding()
{
$user = User::factory()->create();
 
// Test with string UUID in URL
$response = $this->get("/user/{$user->id}");
$response->assertStatus(200);
}
 
public function test_relationships()
{
$parent = User::factory()->create();
$child = User::factory()->create(['parent_id' => $parent->id]);
 
$this->assertEquals($parent->id, $child->parent_id);
$this->assertEquals($parent->name, $child->parent->name);
}
}
⚠️

Important Considerations

  • • Binary UUIDs are not human-readable in database tools
  • • Some database admin tools may not display them correctly
  • • Always use the cast classes for proper conversion
  • • Test thoroughly in your specific database environment

Next Steps