
一、这个生意的逻辑是什么?
大多数中小企业的核心困境是:数据散落在各处,却没法一起用。
订单在 PostgreSQL 里,日志在 S3 的 Parquet 文件里,Excel 报表在共享盘里,API 接口拿到的实时数据在缓存里。传统做法需要一个数据工程师搭 ETL、建数仓、写调度脚本——成本动辄数万,周期数周。
如果你提供一个"数据湖即服务":客户把你给的所有数据源接入同一个 DuckDB 实例,用一条 SQL 就能关联查询所有数据。你按查询量或存储量收费。
核心壁垒:DuckDB 的 ATTACH + httpfs + 直接读 Parquet 的能力,让你不需要任何 ETL 就能实现"数据不动、查询动"。客户不需要迁移数据,你的运维成本几乎为零。
二、技术架构:零 ETL 的统一查询层
客户数据源 你的服务
┌──────────────┐
│ PostgreSQL │ ──ATTACH postgres──┐
└──────────────┘ │
┌──────────────┐ │
│ S3/OSS │ ──ATTACH s3───────┼── DuckDB Engine
│ Parquet 文件 │ read_parquet() │ (Serverless)
└──────────────┘ │
┌──────────────┐ │
│ HTTP API │ ──ATTACH httpfs───┘
│ JSON/CSV │ read_json_auto()
└──────────────┘
每一层都不需要搬运数据。DuckDB 直接在数据源上执行谓词下推(predicate pushdown),只读取需要的列和行。
三、完整代码实现
Step 1:搭建统一查询引擎
import duckdb
# Serverless 模式:内存数据库,用完即销毁
con = duckdb.connect(":memory:")
# 注册 PostgreSQL 数据源(客户的生产库)
con.execute("""
ATTACH 'postgresql://user:***@host:5432/orders' AS orders (TYPE POSTGRES);
""")
# 注册 S3/OSS 上的 Parquet 数据湖
con.execute("""
ATTACH 's3://my-data-lake/' AS datalake (TYPE duckdb_io_s3, REGION us-east-1);
""")
# 注册 HTTP 数据源(外部 API)
con.execute("""
ATTACH 'http://api.example.com/data/' AS api_data (TYPE httpfs);
""")
Step 2:跨源关联查询——核心卖点
SELECT
o.order_id,
o.customer_id,
o.amount,
p.category,
p.unit_cost,
o.order_date,
ROUND(o.amount - p.unit_cost * o.quantity, 2) AS profit,
weather.temperature,
weather.rainfall
FROM orders.main.orders o
JOIN datalake.products.parquet p
ON o.product_id = p.product_id
LEFT JOIN api_data.weather_data weather
ON DATE(o.order_date) = weather.date
WHERE o.order_date >= '2026-01-01'
ORDER BY profit DESC
LIMIT 100;
这段 SQL 的执行流程:
- DuckDB 解析 SQL,生成物理计划
- 对
orders表的order_date谓词下推到 PostgreSQL,只拉取 2026 年的数据 - 对
datalake/products.parquet利用 Parquet 的 zone map 做分区裁剪 - 对
api_data发起 HTTP 请求获取天气数据 - 内存中执行 JOIN 和计算
- 返回结果
整个过程没有一次数据全量搬运。
Step 3:封装为可复用的报告模板
def generate_profit_report(start_date, end_date, region=None):
sql = """
SELECT
p.region,
p.category,
COUNT(*) AS order_count,
SUM(o.amount) AS total_revenue,
SUM(o.amount - p.unit_cost * o.quantity) AS total_profit,
AVG(o.amount - p.unit_cost * o.quantity) AS avg_margin,
PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY o.amount) AS median_order_value
FROM orders.main.orders o
JOIN datalake.products.parquet p
ON o.product_id = p.product_id
WHERE o.order_date BETWEEN '{start}' AND '{end}'
""".format(start=start_date, end=end_date)
if region:
sql += f" AND p.region = '{region}'"
sql += " GROUP BY ALL ORDER BY total_profit DESC"
result = con.execute(sql).fetchdf()
return result
Step 4:自动化定时报告
import duckdb
import smtplib
from email.mime.text import MIMEText
from datetime import datetime, timedelta
def daily_report():
con = duckdb.connect(":memory:")
con.execute("ATTACH 'postgresql://user:***@host:5432/orders' AS orders (TYPE POSTGRES)")
con.execute("ATTACH 's3://my-data-lake/' AS datalake (TYPE duckdb_io_s3, REGION us-east-1)")
yesterday = (datetime.now() - timedelta(days=1)).strftime("%Y-%m-%d")
sql = f"""
SELECT
DATE(order_date) AS sale_date,
COUNT(*) AS orders,
SUM(amount) AS revenue,
SUM(amount) - SUM(unit_cost * quantity) AS profit
FROM orders.main.orders o
JOIN datalake.products.parquet p ON o.product_id = p.product_id
WHERE DATE(order_date) = '{yesterday}'
GROUP BY ALL
"""
result = con.execute(sql).fetchdf()
html = f"""
<h2>📊 昨日经营日报 | {yesterday}</h2>
<p>订单数:<b>{result['orders'].sum()}</b></p>
<p>营收:<b>¥{result['revenue'].sum():,.2f}</b></p>
<p>利润:<b>¥{result['profit'].sum():,.2f}</b></p>
<p>利润率:<b>{result['profit'].sum()/result['revenue'].sum()*100:.1f}%</b></p>
"""
msg = MIMEText(html, 'html')
msg['Subject'] = f'经营日报 {yesterday}'
msg['From'] = '[email protected]'
msg['To'] = '[email protected]'
with smtplib.SMTP('smtp.company.com', 587) as server:
server.starttls()
server.login('[email protected]', 'password')
server.send_message(msg)
四、与传统方案的对比
| 方案 | 数据迁移 | 开发周期 | 运维成本 | 扩展性 |
|---|---|---|---|---|
| 传统 ETL + 数仓 | 需要全量搬运 | 2-4 周 | 高(调度、监控) | 差 |
| Flink/Kafka 实时管道 | 需要改造 | 1-3 月 | 极高 | 中 |
| DuckDB ATTACH 零 ETL | 不需要 | 1-2 天 | 极低 | 好 |
DuckDB 方案的优势在于:客户现有的数据基础设施完全不用动。你只是在上面加了一层 SQL 查询接口。对客户来说风险极低——没有任何沉没成本损失。
五、变现路径详解
路径一:按查询次数收费(轻量级)
适合初期验证市场:
- 为 10 家中小企业提供统一数据查询服务
- 每家每月 500 次查询 × ¥0.5/次 = ¥250/家
- 月收入 ¥2,500,边际成本几乎为零(DuckDB 是 serverless 的)
获客策略:在知识星球/社群分享"一条 SQL 查所有数据源"的技巧,吸引第一批用户。
路径二:SaaS 化(规模化)
将上述代码封装为 Web 应用:
- FastAPI 提供 SQL 查询 API
- Streamlit 提供可视化前端
- 客户上传数据源配置 → 你托管 DuckDB 实例 → 客户通过 Web 界面写 SQL
- 定价:¥999/月/企业,目标 50 家 = ¥50,000/月
技术栈:
# FastAPI 示例
from fastapi import FastAPI, HTTPException
import duckdb
app = FastAPI()
@app.post("/query")
def run_query(config: dict, sql: str):
con = duckdb.connect(":memory:")
for source in config.get("sources", []):
con.execute(f"ATTACH ...")
try:
result = con.execute(sql).fetchdf()
return {"columns": result.columns.tolist(), "data": result.values.tolist()}
except Exception as e:
raise HTTPException(status_code=400, detail=str(e))
路径三:数据产品(高附加值)
将查询结果打包为行业报告:
- 一份深度行业利润分析报告定价 ¥299
- 月销 100 份 = ¥29,900
- 边际成本为零,一次开发反复售卖
关键:选择利润空间大、数据透明度低的行业(如跨境电商、本地生活服务)。
六、关键注意事项
- 安全性:ATTACH 的凭证必须加密存储,不要硬编码。推荐使用环境变量或密钥管理服务。
- 性能:对于超大数据集,先用
EXPLAIN ANALYZE查看执行计划,确认谓词下推生效。 - 成本:DuckDB 是 CPU 密集型,选择合适规格的实例(8核以上性价比最高)。
- 并发:DuckDB 原生支持多线程,但 ATTACH 远程数据源时注意并发连接数限制。建议在 API 层做连接池管理。
- 超时控制:HTTP API 数据源响应慢,设置合理的 timeout 参数避免阻塞。
七、为什么现在入场正是时候?
DuckDB 在 2026 年已经具备了生产级数据湖查询能力:
- PostgreSQL 扩展:
duckdb_attach_postgres()直接连接生产库 - S3/OSS 原生支持:
duckdb_io_s3类型无需额外配置 - httpfs 扩展:直接查询远程 JSON/CSV/Parquet
- Parquet 谓词下推:自动裁剪不需要的列和数据块
- GROUP BY ALL:简化聚合查询,减少 80% 代码量
这些能力的组合,让"数据湖即服务"从一个概念变成了今天就能上线的产品。
八、变现建议总结
| 阶段 | 动作 | 预期收入 | 时间投入 |
|---|---|---|---|
| 第 1 周 | 搭建 MVP,测试 3 个数据源 | ¥0(验证期) | 1-2 天 |
| 第 2-4 周 | 找 3-5 家种子客户免费试用 | ¥0(换反馈) | 每周 5 小时 |
| 第 2 个月 | 开始收费,目标 10 家客户 | ¥2,500-5,000/月 | 每周 10 小时 |
| 第 3-6 个月 | SaaS 化,扩展至 50 家 | ¥10,000-50,000/月 | 每周 15 小时 |
| 第 6 个月后 | 推出行业报告产品 | ¥30,000-80,000/月 | 边际成本趋零 |
用 DuckDB 做数据湖查询层,最大的优势就是不需要迁移数据。客户的现有数据基础设施完全不用动,你只是在上面加了一层 SQL 查询接口。这对客户来说风险极低——没有任何沉没成本损失,对你来说是极低的获客阻力。
📖 本文涉及的全部代码模板和部署脚本已整理成完整教程,包含 S3 认证配置、PostgreSQL 安全连接、Streamlit 前端封装等细节,详见 duckdblab.org