Pinterest 数据工程师面试实录 2026:Feed 推荐数据管道 + Spark + 视觉搜索
pinterestdata-engineerinterviewsqlpythonsparksystem-designrecommendation

Pinterest 数据工程师面试实录 2026:Feed 推荐数据管道 + Spark + 视觉搜索

Pinterest Data Engineer 面试真实经历:SQL 窗口函数、Spark 大数据处理、实时 Pin 推荐数据管道、System Design 完整复盘。第一人称真实面经,含面试官对话与解题思路。

Sam · · 15 分钟阅读

公司:Pinterest 岗位:Data Engineer 面试形式:Phone Screen + Virtual Onsite (4 轮) 结果:Pass → Offer


2026 年 8 月,我参加了 Pinterest 的 Data Engineer 面试。Pinterest 的面试风格非常注重 数据驱动产品优化——从 Feed 推荐算法的数据管道到视觉搜索系统的特征工程,每个环节都围绕着如何提升用户发现感兴趣内容的体验展开。

Pinterest 的数据团队规模很大,数据平台基于 Spark + BigQuery + Kafka,处理 PB 级的 Pin 浏览、点击和收藏数据。面试官非常关注数据管道的可扩展性和数据质量保障。


Phone Screen:SQL + 数据建模

电话面由一位 Senior DE 进行,45 分钟。

题目:Pin 内容分析

给定以下表结构:

  • pin_impressions: impression_id, user_id, pin_id, session_id, impression_time, position
  • pin_clicks: click_id, impression_id, user_id, pin_id, click_time
  • pin_details: pin_id, creator_id, board_id, category, image_url, created_at

请完成以下查询:

  1. 计算每个类别的 CTR(点击率)
  2. 分析 Feed 位置对点击率的影响
  3. 计算用户的 Pin 收藏留存率(看到后 7 天内收藏的比例)

我的解答:

-- 1. 每个类别的 CTR
WITH category_impressions AS (
    SELECT
        d.category,
        COUNT(DISTINCT i.impression_id) AS total_impressions,
        COUNT(DISTINCT c.click_id) AS total_clicks
    FROM pin_details d
    LEFT JOIN pin_impressions i ON d.pin_id = i.pin_id
    LEFT JOIN pin_clicks c ON i.impression_id = c.impression_id
    GROUP BY d.category
)
SELECT
    category,
    total_impressions,
    total_clicks,
    ROUND(total_clicks * 100.0 / NULLIF(total_impressions, 0), 2) AS ctr_pct
FROM category_impressions
ORDER BY ctr_pct DESC;

-- 2. Feed 位置对 CTR 的影响
WITH position_stats AS (
    SELECT
        i.position,
        COUNT(DISTINCT i.impression_id) AS impressions,
        COUNT(DISTINCT c.click_id) AS clicks,
        ROUND(COUNT(DISTINCT c.click_id) * 100.0 / COUNT(DISTINCT i.impression_id), 2) AS ctr_pct
    FROM pin_impressions i
    LEFT JOIN pin_clicks c ON i.impression_id = c.impression_id
    GROUP BY i.position
)
SELECT position, impressions, clicks, ctr_pct
FROM position_stats
ORDER BY position;

-- 3. 用户的 Pin 收藏留存率
WITH first_impressions AS (
    SELECT
        user_id,
        pin_id,
        MIN(impression_time) AS first_impression_time
    FROM pin_impressions
    GROUP BY user_id, pin_id
),
saved_pins AS (
    SELECT DISTINCT
        user_id,
        pin_id,
        MIN(s.save_time) AS first_save_time
    FROM pin_saves s
    GROUP BY user_id, pin_id
)
SELECT
    COUNT(DISTINCT fi.user_id) AS total_users,
    COUNT(DISTINCT sp.user_id) AS users_who_saved,
    ROUND(COUNT(DISTINCT sp.user_id) * 100.0 / COUNT(DISTINCT fi.user_id), 2) AS save_rate_7d
FROM first_impressions fi
LEFT JOIN saved_pins sp
    ON fi.user_id = sp.user_id
    AND fi.pin_id = sp.pin_id
    AND sp.first_save_time BETWEEN fi.first_impression_time
        AND DATE_ADD(fi.first_impression_time, INTERVAL 7 DAY);

