User and Item Models

How user and item features are computed offline and combined online to generate recommendations.

Recommender systems model both users and items as collections of features. These features are computed at different times (offline vs online) and combined at serving time to predict relevance.

User Model

The user model captures who the user is and what they’ve done.

Static Features (Slow-Changing)

Computed offline in batch jobs, updated daily or weekly.

FeatureExampleUpdate Frequency
DemographicsAge, gender, locationRarely changes
Account ageDays since signupDaily
Lifetime statsTotal purchases, avg spendDaily
Category preferences”Likes electronics 70%, books 20%“Weekly
Long-term embeddingDense vector from historical behaviourWeekly
Segment membership”Power user”, “Bargain hunter”Weekly
# Offline batch job (runs nightly)
user_features = {
    "user_id": user.id,
    "account_age_days": (today - user.signup_date).days,
    "lifetime_purchases": count_purchases(user),
    "avg_order_value": mean(user.order_values),
    "top_categories": get_top_categories(user, k=5),
    "user_embedding": embedding_model.encode(user.history),
}
feature_store.write(user.id, user_features)

Dynamic Features (Fast-Changing)

Computed online in real-time or near real-time.

FeatureExampleUpdate Frequency
Session itemsItems viewed this sessionReal-time
Recent clicksLast 10 items clickedReal-time
Current contextTime of day, device, locationPer request
Short-term intent”Browsing winter coats”Per session
Cart contentsItems currently in cartReal-time
# Online at request time
session_features = {
    "session_items": get_session_views(session_id),
    "recent_clicks": get_recent_clicks(user_id, last_n=10),
    "current_hour": datetime.now().hour,
    "device": request.device_type,
    "location": request.geo,
}

Item Model

The item model captures what the item is and how it’s performed.

Static Features (Slow-Changing)

Computed offline, often at item ingestion time.

FeatureExampleUpdate Frequency
Content attributesTitle, description, categoryAt creation
Item embeddingDense vector from content/interactionsWeekly
Price tier”Budget”, “Premium”On price change
Image featuresExtracted from product photosAt creation
Text embeddingsFrom title/descriptionAt creation
# Offline batch job
item_features = {
    "item_id": item.id,
    "category": item.category,
    "brand": item.brand,
    "price": item.price,
    "price_tier": categorize_price(item.price, item.category),
    "content_embedding": text_model.encode(item.title + item.description),
    "image_embedding": image_model.encode(item.image),
}
feature_store.write(item.id, item_features)

Aggregate Features (Updated Periodically)

Computed from user interactions, updated in batch.

FeatureExampleUpdate Frequency
PopularityView count, purchase countHourly/Daily
Avg ratingMean of user ratingsDaily
CTRHistorical click-through rateDaily
Conversion ratePurchases / viewsDaily
Co-purchase stats”Often bought with X”Weekly
# Offline aggregation job
item_stats = {
    "view_count_7d": count_views(item, days=7),
    "purchase_count_7d": count_purchases(item, days=7),
    "avg_rating": mean(item.ratings),
    "ctr": item.clicks / item.impressions,
    "conversion_rate": item.purchases / item.views,
}
feature_store.update(item.id, item_stats)

Real-Time Features

Updated continuously via streaming.

FeatureExampleUpdate Frequency
Stock statusIn stock, low stock, outReal-time
Current priceAfter discountsReal-time
Trending scoreRecent velocity of viewsMinutes

Offline Computation

Most feature computation happens in batch jobs:

