Binary UUIDs: Storage, Byte Order, and Database Examples

    3 June 2024Updated 11 September 2026
    6 min read
    Technical deep-dive
    Tutorial
    uuid
    database
    performance
    architecture

    How much space does binary storage save?

    A UUID contains 128 bits: 16 bytes. Its familiar hyphenated representation contains 36 ASCII characters. Removing the hyphens leaves 32 hexadecimal characters, which still represent the same 16 bytes.

    RepresentationIdentifier payload
    Hyphenated ASCII text36 bytes
    ASCII hexadecimal without hyphens32 bytes
    Binary UUID16 bytes

    The calculation (36 - 16) / 36 gives a 55.6% payload reduction. Row headers, indexes, encoding, alignment, and other columns affect the actual storage difference. Converting an existing native UUID column to another binary type does not produce this saving.

    Smaller keys can improve cache use, but binary storage alone does not guarantee faster queries. The UUID version, index layout, workload, and database engine all matter.

    MySQL: keep the conversion convention consistent

    For MySQL 8.0 and later, store the identifier in BINARY(16) and convert at the application boundary:

    sql
    CREATE TABLE users (
      id BINARY(16) PRIMARY KEY,
      name VARCHAR(255) NOT NULL
    );
    
    INSERT INTO users (id, name)
    VALUES (UUID_TO_BIN('550e8400-e29b-41d4-a716-446655440000'), 'Alice');
    
    SELECT BIN_TO_UUID(id) AS uuid, name FROM users;

    The optional swap flag is intended for version 1 timestamp fields. If existing data uses UUID_TO_BIN(value, 1), decode it with BIN_TO_UUID(value, 1). MySQL's UUID() generates version 1 values. Use the default unswapped conversion for v4 and v7; applying the v1 rearrangement to v7 disrupts its timestamp ordering. These conventions are documented in the MySQL UUID conversion reference.

    Do not switch the flag for an existing column without migrating and verifying every value. A mismatched flag can produce a different, valid-looking identifier.

    PostgreSQL: use the native uuid type

    PostgreSQL stores UUIDs in a dedicated 128-bit type. A primary key creates a unique B-tree index by default. This example uses the built-in version 4 generator available in PostgreSQL 13 and later:

    sql
    CREATE TABLE users (
      id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
      name text NOT NULL
    );
    
    INSERT INTO users (name) VALUES ('Alice') RETURNING id;

    You do not need pgcrypto for this function on those versions. PostgreSQL 18 also provides uuidv7() when timestamp ordering suits the workload. Storage accepts UUIDs independently of the generation method. See the UUID type and UUID functions.

    MongoDB: agree on the UUID representation

    In mongosh, insert and query the same UUID value with the UUID helper:

    js
    db.users.insertOne({
      _id: UUID("550e8400-e29b-41d4-a716-446655440000"),
      name: "Alice"
    });
    
    db.users.findOne({
      _id: UUID("550e8400-e29b-41d4-a716-446655440000")
    });

    The mongosh UUID helper creates a BSON UUID value. Modern standard UUID storage uses binary subtype 4. Older subtype 3 data can follow driver-specific byte orders, so configure the driver representation explicitly when reading legacy data. See MongoDB's UUID representation guide.

    A UUID stored as a string and a UUID stored as BSON binary are different query values. Changing only the query code will not convert existing records.

    Python: verify a round trip

    python
    import uuid
    
    original = uuid.UUID("550e8400-e29b-41d4-a716-446655440000")
    payload = original.bytes
    restored = uuid.UUID(bytes=payload)
    
    assert len(payload) == 16
    assert restored == original
    assert payload.hex() == "550e8400e29b41d4a716446655440000"
    print(str(restored))

    Python's bytes and bytes_le properties use different field byte orders. Choose the representation your database and other clients expect; do not interchange them. The Python UUID reference specifies both layouts.

    Node.js: use the uuid package's ESM API

    Install the uuid package, then save this example as an .mjs file:

    js
    import { parse, stringify } from 'uuid';
    
    const original = '550e8400-e29b-41d4-a716-446655440000';
    const payload = Buffer.from(parse(original));
    const restored = stringify(payload);
    
    if (payload.length !== 16 || restored !== original) {
      throw new Error('UUID round trip failed');
    }
    console.log(restored);

    The uuid package documents parse and stringify. Current releases use ESM imports. Preserve all 16 bytes, including leading zero bytes; treating the value as a JavaScript Number loses precision.

    Measure before migrating

    Compare the same dataset and queries under each proposed schema. Record table and index sizes, insert throughput, and lookup and join latency under representative concurrency. Run enough repetitions to distinguish a consistent effect from cache warm-up or background activity.

    Before switching production reads, backfill a separate column, verify conversion round trips and foreign-key relationships, and check that every client uses the same byte order. Keep the existing representation available until rollback and mixed-client behavior have been tested.

    Use the UUID to binary converter to inspect a value, or read about UUID-based sharding for the separate question of distributing records across nodes.

    Check your implementation

    Use the UUID examples and test vectors to verify versions, v7 timestamps and canonical byte order. Download the fixed fixtures for repeatable tests; generate fresh identifiers for production records.

    Generate Your Own UUIDs

    Ready to put this knowledge into practice? Try our UUID generators:

    Summary

    Store UUIDs in 16 bytes, convert them safely in MySQL, PostgreSQL, MongoDB, Python, and Node.js, and measure the performance trade-offs.

    TLDR;

    A UUID occupies 16 bytes as binary, compared with 36 bytes as ASCII text. That is a 55.6% reduction in identifier payload, not a guaranteed reduction in database size or query time.

    Use PostgreSQL's uuid type, MySQL BINARY(16), or MongoDB's standard UUID representation. Agree on byte order and test round trips before migrating.