VO Round 1:Python + Spark 优化

这一轮由一位 Spark Platform 团队的 DE 进行,60 分钟。

题目:大规模 Pin 相似度计算

设计一个 Spark 程序,计算 Pin 之间的相似度,支持:

  1. 基于视觉特征的相似度计算
  2. 基于用户行为的协同过滤
  3. 支持增量更新

我的解答:

from pyspark.sql import SparkSession
from pyspark.sql.functions import *
from pyspark.sql.types import *
import numpy as np

spark = SparkSession.builder \
    .appName("PinSimilarityComputation") \
    .config("spark.sql.extensions", "io.delta.sql.DeltaSparkSessionExtension") \
    .getOrCreate()

# 加载 Pin 视觉特征
pin_features = spark.read.parquet("/data/pin_features/")  # pin_id, feature_vector

# 加载用户行为
user_interactions = spark.read.parquet("/data/user_interactions/")  # user_id, pin_id, interaction_type, timestamp

# 1. 视觉相似度计算 (Cosine Similarity)
def compute_visual_similarity():
    """计算视觉相似度矩阵"""
    # 使用广播变量优化小表 join
    broadcast_features = F.broadcast(pin_features)
    
    # 自连接计算相似度
    similarity = (pin_features.alias("a")
        .join(broadcast_features.alias("b"), col("a.pin_id") < col("b.pin_id"))
        .withColumn("similarity",
            cos_similarity(col("a.feature_vector"), col("b.feature_vector"))
        )
        .filter(col("similarity") > 0.85)  # 阈值过滤
        .select("a.pin_id AS pin_id_1", "b.pin_id AS pin_id_2", "similarity"))
    
    return similarity

# 2. 协同过滤
def compute_cf_similarity():
    """基于用户行为的协同过滤"""
    # 构建用户-Pin 交互矩阵
    user_pin_matrix = (user_interactions
        .groupBy("user_id", "pin_id")
        .agg(count("*").alias("interaction_count"))
        .filter(col("interaction_count") >= 2))  # 至少交互 2 次
    
    # 计算 Pin 共现矩阵
    pin_cooccurrence = (user_pin_matrix.alias("a")
        .join(user_pin_matrix.alias("b"), "user_id")
        .filter(col("a.pin_id") < col("b.pin_id"))
        .groupBy("a.pin_id", "b.pin_id")
        .agg(sum(col("a.interaction_count") * col("b.interaction_count")).alias("cooccurrence_score")))
    
    return pin_cooccurrence

# 3. 合并相似度并写入
visual_sim = compute_visual_similarity()
cf_sim = compute_cf_similarity()

