创业团队技术选型7月复盘:我们做的5个正确与3个错误决策
一、选型的起点:生存优先
技术选型在创业团队中有一个核心约束:资源极度有限。
我们没有大厂的"试试看"预算。
每一个技术决策都意味着未来6-12个月的沉没成本。
7月是团队成立的第14个月。
我们经历了技术栈的两次重大调整和无数小修复。
回头看,有些决策救了命,有些决策埋了雷。
本文整理5个正确决策和3个错误决策。
每个决策都附上了当时的情境、选择的逻辑和后来的验证。
二、五个正确决策
正确决策一:起步用Monolith,而非微服务
初期只有3个后端和一个前端。
有人建议直接上微服务,"便于后续扩展"。
我们坚持选择了Monolith。
18个月后的验证:
- Monolith开发效率是微服务的2-3倍(在10人以下团队)。
- 用户增长到8万DAU时才开始拆分第一个服务。
- 拆分时数据模型清晰,边界明确,远比"先拆分再调整"容易。
核心逻辑:
微服务的价格 = 运维复杂度 + 网络延迟 + 数据一致性 在验证PMF之前,不要为还没到来的规模支付这个价格。正确决策二:选择生态成熟的技术栈
Go作为后端主语言,不是因为"Go是最好的语言"。
而是因为:
- 标准库丰富(net/http、context、testing都在标准库里)。
- 部署简单(单二进制文件,无需运行时)。
- 招聘相对容易(Go开发者在创业圈供给充足)。
PostgreSQL作为唯一数据库,没有引入MySQL/Redis分片。
理由是:一个PostgreSQL能搞定的事情,不要引入新组件。
// 我们坚持的单数据库架构核心 type Repository struct { db *sql.DB } // 利用PostgreSQL的特性,而非引入新组件 func (r *Repository) CreateTaskQueue(name string) error { // 用PostgreSQL的LISTEN/NOTIFY替代Redis pub/sub _, err := r.db.Exec(` CREATE OR REPLACE FUNCTION notify_task() RETURNS trigger AS $$ BEGIN PERFORM pg_notify('task_channel', json_build_object( 'id', NEW.id, 'type', NEW.type, 'status', NEW.status )::text); RETURN NEW; END; $$ LANGUAGE plpgsql; `) return err }正确决策三:不做性能优化,做性能基准
初期不优化,但建立了完整的性能基准:
- 每个API的P50/P95/P99延迟。
- 数据库慢查询的周度基线。
- 内存和CPU的7日趋势。
当性能真的出问题时,有数据支撑、有方向可循。
而不是"感觉慢了就瞎优化"。
# 性能基准采集脚本(简化) import psycopg2 import time from statistics import median def benchmark_endpoint(url: str, samples: int = 1000): """采集API延迟分布""" latencies = [] for _ in range(samples): start = time.perf_counter() response = requests.get(url) latencies.append(time.perf_counter() - start) latencies.sort() return { 'p50': latencies[int(samples * 0.5)], 'p95': latencies[int(samples * 0.95)], 'p99': latencies[int(samples * 0.99)], 'max': latencies[-1], 'avg': sum(latencies) / samples, }正确决策四:基础设施即代码(IaC)早于功能开发
在MVP完成后就做了完整的Terraform+Ansible自动化。
这个决策当时看起来"浪费了宝贵的开发时间"。
但后来证明是回报最高的技术投资:
- 环境搭建从半天→15分钟。
- 故障恢复从手动→自动。
- 新人入职第二天就能部署完整环境。
正确决策五:日志先行,监控后补
我们反常识地先做了结构化日志,然后才做Metrics和Tracing。
原因是:对于创业团队,95%的问题通过日志能定位。
Metrics告诉你有问题,日志告诉你问题在哪。
// 结构化日志规范 type LogEntry struct { Timestamp time.Time `json:"ts"` Level string `json:"level"` Message string `json:"msg"` TraceID string `json:"trace_id,omitempty"` UserID string `json:"uid,omitempty"` Duration time.Duration `json:"dur,omitempty"` Error string `json:"error,omitempty"` Extra map[string]any `json:"extra,omitempty"` } func (l *Logger) InfoWithContext(ctx context.Context, msg string, fields map[string]any) { entry := LogEntry{ Timestamp: time.Now(), Level: "INFO", Message: msg, TraceID: traceIDFromContext(ctx), Extra: fields, } l.write(entry) }三、三个错误决策
错误决策一:过早引入GraphQL
团队在第8个月引入了GraphQL,初衷是"让前端更灵活地获取数据"。
但带来的问题远多于解决的问题:
- N+1查询问题从偶发变为常态。
- 缓存策略从简单(REST+CDN)变为复杂(需要分析query字符串)。
- 认证鉴权从中间件层面降级到resolver层面。
3个月后回退到RESTful API,损失了约120个开发人日。
教训:API复杂度的提升应该由用户需求驱动,而非技术兴趣驱动。
在DAU<10万的阶段,RESTful是最优解。
错误决策二:技术栈的"求新"心理
在框架选择上多次犯了"因为新所以好"的错误。
典型案例:在第10个月升级到Go 1.23的一个实验性特性,
结果在生产环境暴露了一个编译器bug,回滚了一夜。
反思后的选型三个原则:
- 选最稳定的版本,不选最新的版本。
- 选团队最熟悉的,不选社区最热的。
- 选已验证的方案,不选论文中的方案。
错误决策三:基础设施过度设计
第一版K8s集群为了"高可用",配置了3个master+5个worker。
月成本8000元。但实际负载最多占用30%的资源。
而我们当时的月收入才不到3万。
更务实的方案应该是:
阶段一(验证期):单机Docker Compose(成本:500元/月) 阶段二(成长期):托管K8s最小化(成本:2000元/月) 阶段三(规模期):自建K8s集群(成本:视规模而定)我们在阶段一就上了阶段三的架构。
四、技术债务的7月清理
7月我们做了一次集中的技术债务清理:
- 数据库:清理了23个历史遗留的无用索引,写入性能提升12%。
- 代码:重构了3个最复杂的服务模块,圈复杂度从平均35降到18。
- 监控:补齐了之前"计划中但没做"的10个核心告警规则。
- 文档:编写了架构决策记录(ADR),记录了8个关键决策的背景和理由。
这次清理投入了2个工程师3周的时间。
ROI验证:后续两个迭代的开发效率提升了约20%。
五、总结
核心技术提炼:
- 五正确决策:Monolith起步(PMF前不做微服务)、选成熟技术栈(Go+PostgreSQL)、做性能基准不做优化、IaC早于功能、日志先于监控。
- 三错误决策:过早引入GraphQL(复杂度过早引入)、追新心理(选稳定、不选最新)、基础设施过度设计(匹配增长阶段做架构)。
- 选型三筛法:必要性(解决当前问题?)→ 替代性(有更简单方案?)→ 可逆性(可逆且低成本?)。
- 技术债清理ROI公示:无用索引清理→写入+12%、代码圈复杂度减半→开发效率+20%。
技术债是利息,定期偿还比破产重组便宜。 - 架构的黄金法则:架构复杂度应与业务规模成正比。
规模未到时的复杂性 = 纯负债。