通过 Jinja2 获取 ndb entity 是否计入读取计费
支付阿杰
结论
ent.rel.get() 是否计为一次 Datastore 读取,要看它有没有命中 NDB 缓存:
- 如果命中当前请求的 context cache,通常不会访问 Datastore,也不会新增 Datastore entity read。
- 如果没有命中 context cache,NDB 会从 Datastore 读取关联实体,并计入相应的读取用量。
- 某些旧版 NDB 环境还会使用 Memcache。命中 Memcache 同样可以避免 Datastore 读取,具体行为取决于所用的 NDB 实现和配置。
- Jinja2 不会改变计费规则。在模板和 Python 代码中调用同一个
get(),效果没有本质区别。
因此,下面的表达式不只是读取已有属性,它还会在模板渲染期间查找实体:
{% ent.rel.get().property %}
原因
如果 ent.rel 是 ndb.Key,调用:
ent.rel.get()
时,NDB 会先尝试从可用缓存中获取实体。只有缓存未命中时,才会访问 Datastore。
这里包含两步操作:
ent.rel.get():获取关联 entity,可能触发 Datastore 读取。.property:读取已获取 entity 的属性,不会再次访问 Datastore。
在一次请求或同一个 NDB context 中,对同一个 key 多次调用 get(),后续调用通常会命中 context cache。因此,不能只根据模板中表达式出现的次数推算计费次数。批量读取、缓存配置、事务以及 NDB 版本都可能影响实际结果。
推荐做法
不要在 Jinja2 模板中访问数据。先在视图或业务逻辑中获取关联实体,再把渲染所需的数据传给模板:
related_entity = ent.rel.get() if ent.rel else None
return render_template(
"page.html",
ent=ent,
related_entity=related_entity,
)
模板只读取准备好的值:
{% if related_entity %}
{{ related_entity.property }}
{% endif %}
这样处理有几个好处:
- 数据库访问发生在哪里一目了然,也更容易判断读取次数。
- 可以统一处理 key 为空或关联实体不存在的情况。
- 能避免循环渲染造成 N+1 读取。
- Datastore 调用失败时,不会等到模板渲染阶段才报错。
多个关联实体应批量读取
如果页面要展示多条记录的关联实体,不要在循环里逐个调用 get():
{% for ent in entities %}
{{ ent.rel.get().property }}
{% endfor %}
更合适的做法是在 Python 中收集 keys,再批量获取:
related_keys = [ent.rel for ent in entities if ent.rel]
related_entities = ndb.get_multi(related_keys)
related_by_key = {
entity.key: entity
for entity in related_entities
if entity is not None
}
接着组装模板数据:
rows = [
{
"entity": ent,
"related": related_by_key.get(ent.rel),
}
for ent in entities
]
模板只负责展示:
{% for row in rows %}
{% if row.related %}
{{ row.related.property }}
{% endif %}
{% endfor %}
get_multi() 可以减少请求往返,但不代表只计算一次 entity read。对于未命中缓存的实体,通常仍会分别按照相应的实体读取规则计费。
注意事项
- 关联 key 可能是
None,调用get()前应先检查。 - key 存在不代表目标实体仍然存在。实体被删除后,
get()会返回None。 - context cache 通常只在当前 NDB context 中有效,不能把它当作跨请求的持久缓存。
- 查询结果缓存与按 key 获取实体的缓存不是一回事。执行 query 时,即使部分实体已经存在于 context cache,也不能据此认为查询完全不会产生读取。
- 不同运行环境可能使用旧版 App Engine NDB、
google-cloud-ndb或其他兼容实现,其缓存层和配置方式会有差异。判断实际行为时,应以项目使用的库版本、缓存配置和 Google Cloud 账单指标为准。
备注:内容仅供参考。
