Build Pinterest's Image Deduplication System
data-engineering scalability cost-optimization
System Design Deep Dive
Pinterest Image Deduplication
300 billion pins, countless pixel-shifted twins, one canonical copy stored per image.
A forensic fingerprint lab never compares a fresh print against every record in a national database one pixel at a time. It first reduces the print to a coarse classification, loops, whorls, arches, a handful of minutiae points, and only then runs a detailed comparison against the short list of records that share that coarse signature. Cheap coarse filter first, expensive exact comparison second, is the only reason an AFIS system stays fast as its database grows into the hundreds of millions of records.
Pinterest’s image corpus needs the exact same funnel, except the fingerprints are photographs and there are 300 billion of them. A single popular product photo, recipe image, or wedding dress gets uploaded to Pinterest thousands of times over: saved from a retailer’s site, re-cropped by a blogger, recompressed by a third app, watermarked by a stock photo site, resized by a mobile client. Every one of those is visually the same image to a human looking at it, and every one of those produces a completely different sequence of bytes on disk.
That last part is what breaks the naive approach. Hashing the raw file bytes (MD5, SHA-256) and looking for an exact match only catches images that are byte-for-byte identical, which in practice is a small minority of real-world duplicates. Change one pixel, resave as a lower JPEG quality, or crop ten pixels off an edge, and the exact hash is unrecognizably different even though nothing meaningful about the image changed. At 300 billion pins, exact-hash dedup catches almost none of the actual redundancy sitting in the corpus, which means Pinterest is paying to store, replicate, and serve the same visual content dozens of times over under dozens of different pin IDs.
What we need instead is a notion of “close enough” similarity, computed cheaply enough to run on every upload, and searchable across hundreds of billions of existing images without a linear scan. The forces in tension are speed (the check has to add almost nothing to the pin creation path), correctness (never merge two genuinely different images just because their fingerprints happen to collide), and cost (the index built to find duplicates cannot itself become bigger than the storage it saves). We need to solve for sublinear candidate lookup across a hash space with hundreds of billions of entries, a tunable definition of “the same image,” and a safe, reversible way to reclaim storage from confirmed duplicates, simultaneously.
Requirements and Constraints
Functional Requirements
- Compute a perceptual fingerprint for every newly uploaded pin image before it is durably stored
- Check the new fingerprint against the existing corpus and, if a near-duplicate is found above the configured similarity threshold, link the new pin to the existing canonical image instead of storing a new copy
- If no match is found, store the image as a new canonical image and index its fingerprint for future lookups
- Support configurable similarity thresholds per use case: strict for automatic merge-on-ingest, looser for surfacing “possible duplicate” candidates to a moderation queue
- Run a background storage reclamation pipeline that finds duplicate images stored before this system existed, or missed at ingest time, and merges them to free blob storage
- Guarantee that hash collisions never cause two genuinely different images to be silently merged
- Expose a query API so downstream systems (visual search, copyright tooling) can ask “does this image already exist in the corpus” without triggering ingest side effects
Non-Functional Requirements
- Ingest latency: the dedup check adds p99 under 50ms to the pin creation path
- Scale: roughly 190 billion canonical images already indexed (of 300 billion total pins, since many pins already point at a shared image), 150 million new pin uploads/day, averaging ~1,740 uploads/sec with peaks around 8,000/sec
- Index availability: 99.95% for the fingerprint index; the ingest path fails open (stores as new) rather than blocking a pin creation when the index is unavailable
- Precision: false-merge rate (two genuinely different images incorrectly linked as duplicates) under 1 in 10 million comparisons
- Reclamation throughput: able to re-scan the full existing corpus for missed duplicates within a rolling 30-day window
- Durability: the reclamation pipeline must never delete a blob that still has a live pin reference, under any failure or retry condition
Constraints and Assumptions
- Out of scope: the visual search and “shop the look” recommendation products built on top of image similarity - we build the dedup detection and storage reclamation layer they could sit on, not those products themselves
- We assume images arriving at this system have already passed content moderation and virus scanning
- We fail open on the ingest path: a dedup check that cannot complete within budget, or an unavailable index, results in the image being stored as new, never in a blocked or dropped upload
- We do not attempt to deduplicate video or animated GIF content in this design, only static images
- We target a single authoritative fingerprint index per region; cross-region replication is discussed but region failover is not the primary design target
High-Level Architecture
The system has five major components: the Ingest Gateway (orchestrates the synchronous dedup check), the Perceptual Hash Service (turns pixels into a compact fingerprint), the LSH Index Cluster (sharded approximate-match lookup over the fingerprint space), the Similarity Verifier (turns candidates into a yes/no duplicate decision), and the Storage Reclamation Pipeline (an offline batch process that finds and merges duplicates the ingest path missed).
A pin upload arrives at the Ingest Gateway carrying the raw image bytes. The Gateway calls the Perceptual Hash Service, which decodes the image and computes two complementary fingerprints, a dHash (difference hash, fast and robust to recompression) and a pHash (DCT-based, more robust to color and contrast shifts), each a 64-bit integer. The Gateway then queries the LSH Index Cluster with those fingerprints, which returns a small candidate set of canonical images that are plausibly similar, without ever scanning the full corpus. The Similarity Verifier computes the exact Hamming distance between the new fingerprint and every candidate, cross-checks agreement between dHash and pHash, and decides whether this is truly a duplicate. If it is, the new pin links to the existing canonical image and no new blob is stored. If it is not, the image is stored as a new canonical image and its fingerprint is written into the LSH Index for future lookups.
The Storage Reclamation Pipeline runs entirely off the hot path. It periodically re-scans the corpus (or a targeted slice of it, such as images uploaded before this system existed, or images affected by a threshold change) looking for duplicate clusters the ingest-time check missed, merges them, and garbage collects blobs whose reference count drops to zero. Nothing in that pipeline is allowed to touch the ingest latency budget.
The system is split into a synchronous path that only ever does an approximate lookup plus a cheap verification, and an asynchronous path that does the expensive, thorough re-scanning. Trying to make the ingest path fully thorough (scanning every possible duplicate, re-checking historical images) would blow the 50ms budget instantly. Trying to make the reclamation pipeline lazy would leave decades of redundant storage sitting uncollected. Neither path can do the other's job.
Component Deep Dives
The Ingest Gateway and Perceptual Hash Service
This component’s job is to turn raw image bytes into a compact, comparable fingerprint fast enough that computing it never becomes the bottleneck in the pin creation flow.
A smart engineer’s first instinct is to use a cryptographic hash (SHA-256) because it is fast, well understood, and collision-resistant. The problem is that cryptographic hashes are designed to change completely for a single flipped bit, which is exactly the wrong property here. We want the opposite: a hash that changes gracefully, staying close in hash space when the image changes only a little, and moving far away when the image changes a lot. That property is what perceptual hashing is built for.
// Ingest Gateway -> Perceptual Hash Service RPC contract
// Demonstrates: request/response shape for the synchronous ingest-time hash call
syntax = "proto3";
package pinterest.dedup.v1;
message HashRequest {
string upload_id = 1;
bytes image_bytes = 2;
string content_type = 3; // image/jpeg, image/png, image/webp
}
message HashResponse {
fixed64 dhash = 1; // 64-bit difference hash
fixed64 phash = 2; // 64-bit DCT perceptual hash
uint32 width = 3;
uint32 height = 4;
bool low_entropy = 5; // flagged for near-blank / low-detail images
}
service PerceptualHashService {
rpc ComputeHash(HashRequest) returns (HashResponse);
}Premium Content
Unlock the full article along with everything else in the archive — all in one place.