Back office ops · Production

Rex: Medium's Go recommendation microservice for personalized story feeds

The problem

Medium's Node.js-based recommendation system was too slow and could only rank around 150 candidate stories per feed request, with homepage feed generation taking up to seconds.

First attempt

Node.js's single-threaded event loop caused CPU starvation under the heavy I/O and computation load required to build ranked feeds; technical debt also made testing and iterating on recommendation strategies slow.

Workflow diagram · grounded in source
1
Multi-source story aggregation
trigger
“We source stories we think a user will enjoy, and we understand users may like stories for different reasons. For example, you may always read stories from authors or publications you follow”
2
Collaborative filtering
ai_action
“Finding users with a similar reading history to mine and making recommendations based on those is a technique called collaborative filtering, which Rex relies on to find high-quality stories for each user”
3
Preprocessing filters
validation
“we filter out stories we think may not be suitable for a user at a given time. Maybe we've sourced a user a story they've already read — there's no need to show them the same story twice”
4
Feature annotation
integration
“We calculate most of the features we need for ranking stories via offline Scala jobs and store them in two tables that we query at the time of feed creation. This allows us to minimize the number of I/O calls we're making when assembling…”
5
ML feed ranking
ai_action
“we pass each story and set of values to another Medium microservice that hosts our feed-ranking models. This separate microservice assigns a score to each story, where the score represents how likely we think the user is to read this par…”
6
Diversity postprocessing
validation
“we'll see the top of a user's feed dominated at times by a single author, single publication, or single topic. Because we want a user to see a more diverse set of authors, publications, and topics represented, we added a postprocessor”
7
Redis cache storage
output
“we store the feed in Redis, an in-memory data store, for future use”
8
Cached feed validation
validation
“some of the stories in the cached ranked-feed list may no longer be suitable for candidates”
Reported outcome

Rex can rank 10x more candidate stories per feed request, and feed creation now takes less than one second for 95% of requests.

Reported metrics
Candidate stories ranked per feed10x
Feed creation timeless than one second for 95% of requests
Previous candidate stories ranked150
Previous homepage feed creation timeup to seconds
Reported stack
GoRedisScalaNode.jscollaborative filtering
Source
https://medium.engineering/rex-mediums-go-recommendation-microservice-e077bc9582a
Read source ↗

Frequently asked questions

What did this team achieve with this AI workflow?

Rex can rank 10x more candidate stories per feed request, and feed creation now takes less than one second for 95% of requests.

What tools did this team use?

Go, Redis, Scala, Node.js, collaborative filtering.

What results were reported?

Candidate stories ranked per feed: 10x; Feed creation time: less than one second for 95% of requests; Previous candidate stories ranked: 150; Previous homepage feed creation time: up to seconds (source-reported, not independently verified).

What failed first in this deployment?

Node.js's single-threaded event loop caused CPU starvation under the heavy I/O and computation load required to build ranked feeds; technical debt also made testing and iterating on recommendation strategies slow.

How is this back office ops AI workflow structured?

Multi-source story aggregation → Collaborative filtering → Preprocessing filters → Feature annotation → ML feed ranking → Diversity postprocessing → Redis cache storage → Cached feed validation.