Articles · Databricks
Databricks 面试准备:Coding、System Programming 与架构讨论怎么区分
Databricks 面试准备指南:用官方资料区分 Coding、System Programming 与 Architecture 的边界。围绕事件写入器,练习容量、接纳、并发关闭、持久确认、崩溃恢复与跨服务重试,明确内存模型不能证明磁盘耐久性。标注 PDF 的版本与岗位范围,不把产品知识或邻近角色面经当成统一流程。
Databricks 面试准备最容易出现的偏差,是把 Coding、System Programming 与 Architecture 都叫作“系统设计”,然后用同一张分布式架构图回答。更有效的区分是你需要交付什么:可运行的实现、可检查的单机系统组件,还是从请求到数据的端到端系统。
本文面向准备工程岗位的中文读者,以官方资料中的后端工程部分解释这三种讨论边界,不推定所有团队采用同一套轮次。用 Databricks Software Engineer questions补充练习时,也应先分辨记录对应的能力,而不是只看题目是否出现 cache、file 或 thread。
官方材料能确认哪些区别
Databricks 当前工程 Careers 页面明确说明流程因岗位而异。官网可读取的工程面试准备 PDF是 2025 年 4 月版,分别列出后端、全栈及其他工程方向。因此,首先确认招聘方发给你的是哪个岗位部分,以及是否有更新材料。
该 PDF 的后端部分把 Coding 描述为实现质量、测试与边界处理;System Programming 强调并发、同步、I/O、性能与细化的系统组件伪代码;Architecture 讨论从数据库到 UI 的系统、请求路径、可靠性与取舍。下表是据此整理的准备建议,不是官方评分表,也不是本次邀请的轮次顺序。
| 讨论类型 | 你应展示的产物 | 容易答错的方向 |
|---|---|---|
| Coding | 明确契约、实现、复杂度与测试 | 只有组件图,没有函数行为 |
| System Programming | 状态、共享数据、同步与 I/O 失败语义 | 一句“加锁”或“放队列”,没有关键步骤 |
| Architecture | 端到端路径、规模假设、数据边界与故障行为 | 纠结一个循环,却没有服务之间的协议 |
PDF 还单列 Algorithm:算法选择与效率本身更突出,但不意味着 Coding 可以不分析复杂度。本文没有核实两份同周期、同角色的独立候选人报告,因此不增加固定题数、当前总轮数或统一录取标准。论坛中求建议的帖子、邻近角色的经历,以及带推广目的的描述都不用于填补这个缺口。
同一个事件写入需求,可以有三种问法
下面是原创练习:应用接收事件 (event_id, payload),写入文件,进程可能在任意位置崩溃。初始版本只要求单线程把合法记录编码成一段输出;升级版本要求多个调用方共用一个写入器;再升级才讨论跨服务重试与分区。它不是 Databricks 原题,也不是对 Databricks 产品内部实现的描述。
在 Coding 版本中,先约定 ID 是否唯一、payload 的编码、换行或长度字段、非法记录如何处理。实现应该能把一个输入映射到确定的结果。若采用长度前缀,长度是字节数还是字符数?多字节文本就能揭示差别。不要写出“平时看起来能读”的格式却没有解码契约。
最简单的测试包括空批次、空字符串、重复 ID、多字节内容和 payload 中含换行。若定义批次存在非法记录就整体拒绝,先验证再产生可见输出,避免在第三条发现错误时前两条已经写入。若允许部分成功,就返回逐条状态并说明重试规则,两种约定不能混用。
这时重点是明确行为与可测试的模块,不需要把它升级为 Kafka 替代品。反过来,如果题目已经要求跨线程共享、关闭与崩溃恢复,仅展示序列化函数就还没有完成 System Programming 的问题。
System Programming 从 admission 开始,而不是从 fsync 结束
Admission 指系统决定是否接纳一次操作,并承担后续处理责任。Durability 指在约定的故障范围内,已经确认的数据可以恢复。这两者不同:进入内存队列不等于落盘成功,成功的系统调用也不自动等于断电恢复保证。
为原创写入器定义三种状态:OPEN 接受新事件;DRAINING 拒绝新事件、处理已经接纳的事件;CLOSED 不再处理。接纳操作与切换到 DRAINING 必须有明确的原子边界。否则关闭线程认为队列已空,而另一个线程刚好在检查之后放入事件,这条记录可能永远没人写。
队列容量也要有契约。下面只实现非阻塞接纳的内存状态模型:容量满时拒绝,容量指“接纳但尚未完成的事件”,包括正在处理的一条。模型用互斥锁保护状态与容量,不包含真正文件 I/O、线程唤醒、持久化或性能验证。
from collections import deque
from threading import Lock
class Admission:
def __init__(self, capacity):
if type(capacity) is not int or capacity <= 0:
raise ValueError("capacity must be positive")
self.capacity = capacity
self.state = "OPEN"
self.pending = deque()
self.outstanding = 0
self.lock = Lock()
def offer(self, item):
if item is None:
raise ValueError("None is reserved for an empty take")
with self.lock:
if self.state != "OPEN" or self.outstanding == self.capacity:
return False
self.pending.append(item)
self.outstanding += 1
return True
def take(self):
with self.lock:
return self.pending.popleft() if self.pending else None
def finish(self):
with self.lock:
if self.outstanding <= len(self.pending):
raise RuntimeError("no in-flight item")
self.outstanding -= 1
if self.state == "DRAINING" and self.outstanding == 0:
self.state = "CLOSED"
def close(self):
with self.lock:
self.state = "DRAINING" if self.outstanding else "CLOSED"
finish 的使用前提是消费者对每个取出的项目恰好调用一次,并且处理成功或失败已经被明确通知给调用方。它不是“磁盘成功”的接口。把这份前提写出来,才能看出模型遗漏了项目级完成令牌:生产版可能需要按项目完成,拒绝重复完成,而不是仅用总计数。
Python 的 queue 官方文档说明队列的同步、容量与任务跟踪操作,也提醒队列大小判断的局限。它可作为语言库语义的参考,但 join 或任务完成计数仍不是文件持久化证明;最终确认必须绑定实际写入器的约定。
模型要求容量是正整数,并保留 None 表示空队列,避免消费者无法分辨“取出一个 None 事件”与“没有事件”。若业务真的允许空值,就应返回有明确存在标记的结果或单独的哨兵对象,而不是让调用方靠猜测判断。
精确说出确认与崩溃的关系
接纳后让单一写线程编码与写入,可以减少文件偏移竞争,但仍需要处理部分写入:底层一次写入未必完成整个缓冲区。伪代码应有剩余字节循环、失败后的状态,以及哪些调用方得到错误。用“调用 write”四个字略过这些细节,不能证明组件正确。
假设确认要求记录已经完成约定的持久化动作,那么正常路径是接纳、编码、完整写入、持久化、通知完成。同步持久化可降低未确认窗口,却增加每次延迟;批量持久化可以摊薄成本,但必须说明批次内哪些记录一起完成,以及失败时怎样传播错误。
| 崩溃位置 | 调用方观察到什么 | 恢复后需要区分什么 |
|---|---|---|
| 接纳前 | 未接纳或没有明确结果 | 可以重新尝试接纳 |
| 入队后、持久化前 | 尚未收到持久成功 | 记录可能不存在或不完整 |
| 持久化后、回复前 | 超时,结果不确定 | 记录可能已存在,重试可能重复 |
| 回复成功后 | 已收到约定成功 | 恢复必须兑现已确认数据的承诺 |
文件恢复可提出帧长度、校验和、版本字段与完整记录扫描。最后一帧不完整不等于整个文件都损坏;但若没有可区分的记录边界,就难以安全截断尾部。新增文件、重命名与目录元数据的耐久性也需要按系统与文件系统语义另行确认,本文的内存模型没有测试这些行为。
关闭不是“把线程停掉”
关闭需要回答谁还能提交、谁等待完成、等待多久以及错误怎样返回。优雅关闭通常先停止接纳,再处理已接纳任务,然后释放资源。若直接杀掉消费者,队列中的承诺会变成悬空状态;若无限等待磁盘,则关闭本身可能成为事故中的阻塞点。
检查一个容量为 2 的状态轨迹:接纳 A、B 后拒绝 C;取出 A 不会释放 outstanding 容量,因为 A 还没完成;关闭后拒绝 D;完成 A 与 B 后才能进入 CLOSED。这个轨迹比写“有一个锁”更能检验容量与关闭是否使用同一套语义。
扩展到阻塞接纳时,还要让等待者在关闭或致命 I/O 错误时醒来。锁内不宜执行慢 I/O,否则接纳、取消与关闭都会被同一个磁盘等待牵连。可以在锁内变更状态、移出批次,在锁外写入,再以明确的完成协议通知。分离这些步骤之后,必须重新检查所有权和重复完成。
错误模式至少分两类:某条记录无法编码,可能只拒绝该条;存储不可写则可能影响后续所有接纳,写入器应转成失败状态并通知等待者。题目若要求关闭可重入,也要说明第二次关闭不会重新打开系统或丢失先前错误,而不是靠“通常不会调用两次”处理。
Architecture 把不确定结果带到服务边界
当写入器变成远程服务,“超时就是没写”不再成立。持久化后响应丢失,客户端会重试;因此需要为去重定义事件 ID 的作用域、重复载荷是否必须一致、去重状态保存多久,以及服务重启后是否仍可识别已完成记录。
幂等意味着同一个逻辑操作在约定范围内重复执行,不产生额外业务效果。它不是简单缓存一次 HTTP 响应,也不是“使用 UUID”就自动成立。若去重表只在内存中,重启后可能重复落入;若去重标记与业务数据分别持久化,则崩溃可能令两者不一致。
再讨论分区:按租户或事件键分配可以保留某种局部顺序,但一个热门键仍可能形成热点。跨分区全局顺序、单键顺序与无顺序要求的成本不同。不要承诺全球所有事件严格排序,又只画独立写入器而没有协调机制。
容量估算必须说明单位。一个虚构需求若是每秒 2,000 条、每条编码后 1 KB,则有效负载约为每秒 2 MB;复制、索引与协议开销另算。若存储短时只能处理每秒 1,500 条,积压每秒增长 500 条,10 秒约增加 5,000 条。有限队列只能吸收突发,不能解决长期到达率大于处理率。
在设计图旁列出过载策略:拒绝并提示退避、按租户配额、保留有界缓冲,或降级非关键事件。不要把丢弃遥测与丢弃计费记录混为同一策略。你选择的策略应能沿着 API 返回码与重试行为传到客户端。
项目深挖不能用本次练习替代真实经验
官方 PDF 另列 Domain Deep Dive,与你实际解决过的问题相关。不要把上面的玩具模型描述成自己建设过的线上持久化系统。没有相关经历时,可以清楚说明这是准备练习,转而用真实项目展示接口边界、测试、故障定位与决策过程。
准备一个能解释的片段:你负责哪一层、当时约束是什么、为什么选择某个方案、怎样发现它的缺点。若指标来自离线压测,就说离线;若改善发生在整个团队上线之后,就不要把全部收益归给一个提交。被问到尚未做过的并发或恢复问题时,说明会怎样验证,比虚构生产事故更可靠。
用一组可检查的操作序列检验内存模型
先在单线程中验证状态,不把“启动了几个线程没有报错”当作并发正确性证明。容量 2 时,接纳 A 和 B 后 outstanding 为 2;take 取走 A 后 pending 只剩 B,但 outstanding 仍是 2。此时 C 被拒绝,说明容量统计的是未完成承诺,不是队列当前长度。
继续 close,状态变为 DRAINING;再 offer D 仍须拒绝。finish A 后 outstanding 为 1,不能提前关闭。取出并完成 B 后才是 CLOSED。最后再次 close 不应重新开放;空队列的 take 返回 None,不能当作一个待完成事件。对没有进行中的事件调用 finish,应暴露调用协议错误。
并发讨论中,圈出 offer 与 close 各自获得同一把锁的区间。若 offer 先完成状态检查并接纳,close 必须把它包括在待处理责任中;若 close 先切换状态,offer 就不能接纳。这说明接纳边界如何排序,但还没有证明消费者通知、真实 I/O 或每个 future 的完成协议。
如果需求改成多个消费者,就追问完成顺序是否等于文件顺序。两个消费者依次取到 A、B,却可能先完成 B;只靠队列先进先出不能保证落盘先后。若要求严格顺序,可以保留单写线程,或引入序号与有界重排;任何选择都需要说明慢事件是否阻塞后续事件。
也要区分取消的两个阶段:未被接纳前可以直接返回拒绝;接纳后若调用方放弃等待,系统是否仍须兑现写入责任?取消等待与撤销写入不是同一个动作。题目没有承诺撤销时,不能因 future 被取消就丢掉已经接纳的数据。
练习交付物是否匹配讨论类型
Coding 的自查可以是一份编码与解码的往返测试,System Programming 的自查可以是一条操作轨迹和一条崩溃轨迹,Architecture 的自查则需要显示客户端超时后重试如何穿过服务、去重状态与存储。三种产物可围绕相同事件,但不能互相替代。
遇到范围不清的追问,可以先说:“我目前按单机组件解释接纳和持久确认;您希望继续细化锁与 I/O,还是扩展到跨机器复制?”这不是拖延作答,而是明确接下来证明哪一层。确认范围后,继续给具体行为,不停留在术语解释。
先知道要交付什么,再决定练什么
Databricks 面试准备不是背更多产品名,也不是把所有技术讨论升级成大规模设计。先用当前邀请确认范围,再检查自己的实现、系统组件和端到端路径是否各有可检验的产物。
到 Databricks Software Engineer questions选择与你将参加的讨论类型相符的一题,尝试用另一种边界重讲同一需求。若讲 Coding 时只有图,或讲 Architecture 时只有锁,就是下一次练习最该修复的偏差。
资料来源与延伸阅读
- Databricks:Engineering at Databricks 与岗位可变流程
- Databricks:Engineering Careers Site Interview Prep,2025 年 4 月版
- Python:queue 同步、容量与任务跟踪文档
核查日期:2026 年 10 月 6 日。官方 PDF 的版本日期与本次核查日期不同;具体轮次仍需招聘方确认。模型仅验证内存状态契约,不验证真实磁盘、断电恢复或 Databricks 生产系统。