DEV Community

Cover image for igbinary: half the Redis memory for one line of config
kevariable
kevariable

Posted on

igbinary: half the Redis memory for one line of config

Everything PHP stores in Redis gets serialized first: sessions, cache entries, queued jobs. PHP's native serialize() produces a verbose text format, and most of us never think about it. igbinary is a PHP extension that swaps in a compact binary format instead, and you enable it with one config line, your code does not change.

I benchmarked it properly on a Laravel app (PHP 8.4, phpredis 6.3, Redis 7), and one of the results confused me enough that figuring it out taught me more than the benchmark itself.

Where it plugs in

phpredis:

$redis->setOption(Redis::OPT_SERIALIZER, Redis::SERIALIZER_IGBINARY);
Enter fullscreen mode Exit fullscreen mode

Laravel, one line in config/database.php:

'redis' => [
    'options' => [
        'serializer' => Redis::SERIALIZER_IGBINARY,
    ],
],
Enter fullscreen mode Exit fullscreen mode

Native PHP sessions and APCu, in php.ini:

session.serialize_handler = igbinary
apc.serializer = igbinary
Enter fullscreen mode Exit fullscreen mode

The benchmark

Four payloads: a realistic session array (~2.7KB: user profile, cart, tokens, flash), a single product row, a 100-product list (~35KB), and a 1000-element nested array (~125KB). Each serialized with native serialize(), igbinary_serialize(), and json_encode() for reference, then the session payload pushed through a real Redis round-trip both ways.

Size first:

payload serialize igbinary json
session ~2.7KB 2,774 B 1,462 B (-47%) 1,901 B
100 products ~35KB 35,508 B 16,112 B (-55%) 27,971 B
nested ~125KB 125,184 B 39,557 B (-68%) 83,176 B

Unserialize throughput (ops/sec):

payload serialize igbinary
session ~2.7KB 287k 257k (slightly slower!)
100 products 33.3k 43.0k (+29%)
nested 3.2k 6.2k (+94%)

And the part that actually matters, the same session payload through Redis via phpredis:

PHP serializer igbinary
Redis MEMORY USAGE per session 3,128 B 1,592 B (-49%)
1,000 sessions 3,047 KiB 1,547 KiB
SET ops/s 15,145 15,864 (+5%)
GET ops/s 15,895 16,771 (+6%)

JSON, for the record, was bigger than igbinary and slower to decode in every single test.

The puzzle: why is the small payload slower but the big ones faster?

This is the result that confused me. igbinary unserialize was 10% slower on the session but 29% faster on the product list and 94% faster on the nested array. Same extension, opposite outcomes. Why?

Two forces are competing:

igbinary pays a fixed cost per call. It sets up a string table (for its deduplication) on every serialize and unserialize. On a tiny 2.7KB payload, that fixed cost is a meaningful share of the total time, and PHP's native format, a dumb linear text scan, is genuinely fast at small sizes.

igbinary wins on repetition. The 100-product list repeats the same eight keys (id, name, sku, price...) a hundred times. igbinary interns each string once and back-references it, so decoding allocates those keys once. Native unserialize re-parses and re-allocates every repeated key, all 800 of them. Plus igbinary walks half the bytes and reads binary type tags instead of parsing ASCII numbers. The nested array amplifies the same effect.

The session payload has almost no repetition relative to its size (tokens, emails, random strings are unique), so it gets the fixed cost without the dedup payoff.

Rule of thumb that falls out: CPU cost is a wash on small unique data, and wins big on anything with repeated structure, which is most real application data.

The microbench is not the pipeline

Here is the thing though: even for the "slower" session payload, the end-to-end Redis round-trip was FASTER with igbinary. +5% on SET, +6% on GET.

Because the microbench measures CPU only. The pipeline pays for bytes: bytes serialized, bytes on the wire, bytes Redis parses and stores, bytes coming back. Halving the payload beats a ~100 nanosecond CPU difference every time. And per real request you unserialize a session once, 0.004ms, invisible next to a multi-millisecond request, while the 49% Redis memory saving is permanent.

The usage pattern that matters is "serialize rarely, unserialize often", and reads are where igbinary is strongest.

Tradeoffs

  • Binary and PHP-only: not readable in redis-cli, and nothing except PHP with the extension can decode it. Use JSON where other languages consume the data
  • Every reader needs ext-igbinary: all app servers, workers, cron boxes
  • Switching serializers makes existing entries unreadable: flush the cache or session store when migrating
  • Object semantics are preserved (__sleep, __wakeup, references), so behaviour is drop-in
  • Pairs well with phpredis compression (Redis::OPT_COMPRESSION, LZ4/ZSTD) for large values

Takeaway

One line of config. Sessions take half the Redis memory, payloads shrink 47-68%, reads get faster on real data shapes, and the round-trip gains a few percent on top. The only real costs are operational: the extension everywhere, and a flush when you switch.

Size is what scales. CPU differences at these speeds are noise; half the RAM and half the bytes on the wire are not.

Other people's benchmarks

Top comments (0)