数据存储

第08课:Parquet与Avro格式

数据工程实战课程 · 数据存储阶段

💾 Parquet与Avro格式

列式存储在分析场景下具有更高压缩率和更快查询,Parquet和Avro是两种最重要的开放数据格式。

🐍 Python实战:核心实战

# Parquet格式模拟:列式存储、压缩、谓词下推
import hashlib, time, random, json
from datetime import datetime

class ColumnChunk:
    def __init__(self, name, dtype):
        self.name = name; self.dtype = dtype; self.values = []
        self.stats = {'min':None,'max':None,'null_count':0,'distinct':0}
        self.uncompressed = 0; self.compressed = 0
    def add(self, v):
        self.values.append(v); self.uncompressed += len(str(v).encode())
    def compute_stats(self):
        non_null = [v for v in self.values if v is not None]
        self.stats['null_count'] = len(self.values)-len(non_null)
        if non_null:
            self.stats['min'] = min(non_null); self.stats['max'] = max(non_null); self.stats['distinct'] = len(set(non_null))
        self.compressed = int(self.uncompressed * 0.3)
    def can_skip(self, op, val):
        mn, mx = self.stats.get('min'), self.stats.get('max')
        if mn is None: return False
        if op == '>' and mx <= val: return True
        if op == '<' and mn >= val: return True
        return False

class RowGroup:
    def __init__(self): self.columns = {}; self.row_count = 0
    def add_column(self, name, dtype, values):
        chunk = ColumnChunk(name, dtype)
        for v in values: chunk.add(v)
        chunk.compute_stats(); self.columns[name] = chunk
        self.row_count = max(self.row_count, len(values))

class ParquetFile:
    def __init__(self, schema): self.schema = schema; self.row_groups = []
    def write_row_group(self, records):
        rg = RowGroup()
        for col, dtype in self.schema.items():
            rg.add_column(col, dtype, [r.get(col) for r in records])
        self.row_groups.append(rg)
    def read_column(self, col, predicates=None):
        result = []; skipped = 0
        for rg in self.row_groups:
            skip = False
            if predicates:
                for pc, (op, val) in predicates.items():
                    if pc in rg.columns and rg.columns[pc].can_skip(op, val): skip = True; break
            if skip: skipped += 1; continue
            if col in rg.columns: result.extend(rg.columns[col].values)
        return result, skipped
    def stats(self):
        total = sum(rg.row_count for rg in self.row_groups)
        comp = sum(sum(c.compressed for c in rg.columns.values()) for rg in self.row_groups)
        uncomp = sum(sum(c.uncompressed for c in rg.columns.values()) for rg in self.row_groups)
        return {'rows':total,'compressed':comp,'uncompressed':uncomp,'ratio':round(uncomp/comp,2) if comp else 0}

schema = {'id':'int','name':'string','amount':'float','region':'string'}
pf = ParquetFile(schema)
for rg_idx in range(5):
    records = [{'id':rg_idx*1000+i+1,'name':f'客户_{rg_idx*1000+i+1}','amount':round(random.uniform(100,10000),2),'region':random.choice(['华东','华南','华北','西南','东北'])} for i in range(1000)]
    pf.write_row_group(records)

s = pf.stats()
print(f"Parquet: {s['rows']}行, 压缩{1/s['ratio']:.1%}, 压缩比{s['ratio']}x")
amounts, _ = pf.read_column('amount')
print(f"列裁剪amount: {len(amounts)}值")
result, skipped = pf.read_column('amount', {'amount':('>',8000)})
print(f"谓词下推(>8000): {len(result)}值, 跳过{skipped}行组")
for col, chunk in pf.row_groups[0].columns.items():
    print(f"  {col}: [{chunk.stats['min']},{chunk.stats['max']}], distinct={chunk.stats['distinct']}, 压缩{chunk.uncompressed/chunk.compressed:.1f}x")
print("✅ 验证通过 - Parquet模拟运行正常")
💡 实战建议:动手实现本课代码示例,修改参数观察输出变化。生产环境还需考虑错误处理、监控告警和数据安全。

🎯 Parquet压缩

  1. RLE编码
  2. 字典编码
  3. 对比压缩率

🎯 Schema演进

  1. Avro兼容性规则
  2. 向前/向后兼容

🏆 第8课成就解锁

🎁 下一课预告:分区与分桶!

📖 原理深入:现代数据存储架构

存储引擎选型指南

存储成本优化策略

策略原理节省比例
分层存储热SSD/温HDD/冷对象存储50-80%
数据压缩列式存储高压缩比60-90%
生命周期自动归档删除30-60%
降采样历史数据低精度70-95%

🏆 最佳实践:数据存储设计检查清单

🔬 深度案例:生产环境实战经验

踩坑与解决方案

在生产环境中,我们经常遇到教科书不会告诉你的问题。以下是常见的实战经验和解决方案:

监控与运维指标

指标告警阈值监控方式
管道延迟超过SLA 120%端到端时间戳追踪
数据量偏差日均偏差超过30%与历史同期对比
错误率超过0.1%错误计数/总记录数
数据新鲜度超过SLA 150%最新数据时间戳
资源利用率CPU持续>80%系统监控
⚠️ 常见反模式
💡 架构演进建议:不要一开始就追求完美架构。从MVP开始,随业务增长逐步演进。每次演进都要回答三个问题:当前瓶颈是什么?解决它的投入产出比如何?是否为后续扩展留了空间?

📚 延伸阅读与参考

推荐资源

🤔 常见问题与解答

Q1: 如何选择批处理还是流处理?

A: 根据业务延迟要求决定:秒级用流处理、分钟级用微批、小时级用批处理。大部分场景批处理足够,流处理用于实时业务场景(风控、监控、推荐)。

Q2: 数据湖会取代数据仓库吗?

A: 不会完全取代,但边界在模糊。湖仓一体(如Iceberg/Delta Lake)正在融合两者优势。未来趋势是统一存储+多引擎计算。

Q3: 初创公司如何选择数据技术栈?

A: 先用云原生服务(BigQuery/Snowflake + Fivetran + dbt + Looker),验证业务后再考虑自建。过早优化是万恶之源。

Q4: 如何说服业务方投资数据治理?

A: 用钱说话:数据质量问题的直接经济损失(错误决策、合规罚款、重复劳动)远大于治理投入。量化问题是第一步。

性能调优实战技巧

数据工程团队协作模式

角色职责交付物
数据工程师管道开发与维护数据管道、基础设施
数据分析师数据分析与报表SQL查询、BI报表
数据科学家模型与实验ML模型、实验报告
数据产品经理需求与优先级PRD、数据产品规划
数据管家数据质量与治理质量规则、数据字典
💡 职业发展建议:数据工程是一个快速发展的领域。保持学习的关键是:1)理解原理而非仅会工具;2)动手实践而非仅看文档;3)关注社区动态,了解最新趋势;4)建立个人项目,积累实战经验。