查询改写:从语义到存储的优化翻译
在传统系统中,开发者需要知道数据存在哪个表、哪个库才能写查询。但在 coomia-dip 中,用户通过 Ontology 语义查询——他们只关心"业务对象"和"业务关系":
Coomia发布于 2025年12月24日11 分钟阅读
分享本文Twitter / X
查询改写:从语义到存储的优化翻译
“系列:S10 设计模式 · 第 9 篇 | 难度:高级 | 阅读时间:18 分钟
#TL;DR
- 查询改写(Query Rewrite)模式将用户的高层语义查询(基于 Ontology 模型)翻译为底层存储引擎的优化查询(SQL/Iceberg/gRPC),同时注入权限过滤、租户隔离和数据脱敏等横切关注点。
- coomia-dip 的查询改写器是一个多阶段管道:解析 → 语义验证 → 权限注入 → 谓词下推 → 物理查询生成 → 成本优化。
- 结合 Nessie 的 Git-like 分支和 Iceberg 的时间旅行,查询改写器支持跨版本查询和历史快照查询。
#引言:用户不应该知道数据存在哪里
在传统系统中,开发者需要知道数据存在哪个表、哪个库才能写查询。但在 coomia-dip 中,用户通过 Ontology 语义查询——他们只关心"业务对象"和"业务关系":
Python
# 用户的语义查询(不关心物理存储)
query = """
找到所有信用评分低于 600 且最近 30 天有大额交易的客户,
以及他们关联的风控告警
"""
# coomia-dip 需要将此翻译为:
# 1. 从 Iceberg 表 customer_profiles 查询信用评分
# 2. 从 Iceberg 表 transactions 查询近 30 天交易
# 3. 从 gRPC 服务查询风控告警
# 4. 注入租户隔离条件
# 5. 注入数据脱敏规则
# 6. JOIN 结果并排序
查询改写器就是这个翻译层——它让用户用"业务语言"查询,同时生成高效的物理执行计划。
#一、查询改写管道
#1.1 管道架构
Python
from dataclasses import dataclass, field
from typing import Any
from abc import ABC, abstractmethod
@dataclass
class QueryContext:
"""Context for query rewriting."""
tenant_id: str
world_id: str
actor_id: str
actor_roles: list[str]
branch: str = "main" # Nessie branch
timestamp: str | None = None # 时间旅行查询
explain: bool = False # 是否返回执行计划
metadata: dict[str, Any] = field(default_factory=dict)
@dataclass
class SemanticQuery:
"""Parsed semantic query."""
object_types: list[str]
filters: list[dict]
projections: list[str]
joins: list[dict]
order_by: list[dict]
limit: int | None = None
offset: int | None = None
aggregations: list[dict] = field(default_factory=list)
@dataclass
class PhysicalPlan:
"""Physical execution plan."""
steps: list["PhysicalStep"]
estimated_cost: float
estimated_rows: int
@dataclass
class PhysicalStep:
"""A step in the physical execution plan."""
operation: str # scan | filter | join | aggregate | sort
target: str # table name or service endpoint
details: dict[str, Any] = field(default_factory=dict)
class QueryRewritePipeline:
"""Multi-stage query rewrite pipeline."""
def __init__(self, stages: list["RewriteStage"]):
self._stages = stages
async def rewrite(
self,
raw_query: str,
context: QueryContext,
) -> PhysicalPlan:
"""Rewrite a semantic query into a physical execution plan."""
# Stage 1: Parse
semantic = await self._stages[0].process(raw_query, context)
# Stage 2-N: Apply each rewrite stage
for stage in self._stages[1:]:
semantic = await stage.process(semantic, context)
return semantic
class RewriteStage(ABC):
"""Abstract base for query rewrite stages."""
@abstractmethod
async def process(self, input_data: Any, context: QueryContext) -> Any:
"""Process input and return transformed output."""
...
#1.2 解析阶段
Python
class QueryParser(RewriteStage):
"""Parse raw query into semantic representation."""
async def process(self, raw_query: str, context: QueryContext) -> SemanticQuery:
"""Parse the ontology query language (OQL)."""
tokens = self._tokenize(raw_query)
ast = self._build_ast(tokens)
return SemanticQuery(
object_types=self._extract_types(ast),
filters=self._extract_filters(ast),
projections=self._extract_projections(ast),
joins=self._extract_joins(ast),
order_by=self._extract_order_by(ast),
limit=self._extract_limit(ast),
offset=self._extract_offset(ast),
aggregations=self._extract_aggregations(ast),
)
def _tokenize(self, query: str) -> list:
"""Tokenize the query string."""
# OQL 语法解析
pass
def _build_ast(self, tokens: list) -> dict:
"""Build abstract syntax tree."""
pass
#1.3 语义验证阶段
Python
class SemanticValidator(RewriteStage):
"""Validate query against Ontology schema."""
async def process(
self, query: SemanticQuery, context: QueryContext
) -> SemanticQuery:
"""Validate that all referenced types and properties exist."""
for obj_type in query.object_types:
schema = await self._schema_registry.get(obj_type, context.world_id)
if not schema:
raise QueryValidationError(
f"ObjectType '{obj_type}' not found in world '{context.world_id}'"
)
# 验证字段存在
for proj in query.projections:
if proj.startswith(f"{obj_type}."):
field_name = proj.split(".", 1)[1]
if field_name not in schema["properties"]:
raise QueryValidationError(
f"Property '{field_name}' not found in '{obj_type}'"
)
# 验证 JOIN 关系存在
for join in query.joins:
link = await self._schema_registry.get_link_type(
join["from_type"], join["to_type"]
)
if not link:
raise QueryValidationError(
f"No relationship between '{join['from_type']}' and '{join['to_type']}'"
)
return query
#1.4 权限注入阶段
Python
class PermissionInjector(RewriteStage):
"""Inject permission filters into the query."""
async def process(
self, query: SemanticQuery, context: QueryContext
) -> SemanticQuery:
"""Add tenant isolation and RBAC filters."""
# 注入租户隔离
query.filters.append({
"field": "_tenant_id",
"operator": "eq",
"value": context.tenant_id,
"injected": True, # 标记为系统注入的过滤条件
})
# 注入 World 隔离
query.filters.append({
"field": "_world_id",
"operator": "eq",
"value": context.world_id,
"injected": True,
})
# 注入 RBAC 行级过滤
for obj_type in query.object_types:
row_filter = await self._rbac_service.get_row_filter(
context.actor_id, context.actor_roles, obj_type
)
if row_filter:
query.filters.append({
"field": row_filter["field"],
"operator": row_filter["operator"],
"value": row_filter["value"],
"injected": True,
"source": "rbac",
})
# 移除无权限的字段
accessible_fields = await self._rbac_service.get_accessible_fields(
context.actor_id, context.actor_roles, query.object_types
)
query.projections = [
p for p in query.projections
if p in accessible_fields or p == "*"
]
return query
#1.5 物理查询生成
Python
class PhysicalQueryGenerator(RewriteStage):
"""Generate physical execution plan from semantic query."""
async def process(
self, query: SemanticQuery, context: QueryContext
) -> PhysicalPlan:
"""Convert semantic query to physical plan."""
steps = []
for obj_type in query.object_types:
# 确定物理存储位置
storage = await self._storage_registry.get_storage(
obj_type, context.world_id
)
if storage.type == "iceberg":
# 生成 Iceberg/Trino SQL
sql = self._generate_iceberg_sql(query, obj_type, storage, context)
steps.append(PhysicalStep(
operation="scan",
target=storage.table_name,
details={
"sql": sql,
"branch": context.branch,
"timestamp": context.timestamp,
"engine": "trino",
},
))
elif storage.type == "grpc_service":
# 生成 gRPC 调用
steps.append(PhysicalStep(
operation="rpc_call",
target=storage.endpoint,
details={
"service": storage.service_name,
"method": "Query",
"filters": [f for f in query.filters if self._applies_to(f, obj_type)],
"projections": [p for p in query.projections if p.startswith(f"{obj_type}.")],
},
))
# 添加 JOIN 步骤
for join in query.joins:
steps.append(PhysicalStep(
operation="join",
target="in_memory",
details={
"type": join.get("type", "inner"),
"left": join["from_type"],
"right": join["to_type"],
"on": join["on"],
},
))
# 添加聚合步骤
if query.aggregations:
steps.append(PhysicalStep(
operation="aggregate",
target="in_memory",
details={"aggregations": query.aggregations},
))
# 添加排序和分页
if query.order_by:
steps.append(PhysicalStep(
operation="sort",
target="in_memory",
details={"order_by": query.order_by},
))
return PhysicalPlan(
steps=steps,
estimated_cost=self._estimate_cost(steps),
estimated_rows=self._estimate_rows(steps),
)
def _generate_iceberg_sql(
self,
query: SemanticQuery,
obj_type: str,
storage: "StorageInfo",
context: QueryContext,
) -> str:
"""Generate SQL for Iceberg table query."""
projections = [
p.split(".", 1)[1] for p in query.projections
if p.startswith(f"{obj_type}.")
]
proj_clause = ", ".join(projections) if projections else "*"
# 构建 WHERE 子句
where_parts = []
for f in query.filters:
if self._applies_to(f, obj_type):
field = f["field"].replace(f"{obj_type}.", "")
where_parts.append(
f"{field} {f['operator']} {self._format_value(f['value'])}"
)
where_clause = " AND ".join(where_parts) if where_parts else "1=1"
# Nessie 分支和时间旅行
table_ref = f"{storage.catalog}.{storage.schema_name}.{storage.table_name}"
if context.branch != "main":
table_ref += f"@{context.branch}"
if context.timestamp:
table_ref += f" FOR SYSTEM_TIME AS OF TIMESTAMP '{context.timestamp}'"
sql = f"SELECT {proj_clause} FROM {table_ref} WHERE {where_clause}"
if query.order_by:
order_parts = [
f"{o['field'].replace(f'{obj_type}.', '')} {o.get('direction', 'ASC')}"
for o in query.order_by
if o["field"].startswith(f"{obj_type}.")
]
if order_parts:
sql += f" ORDER BY {', '.join(order_parts)}"
if query.limit:
sql += f" LIMIT {query.limit}"
if query.offset:
sql += f" OFFSET {query.offset}"
return sql
#二、优化策略
#2.1 谓词下推
Python
class PredicatePushdown(RewriteStage):
"""Push predicates closer to data sources."""
async def process(
self, plan: PhysicalPlan, context: QueryContext
) -> PhysicalPlan:
"""Move filters to earliest possible execution point."""
join_steps = [s for s in plan.steps if s.operation == "join"]
scan_steps = [s for s in plan.steps if s.operation in ("scan", "rpc_call")]
for join_step in join_steps:
join_filters = join_step.details.get("filters", [])
for f in join_filters:
# 如果过滤条件只涉及一个表,下推到该表的 scan 步骤
target_type = self._get_filter_type(f)
for scan in scan_steps:
if scan.target == target_type:
scan.details.setdefault("filters", []).append(f)
join_filters.remove(f)
break
return plan
#2.2 投影裁剪
Python
class ProjectionPruning(RewriteStage):
"""Remove unnecessary columns from scan operations."""
async def process(
self, plan: PhysicalPlan, context: QueryContext
) -> PhysicalPlan:
"""Only fetch columns that are actually needed."""
# 收集所有需要的字段
needed_fields: set[str] = set()
for step in plan.steps:
if step.operation == "join":
needed_fields.update(step.details.get("on", {}).values())
if step.operation == "sort":
for o in step.details.get("order_by", []):
needed_fields.add(o["field"])
if step.operation == "aggregate":
for a in step.details.get("aggregations", []):
needed_fields.add(a["field"])
# 裁剪 scan 步骤中不需要的列
for step in plan.steps:
if step.operation == "scan":
current_projections = step.details.get("projections", [])
if current_projections and current_projections != ["*"]:
step.details["projections"] = [
p for p in current_projections
if p in needed_fields or p in current_projections
]
return plan
#2.3 数据脱敏注入
Python
class DataMaskingInjector(RewriteStage):
"""Inject data masking rules into query results."""
async def process(
self, plan: PhysicalPlan, context: QueryContext
) -> PhysicalPlan:
"""Add masking transformations to sensitive fields."""
masking_rules = await self._masking_service.get_rules(
context.actor_id, context.actor_roles
)
if masking_rules:
plan.steps.append(PhysicalStep(
operation="transform",
target="in_memory",
details={
"type": "masking",
"rules": masking_rules,
},
))
return plan
#三、时间旅行查询
#3.1 基于 Nessie 分支的历史查询
Python
class TimeTravelQueryRewriter:
"""Rewrite queries for time-travel capabilities."""
async def rewrite_for_time_travel(
self,
query: SemanticQuery,
timestamp: datetime,
context: QueryContext,
) -> SemanticQuery:
"""Rewrite query to access data at a specific point in time."""
context.timestamp = timestamp.isoformat()
# 获取该时间点的 Nessie commit
commit = await self._nessie.get_commit_at(
context.branch, timestamp
)
context.metadata["nessie_commit"] = commit.hash
return query
async def rewrite_for_branch(
self,
query: SemanticQuery,
branch: str,
context: QueryContext,
) -> SemanticQuery:
"""Rewrite query to access data on a specific branch."""
context.branch = branch
return query
async def diff_query(
self,
query: SemanticQuery,
from_ref: str,
to_ref: str,
context: QueryContext,
) -> dict:
"""Query the difference between two points in time."""
results_from = await self._executor.execute(
query, QueryContext(**{**vars(context), "branch": from_ref})
)
results_to = await self._executor.execute(
query, QueryContext(**{**vars(context), "branch": to_ref})
)
return {
"added": [r for r in results_to if r not in results_from],
"removed": [r for r in results_from if r not in results_to],
"modified": self._find_modified(results_from, results_to),
}
#四、查询缓存
#4.1 语义级缓存
Python
class SemanticQueryCache:
"""Cache query results at the semantic level."""
async def get_or_execute(
self,
query: SemanticQuery,
context: QueryContext,
executor: callable,
ttl_seconds: int = 300,
) -> list[dict]:
"""Get cached results or execute and cache."""
cache_key = self._compute_cache_key(query, context)
# 检查缓存
cached = await self._cache.get(cache_key)
if cached is not None:
return cached
# 执行查询
results = await executor(query, context)
# 缓存结果
await self._cache.set(cache_key, results, ttl=ttl_seconds)
return results
def _compute_cache_key(
self, query: SemanticQuery, context: QueryContext
) -> str:
"""Compute a deterministic cache key."""
import hashlib, json
key_data = {
"types": sorted(query.object_types),
"filters": sorted(str(f) for f in query.filters),
"projections": sorted(query.projections),
"tenant": context.tenant_id,
"world": context.world_id,
"branch": context.branch,
"timestamp": context.timestamp,
}
return hashlib.sha256(
json.dumps(key_data, sort_keys=True).encode()
).hexdigest()
#五、EXPLAIN 查询分析
Python
class QueryExplainer:
"""Explain query execution plan for debugging and optimization."""
async def explain(
self, raw_query: str, context: QueryContext
) -> dict:
"""Generate a detailed explanation of the query execution plan."""
context.explain = True
plan = await self._pipeline.rewrite(raw_query, context)
explanation = {
"query": raw_query,
"plan": {
"steps": [
{
"operation": s.operation,
"target": s.target,
"details": s.details,
}
for s in plan.steps
],
"estimated_cost": plan.estimated_cost,
"estimated_rows": plan.estimated_rows,
},
"optimizations_applied": [
"predicate_pushdown",
"projection_pruning",
"data_masking_injection",
],
"injected_filters": [
f for step in plan.steps
for f in step.details.get("filters", [])
if f.get("injected")
],
}
return explanation
#Key Takeaways
- 语义到物理:查询改写器将用户的 Ontology 语义查询翻译为底层存储引擎的优化物理查询
- 多阶段管道:解析 → 验证 → 权限注入 → 谓词下推 → 物理生成 → 成本优化
- 透明安全:租户隔离、RBAC 行级过滤、数据脱敏在查询改写阶段透明注入
- 时间旅行:结合 Nessie 和 Iceberg 支持历史快照查询和跨版本比较
- 性能优化:谓词下推和投影裁剪减少数据传输,语义缓存减少重复计算
- 可观测:EXPLAIN 能力让开发者理解查询执行计划和优化策略
#Next Article
下一篇我们将探讨投递保证模式(Delivery Guarantee)——coomia-dip 如何确保跨 Layer 的消息不丢失、不重复。
S10-10: 投递保证:消息不丢失、不重复
#Tags
#设计模式 #查询改写 #QueryRewrite #谓词下推 #权限注入 #时间旅行 #Nessie #Iceberg #查询优化