┌─────────────────────────────────────────────────────┐
│                    OFFLINE                          │
├─────────────────────────────────────────────────────┤
│                                                     │
│  Data Warehouse                                     │
│       ↓                                             │
│  Batch Processing (Spark/Airflow)                   │
│       ↓                                             │
│  ┌─────────────┐    ┌─────────────┐                │
│  │ User Features│    │Item Features│                │
│  └─────────────┘    └─────────────┘                │
│       ↓                   ↓                         │
│  ┌─────────────────────────────────┐               │
│  │         Feature Store           │               │
│  │   (Redis, DynamoDB, Feast)      │               │
│  └─────────────────────────────────┘               │
│                                                     │
│  Embedding Model Training                           │
│       ↓                                             │
│  ┌─────────────────────────────────┐               │
│  │    ANN Index (FAISS, ScaNN)     │               │
│  └─────────────────────────────────┘               │
│                                                     │
└─────────────────────────────────────────────────────┘

What happens offline:

  1. Train embedding models on historical data
  2. Generate user embeddings from their interaction history
  3. Generate item embeddings from content and interactions
  4. Compute aggregate statistics (popularity, CTR, ratings)
  5. Build ANN index from item embeddings
  6. Push all features to the Feature Store

Online Interaction

When a request arrives, features are assembled and combined:

┌─────────────────────────────────────────────────────┐
│                    ONLINE                           │
├─────────────────────────────────────────────────────┤
│                                                     │
│  User Request                                       │
│       ↓                                             │
│  ┌─────────────────────────────────┐               │
│  │   Fetch from Feature Store      │               │
│  │   - User static features        │               │
│  │   - Item features (for candidates) │            │
│  └─────────────────────────────────┘               │
│       ↓                                             │
│  ┌─────────────────────────────────┐               │
│  │   Compute Real-Time Features    │               │
│  │   - Session context             │               │
│  │   - Current time/location       │               │
│  └─────────────────────────────────┘               │
│       ↓                                             │
│  ┌─────────────────────────────────┐               │
│  │      Combine All Features       │               │
│  │   [user] + [item] + [context]   │               │
│  └─────────────────────────────────┘               │
│       ↓                                             │
│  ┌─────────────────────────────────┐               │
│  │        Scoring Model            │               │
│  │     predict(combined_features)  │               │
│  └─────────────────────────────────┘               │
│       ↓                                             │
│  Ranked Results                                     │
│                                                     │
└─────────────────────────────────────────────────────┘

What happens online:

  1. Receive user request with user_id and context
  2. Fetch pre-computed user features from Feature Store
  3. Run retrieval to get candidate items
  4. Fetch pre-computed item features for each candidate
  5. Compute real-time features (session, context)
  6. Combine user + item + context features into a single vector
  7. Score each candidate with the ranking model
  8. Apply ordering logic and return results

Feature Combination Example

def score_candidate(user_id, item_id, context):
    # Fetch offline features
    user_features = feature_store.get_user(user_id)
    item_features = feature_store.get_item(item_id)

    # Compute online features
    session_features = compute_session_features(context)

    # Combine into single feature vector
    combined = {
        # User features
        "user_embedding": user_features["embedding"],
        "user_avg_spend": user_features["avg_order_value"],
        "user_category_affinity": user_features["top_categories"],

        # Item features
        "item_embedding": item_features["embedding"],
        "item_price": item_features["price"],
        "item_popularity": item_features["view_count_7d"],
        "item_ctr": item_features["ctr"],

        # Context features
        "hour_of_day": session_features["current_hour"],
        "device": session_features["device"],
        "items_viewed_this_session": len(session_features["session_items"]),

        # Interaction features
        "price_vs_user_avg": item_features["price"] / user_features["avg_order_value"],
        "user_item_category_match": category_match_score(user_features, item_features),
    }

    return ranking_model.predict(combined)

Two-Tower Architecture

A common pattern separates user and item encoding:

User Features ──→ User Tower ──→ User Embedding ─┐
                                                  ├──→ Dot Product ──→ Score
Item Features ──→ Item Tower ──→ Item Embedding ─┘

Why this helps:

  • Item embeddings can be pre-computed offline
  • Only user embedding needs computation at request time
  • Enables fast ANN lookup for retrieval

See Also

-
-