# 合并两种相似度
combined_similarity = (visual_sim
    .unionByName(cf_sim, allowMissingColumns=True)
    .withColumn("visual_sim", coalesce(col("similarity"), lit(0)))
    .withColumn("cf_sim", coalesce(col("cooccurrence_score"), lit(0)))
    .withColumn("final_score", 
        col("visual_sim") * 0.6 + col("cf_sim") * 0.4)  # 加权合并)

# 写入 Delta 表
combined_similarity.write \
    .mode("overwrite") \
    .option("mergeSchema", "true") \
    .save("/data/pin_similarity/delta/")

面试官追问:

“如果 Pin 数量达到 10 亿级别,Spark 怎么处理?”

我回答:

  1. 分桶策略:对 Pin 特征进行分桶,只计算同桶内的相似度
  2. 近似最近邻 (ANN):使用 FAISS 或 ScaNN 进行近似相似度计算
  3. 增量更新:只对新 Pin 计算相似度,老 Pin 定期重新计算
# 使用 LSH (Locality Sensitive Hashing) 进行近似最近邻搜索
from pyspark.ml.feature import BucketedRandomProjectionLSH

lsh = BucketedRandomProjectionLSH(
    inputCol="feature_vector",
    outputCol="hashes",
    numHashTables=10
)

model = lsh.fit(pin_features)

# 找到近似相似对
similar_pairs = model.approxSimilarityJoin(
    pin_features, pin_features, 
    threshold=0.85,
    distCol="jaccardDistance"
).select("datasetA.pin_id", "datasetB.pin_id", "jaccardDistance")

VO Round 2:SQL + 数据建模

这一轮由一位 Analytics 团队的 DE 进行,60 分钟。

题目:用户行为分析数据模型

设计一个数据模型,支持:

  1. 用户日常行为分析(浏览、点击、收藏、分享)
  2. Pin 内容效果分析
  3. Feed 推荐效果评估

我的设计:

-- 1. 事实表:用户行为
CREATE TABLE fact_user_events (
    event_id STRING PRIMARY KEY,
    user_id STRING,
    pin_id STRING,
    event_type STRING,  -- view, click, save, share, comment
    event_time TIMESTAMP,
    session_id STRING,
    feed_position INT,
    device_type STRING,
    platform STRING,  -- web, ios, android
    country STRING
)
PARTITION BY DATE(event_time);

-- 2. 维度表:Pin 内容
CREATE TABLE dim_pins (
    pin_id STRING PRIMARY KEY,
    creator_id STRING,
    board_id STRING,
    category STRING,
    subcategory STRING,
    has_video BOOLEAN,
    created_at TIMESTAMP,
    load_date DATE DEFAULT CURRENT_DATE()
)
PARTITION BY DATE(created_at);

-- 3. 维度表:用户
CREATE TABLE dim_users (
    user_id STRING PRIMARY KEY,
    signup_date DATE,
    country STRING,
    device_type STRING,
    is_verified BOOLEAN,
    load_date DATE DEFAULT CURRENT_DATE()
);

-- 4. 聚合视图:每日用户行为摘要
CREATE VIEW v_daily_user_activity AS
SELECT
    DATE(ue.event_time) AS activity_date,
    ue.user_id,
    COUNT(CASE WHEN ue.event_type = 'view' THEN 1 END) AS views,
    COUNT(CASE WHEN ue.event_type = 'click' THEN 1 END) AS clicks,
    COUNT(CASE WHEN ue.event_type = 'save' THEN 1 END) AS saves,
    COUNT(CASE WHEN ue.event_type = 'share' THEN 1 END) AS shares,
    COUNT(DISTINCT ue.pin_id) AS unique_pins_viewed
FROM fact_user_events ue
GROUP BY DATE(ue.event_time), ue.user_id;

-- 5. Pin 效果分析视图
CREATE VIEW v_pin_performance AS
SELECT
    p.pin_id,
    p.category,
    p.created_at,
    COUNT(DISTINCT ue.event_id) AS total_events,
    COUNT(DISTINCT CASE WHEN ue.event_type = 'view' THEN ue.event_id END) AS impressions,
    COUNT(DISTINCT CASE WHEN ue.event_type = 'click' THEN ue.event_id END) AS clicks,
    COUNT(DISTINCT CASE WHEN ue.event_type = 'save' THEN ue.event_id END) AS saves,
    COUNT(DISTINCT CASE WHEN ue.event_type = 'share' THEN ue.event_id END) AS shares,
    ROUND(COUNT(DISTINCT CASE WHEN ue.event_type = 'click' THEN ue.event_id END) * 100.0 /
          NULLIF(COUNT(DISTINCT CASE WHEN ue.event_type = 'view' THEN ue.event_id END), 0), 2) AS ctr
FROM dim_pins p
LEFT JOIN fact_user_events ue ON p.pin_id = ue.pin_id
GROUP BY p.pin_id, p.category, p.created_at;

VO Round 3:System Design — 实时 Pin 推荐数据管道

这一轮由一位 Principal Engineer 进行,60 分钟。

题目:设计 Pinterest 的实时推荐数据管道

设计一个数据管道,支持:

  1. 实时收集用户行为数据
  2. 实时更新用户特征
  3. 支持实时推荐推理
  4. 端到端延迟 < 50ms

我的架构设计:

┌─────────────────────────────────────────────────────────────────┐
│                Pinterest Real-time Recommendation Pipeline       │
│                                                                   │
│  ┌──────────────────────────────────────────────────────────┐   │
│  │                    Data Collection                         │   │
│  │  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐   │   │
│  │  │ Pin Views    │  │ Pin Clicks   │  │ Pin Saves    │   │   │
│  │  │ (Kafka)      │  │ (Kafka)      │  │ (Kafka)      │   │   │
│  │  └──────────────┘  └──────────────┘  └──────────────┘   │   │
│  └──────────────────────────────────────────────────────────┘   │
│                            │                                     │
│                            ▼                                     │
│  ┌──────────────────────────────────────────────────────────┐   │
│  │                    Stream Processing                       │   │
│  │  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐   │   │
│  │  │ Spark        │  │ Feature      │  │ User Profile │   │   │
│  │  │ Streaming    │  │ Engineering  │  │ Update       │   │   │
│  │  └──────────────┘  └──────────────┘  └──────────────┘   │   │
│  └──────────────────────────────────────────────────────────┘   │
│                            │                                     │
│                            ▼                                     │
│  ┌──────────────────────────────────────────────────────────┐   │
│  │                    Feature Store                           │   │
│  │  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐   │   │
│  │  │ Redis        │  │ BigQuery     │  │ Pin Feature  │   │   │
│  │  │ (Real-time)  │  │ (Batch)      │  │ Index        │   │   │
│  │  └──────────────┘  └──────────────┘  └──────────────┘   │   │
│  └──────────────────────────────────────────────────────────┘   │
│                            │                                     │
│                            ▼                                     │
│  ┌──────────────────────────────────────────────────────────┐   │
│  │                    Model Serving                           │   │
│  │  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐   │   │
│  │  │ TensorFlow   │  │ FAISS        │  │ A/B Test     │   │   │
│  │  │ Serving      │  │ (ANN Search) │  │ Framework    │   │   │
│  │  └──────────────┘  └──────────────┘  └──────────────┘   │   │
│  └──────────────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────────────┘

关键技术决策:

  • Kafka:高吞吐事件收集,支持回溯和重放
  • Spark Streaming:流式特征计算,与批处理统一 API
  • Redis:实时特征存储,支持低延迟读取
  • FAISS:大规模向量相似度搜索,支持亿级 Pin 检索
  • TensorFlow Serving:模型推理服务,支持版本管理

VO Round 4:Behavioral

最后一轮由 Hiring Manager 进行,60 分钟。

典型问题

Q1: Why Pinterest?

我回答:Pinterest 是一个独特的发现平台——用户不是带着明确搜索目的,而是通过浏览 Feed 发现感兴趣的内容。这种”无意中发现”的场景对数据驱动的推荐系统提出了极高的要求,这正是吸引我的地方。

Q2: Describe a time you improved a data pipeline’s performance.

我分享了一个将 Spark 作业从 2 小时优化到 15 分钟的经历:

  • Situation: Pin 相似度计算作业运行时间过长,影响推荐模型更新频率
  • Task: 将作业运行时间从 2 小时缩短到 30 分钟以内
  • Action:
    1. 使用广播变量优化小表 join
    2. 对特征向量进行分桶,减少 shuffle 数据量
    3. 启用 Spark AQE 动态优化
    4. 使用 Delta Lake 替代 Parquet,利用时间旅行特性
  • Result: 运行时间缩短到 15 分钟,节省了 80% 的计算资源

面试总结

成功经验

  1. SQL 能力:窗口函数、CTE、分区表是基础
  2. Spark 优化:广播变量、分桶、AQE、Delta Lake
  3. System Design:实时推荐系统的数据管道设计
  4. 行为面试:准备性能优化和数据质量改进的故事

推荐阅读

  • Spark 性能优化指南 — 广播变量、分桶、AQE
  • 推荐系统数据管道 — 特征工程、模型训练
  • Kafka 架构设计 — 分区、副本、消费者组

💡 需要面试辅导?

如果你对准备技术面试感到迷茫,或者想要个性化的面试指导和简历优化,欢迎联系 Interview Coach Pro 获取一对一辅导服务。

👉 联系我们 获取专属面试准备方案

准备好拿下下一次面试了吗?

获取针对你的目标岗位和公司的个性化辅导方案。

联系我们