Social Graph Service

GolangPostgresRedisAWS Lambda
Social Graph Service illustration

Overview

Social Graph Service manages the relationships between players: friends, invitations, blocks and the queries that sit on top of them. Social features look simple from the outside and are deceptively awkward underneath, because almost every question a game asks is a graph traversal under a latency budget. "Which of my friends are online?" touches every edge a player has, and it gets asked on every screen.

The service is built in Golang on PostgreSQL (Neon) with Redis caching the queries that run constantly — friend lists, online status, pending invitations. Relationships are bidirectional and stored once rather than duplicated per direction, which avoids the classic bug where two players disagree about whether they are friends because one side of the write succeeded and the other did not.

It runs as a monolith with AWS Lambda handling event-driven work, so invitation notifications and status fan-out scale independently of the request path. Blocking is treated as a first-class relationship rather than an afterthought, which means a blocked player disappears from suggestions, search and chat consistently instead of in whichever places happened to be wired up.

Built for Impact

Advanced features that power your digital infrastructure.

Bidirectional Friendships

Relationships are stored once rather than duplicated per direction, so the two sides can never disagree about whether a friendship exists.

Invitations and Requests

Pending, accepted, declined and expired states handled explicitly, with the edge cases — mutual requests, re-requests after a decline — resolved rather than left to the client.

Blocking as a First-Class Relationship

A blocked player is removed from suggestions, search, chat and presence consistently, instead of only in the places that remembered to check.

Presence and Online Status

Cached friend presence so the friends list opens instantly rather than fanning out a status query across every edge on every render.

Friend Suggestions

Mutual-connection and shared-activity suggestions computed from the graph, giving players a reason to expand their network without manual search.

Event-Driven Notifications

Invitations and status changes fan out through Lambda, so notification volume scales independently of the request path serving the app.

Specs

Architecture

Monolith

Language

Golang

Database

Postgres (Neon)

Cache

Redis

Deployment

AWS Lambda / Docker

Use Case

Social Connections

How It Works

1

Send an invitation

A player invites another by id or handle. The service records a pending edge and handles the awkward cases explicitly: a mutual invitation resolves straight to a friendship rather than leaving two pending requests, and an invitation to someone who has blocked you fails quietly rather than revealing the block.

2

Accept and materialise

On acceptance the pending edge becomes a friendship, stored once and readable from either side. Because there is a single row rather than one per direction, there is no window in which the two players disagree about their relationship.

3

Query the graph

Friend lists, mutual connections and presence are served from Redis where possible, falling back to Postgres on a miss. This is the path that runs most often — the friends list is on almost every screen — so it is the one that gets cached hardest.

4

Fan out events

Invitations, acceptances and presence changes are emitted as events handled by Lambda. Notification work therefore scales on its own rather than competing with the requests players are waiting on.

Common Use Cases

Multiplayer social layers

Friends, parties and invitations for a game where playing with people you know is the retention mechanic. Presence caching is what keeps the friends list from becoming the slowest screen in the product.

Community platforms

Following, connections and blocking for a social product. The graph primitives are the same whether the edges are called friends or follows; only the directionality rules differ.

Cross-game friend lists

A shared social graph across a publisher's catalogue, so a friendship made in one title carries into the next. Far easier to establish once than to reconcile across live games later.

What to Decide Early

Decide whether relationships are symmetric

Friends are mutual and require acceptance; follows are one-directional and do not. The two produce different privacy expectations, different notification volumes and different graph shapes. Picking one deliberately matters, because retrofitting the other later means reinterpreting every existing edge and explaining the change to users who understood the original model.

Set limits before someone finds them

Friend list caps, invitation rate limits and request expiry all exist to prevent abuse rather than to inconvenience players. Without them, invitation spam becomes a support problem and unbounded lists become a performance one. Choosing the numbers early is easy; lowering a limit after players have already exceeded it is not.

Work out what blocking actually hides

Blocking is simple to store and subtle to apply. Whether a blocked player disappears from leaderboards, whether they can still see public profiles, and whether the block is visible to them are all product decisions with real consequences. Treating blocking as a stored relationship rather than a UI filter is what makes applying it consistently possible.

Frequently Asked Questions

Fast enough to run on every screen, because they are served from Redis rather than traversing Postgres each time. The friends list is one of the most frequently rendered pieces of a multiplayer UI, so it gets cached accordingly.
The mutual invitation resolves directly into a friendship rather than leaving both sides with a pending request to accept. It is a small case that looks broken to players when it is not handled.
Blocking is a stored relationship, not a client-side filter, so a blocked player is excluded from suggestions, search, presence and chat consistently. Invitations to someone who has blocked you fail without revealing that a block exists.
Yes. Queries are indexed and cached rather than recomputed, so a player with a very large network is a memory consideration rather than a latency cliff.
It emits events for invitations, acceptances and presence changes through Lambda. Delivery to push, email or in-app channels is wired to your own notification system, since routing rules tend to be product-specific.
Contacts, platform friend lists and social graphs can all be used as suggestion sources, matched against existing players rather than imported wholesale. The distinction matters: suggesting someone who is already playing converts far better than inviting someone who is not, and it avoids the privacy problems that come with storing a contact list you were only meant to read once.

Still have questions? Talk to our team

Scale Your Infrastructure

Ready to handle millions of users? Our backend experts are here to build the high-concurrency, secure architecture your mission-critical project demands.

Request Custom Solution