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);
Laravel, one line in config/database.php:
'redis' => [
'options' => [
'serializer' => Redis::SERIALIZER_IGBINARY,
],
],
Native PHP sessions and APCu, in php.ini:
session.serialize_handler = igbinary
apc.serializer = igbinary
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
- igbinary's own suite: https://github.com/igbinary/igbinary/blob/master/benchmark/comparisons.php
- native vs json vs igbinary vs msgpack on session-like arrays: https://gist.github.com/spajak/d07a999deb0430e2b6b7e58fc44213d1
- Drupal field reports (10-30% faster pages, ~50% less unserialize time): https://www.drupal.org/project/redis/issues/2143149
- Ilia Alshanetsky, "Igbinary, the great serializer": https://ilia.ws/blog/igbinary-the-great-serializer
Top comments (0)