Social Graph (LinkedIn / Facebook TAO) — 1B users, 200B+ edges. Sharded MySQL adjacency (objects+assoc tables, sharded by id1) behind a stateless TAO-style cache tier (Memcached, 99% hit). Two ADRs: graph DB vs sharded SQL adjacency, and cache strategy for hot edges (celebrities). 5 scenarios: add friend (bidirectional write + invalidate), friends-of-friends 2-hop (scatter-gather), mutual friends (sorted-list intersection), PYMK (2-hop + ML rerank via PyTorch BigGraph), hot celebrity read (pre-sharded follower list with leases).
Social Graph является фундаментом социальных продуктов: друзья, подписки, блокировки, mutual friends, people you may know, privacy filters, feed eligibility and messaging permissions. На малом масштабе это выглядит как таблица friendships, но на уровне Facebook, LinkedIn или Instagram graph queries возникают почти на каждой странице и должны отвечать за миллисекунды. Один feed view может сделать десятки проверок: является ли автор другом, подписан ли viewer, есть ли block, какие mutual friends показать, можно ли отправить сообщение.
Кейс важен потому, что он разрушает простую идею "возьмем graph database". Native graph DB хороши для глубоких traversals and pathfinding, но основной production workload социальной сети часто состоит из 1-hop и 2-hop запросов с экстремальным QPS. Для такого профиля sharded adjacency storage плюс cache tier может быть лучше, чем универсальная graph database.
Этот дизайн тренирует ::concept{slug="neo4j-graph-db"}, ::concept{slug="sharding"}, ::concept{slug="caching-strategies"}, ::concept{slug="social-graph"}, ::concept{slug="multi-region"}, ::concept{slug="consistency"} и ::concept{slug="recommendation-system"}. Он также связан с news feed: feed зависит от graph для fanout и privacy, а graph зависит от cache and storage discipline.
Мысленная модель TAO-style graph: есть objects and associations. Object представляет user, post, photo, page или comment. Association представляет directed edge: friend, follow, block, like, tag, member_of. Даже bidirectional friendship хранится как две directed associations: X friend Y and Y friend X. Большинство запросов формулируется как assoc_get(id1, type, limit), assoc_exists(id1, type, id2), assoc_count(id1, type) или 2-hop expansion.
Storage шардируется по id1, потому что самые частые queries спрашивают outgoing edges конкретного пользователя. Для friends_of(user) это один shard или небольшой набор partition. Для mutual friends нужно получить два списка и пересечь их в памяти. Для people you may know нужно взять friends user, затем friends каждого friend, посчитать overlap, убрать self/already friends/blocked и отдать candidates в ranking.
Cache tier является не optimization, а частью architecture. При сотнях миллионов или миллиардах users storage не выдержит каждый page load. Stateless TAO servers обращаются к Memcache/Redis-like assoc cache, а при miss читают replica storage, заполняют cache и возвращают результат. Writes идут в master shard и инвалидируют affected keys.
Диаграмма показывает client, load balancer, GraphQL/API Gateway, TAO tier, Memcache assoc cache, MySQL master shards, regional replicas, PYMK service and graph embeddings service. Важная идея: application не ходит напрямую в database за graph edges. Оно обращается к graph API, который знает cache keys, shard routing, privacy rules, leases and invalidation.
Master and replicas на диаграмме показывают read/write split. Writes friend accept, follow, unfollow and block идут в master. Reads обычно обслуживаются из cache или local replica. Cross-region replication asynchronous, поэтому система должна понимать eventual consistency. Например, только что принятый friend request может мгновенно показываться в регионе записи, но появиться в другом регионе через десятки или сотни миллисекунд.
PYMK и graph embeddings вынесены отдельно, потому что suggestions не должны выполняться как бесконечный online traversal на пользовательском запросе. Online service может делать bounded 2-hop expansion, но heavy ranking, embeddings and offline graph processing лучше готовить заранее.
Accept friend request показывает bidirectional write. Система должна атомарно добавить две ассоциации или иметь компенсирующий механизм, иначе X будет видеть Y другом, а Y не будет видеть X. После записи нужно инвалидировать friends:X, friends:Y, exists:X:Y, exists:Y:X and related counts. Если cache не инвалидировать, пользователи увидят старое состояние.
Friends-of-friends 2-hop показывает bounded scatter-gather. Сначала получаем 200 friends пользователя, затем параллельно читаем friends каждого friend, получаем десятки тысяч candidates, dedupe, фильтруем already-friends and blocked, возвращаем top N. Этот сценарий объясняет, почему 2-hop еще можно держать online, а 5-hop traversal уже лучше переносить в offline analytics.
Mutual friends показывает set intersection. Если списки отсортированы по user id или хранятся в compact representation, пересечение можно сделать быстро в памяти. Для очень популярных пользователей список может быть capped или paged, поэтому ответ "37 mutual" иногда является approximate или computed from top-N.
People You May Know показывает комбинацию graph и ML. 2-hop дает candidates, но ranking решает, кого показать: mutual count, workplace, school, city, embedding similarity, recent interactions, negative signals. Без ranker suggestions становятся шумными.
Hot celebrity read показывает проблему hot keys. Один пользователь с 100M followers не может храниться в одном cache key и обслуживаться одним Memcache shard. Нужны sharded follower lists, pinned hot keys, leases, request collapsing and pagination.
Block scenario учит privacy. Block edge должен переопределять friendship/follow and feed visibility. Privacy filter нельзя забывать в suggestions, mutual friends, follower lists and search.
Graph DB удобна для expressive traversals, но sharded SQL adjacency лучше для экстремального 1-hop/2-hop workload. Цена TAO-style подхода: больше application logic, explicit cache invalidation, shard routing and query limitations. Зато point lookups and adjacency lists становятся предсказуемыми.
Шардирование по id1 ускоряет outgoing queries, но incoming queries типа followers_of(user) требуют обратной association, отдельного index или второй таблицы. Поэтому directed relationships часто пишутся в обе стороны там, где это нужно продукту.
Cache hit ratio 99 процентов звучит отлично, но оставшийся 1 процент при 100M QPS все еще огромен. Storage должен выдерживать misses, а cache должен использовать leases and request coalescing, чтобы thundering herd не пробил database.
Strong consistency для global graph дорогая. Friend accept можно сделать strongly consistent в home region, но cross-region reads часто eventual. Для security-sensitive edges вроде block можно использовать более строгую propagation policy или локальный negative cache, потому что stale block is worse than stale mutual friend count.
Precomputing suggestions снижает online latency, но может устаревать. Online 2-hop дает freshness, но стоит дороже. Практичный вариант: offline candidates plus online filtering and reranking.
Facebook TAO является известным примером graph serving layer поверх MySQL associations and cache. LinkedIn, Instagram and large messaging platforms используют похожие идеи: graph API перед storage, heavy cache, sharded edges, async replication and offline recommendation pipelines. Neo4j, JanusGraph and TigerGraph применимы в других режимах: fraud detection, knowledge graph, deep traversal, analytics, но не всегда подходят для primary social graph serving at massive QPS.
В реальных системах edge types быстро множатся: friend, follow, close_friend, block, mute, subscribe, member, admin, like, tag. Поэтому schema должна поддерживать typed associations, timestamps, version, privacy metadata and pagination cursor. Для audit and abuse часто нужен history: кто кого заблокировал, когда relationship был удален, какой action triggered recommendation.
Выбирать graph database только потому, что предметная область называется graph. Нужно смотреть на workload: глубина traversal, QPS, latency, write rate, consistency and operational maturity.
Хранить friendship как одну undirected строку без ясной модели запросов. Для friends_of(X) и is_friend(X,Y) directed duplicated edges часто проще and faster.
Не учитывать block/privacy в каждом read path. Suggestions, mutual friends and feed eligibility должны применять privacy filters, иначе возникают серьезные product and safety bugs.
Делать unbounded 2-hop traversal. У пользователя с тысячами connections и celebrities во втором хопе candidate set может взорваться. Нужны caps, sampling, pagination and ranking budget.
Инвалидировать только один cache key при write. Friend accept меняет оба списка, existence checks, counts, suggestions and feed eligibility.
Считать averages достаточными. Средний пользователь имеет сотни friends, но celebrities and organizations создают hot partitions and hot cache keys.
TAO-style architecture не нужна маленькому продукту. Если у вас тысячи или миллионы пользователей и умеренный QPS, Postgres table with indexes, materialized counters and Redis cache может быть достаточной. Сложный graph serving layer принесет больше операционной нагрузки, чем пользы.
Этот подход не подходит для deep graph analytics, shortest path over many hops, fraud ring detection or knowledge graph exploration. Там лучше batch processing, graph processing engines или specialized graph database.
Он также не является recommendation system сам по себе. Social graph дает candidates and features, но ranking, exploration, diversity and feedback loops требуют отдельного ML/recommendation layer.
Если relationship model требует ACID transactions across many unrelated users and edge types, sharding by id1 может усложнить consistency. Нужно либо менять product semantics, либо использовать workflow/saga, либо выбирать storage под transactional constraints.
Начните с ::concept{slug="sharding"}: поймите, почему ключ id1 делает 1-hop запросы быстрыми и какие запросы становятся сложными. Затем изучите ::concept{slug="caching-strategies"}, leases and request coalescing. Для сравнения посмотрите ::concept{slug="neo4j-graph-db"}: важно понимать, где native graph DB выигрывает, а где проигрывает. После этого переходите к ::concept{slug="recommendation-system"} и кейсу News Feed, потому что graph rarely exists alone; он кормит feed, search, messaging permissions and suggestions.