把日志数据以parquet格式存放到s3对象存储,查询使用polars,在如下场景可以取代clickhouse:
- 成本敏感且数据量巨大:你需要以最低的成本存储和查询TB甚至PB级的日志历史数据,进行审计、合规性检查或全量趋势分析
- 分析模式灵活多变:你的查询需求不固定,经常需要执行一次性的、探索性的临时分析,Polars强大的表达能力和灵活的Schema非常适合此场景
- 追求极简运维:你希望避免维护一个分布式数据库集群的复杂性,更倾向于使用无状态的计算服务搭配对象存储
如果决定采用 Polars + S3 的方案,以下几点建议可以帮助你获得最佳体验:
- 数据分区是关键:按照时间(如year/month/day)甚至业务标签(如region/service)对S3上的Parquet文件进行分区,可以极大提升查询效率,Polars可以借助这些分区信息快速跳过无关数据
- 启用Polars的流式引擎:对于超大规模数据的聚合查询,在collect时使用
engine='streaming'参数,可以有效突破内存限制,实现平滑处理 - 优化Parquet文件:避免产生大量小文件。在数据写入阶段,将小文件合并成大小适中(如100MB-1GB)的Parquet文件,这不仅能提升查询性能,也能节省S3存储成本
parquet文件
存算分离: 计算用polars,存储用s3,文件格式用parquet。
parquet文件就是用来存冷数据的,热数据请放到postgresql里面,并定期将postgresql中的冷数据转成parquet文件后迁移到s3,或者将s3中的热数据转到postgresql中来。
parquet + polars,全文检索的时候,比较慢怎么办,又不想使用elasticSearch这类增加数据冗余的方案
class ProductionParquetSearcher:
"""生产环境Parquet全文检索优化方案"""
def __init__(self, data_path: str):
self.data_path = Path(data_path)
# 1. 元数据缓存
self.metadata_cache = self.load_metadata()
# 2. 布隆过滤器索引
self.bloom_index = self.load_bloom_index()
# 3. 热点数据缓存
self.hot_cache = TTLCache(maxsize=1000, ttl=300)
def search(self, query: str, use_index: bool = True):
# 第一步:快速过滤
if use_index and query in self.bloom_index:
# 第二步:索引定位
candidate_files = self.locate_via_index(query)
# 第三步:并行精确搜索
with ThreadPoolExecutor() as executor:
results = list(executor.map(
lambda f: self.exact_search_in_file(f, query),
candidate_files
))
return pl.concat([r for r in results if r is not None])
# 后备:全扫描
return self.full_scan(query)
def exact_search_in_file(self, file_path: Path, query: str):
"""单个文件的精确搜索(流式)"""
return (
pl.scan_parquet(file_path)
.filter(pl.col("content").str.contains(query))
.collect(streaming=True) # 流式避免内存问题
)
关键建议
- 先测量再优化:用 pl.Config监控查询性能
- 分层存储:热数据放内存,温数据建索引,冷数据全扫描
- 不要过早优化:先确认瓶颈在哪里
本文发表于 0001-01-01,最后修改于 0001-01-01。
本站永久域名「 jiavvc.top 」,也可搜索「 极客油画 」找到我。

