PAYATHON 2026

Google App Engine 使用 RemoteApiServlet 批量加载的计费问题

支付阿杰

结论

在旧版 Google App Engine 的计费模型中,这种情况可能是正常的。通过 RemoteApiServlet 发起并以应用管理员身份认证的 Remote API 请求,其 CPU 使用量可能不会计入最终账单,但仍会占用应用的资源配额。

因此,以下两种现象可以同时出现:

  • Billing history 中的 CPU 费用为 $0.0。
  • CPU quota 达到上限后,Remote API 返回 HTTP 503。

原因在于,资源配额统计和最终计费是两套机制。CPU 不收费,并不代表它不受配额限制。

为什么控制台显示约 70 CPU hours

Bulkloader 不会绕过应用直接写入 Datastore。它通过 RemoteApiServlet 向应用发送请求,再由应用执行 Datastore 操作。因此,控制台仍会记录这些请求消耗的 CPU、Datastore 操作和带宽等资源。

资源使用页面中的 CPU hours 主要用于配额统计和运行监控,不能直接按照“显示的 CPU hours × 单价”计算最终账单。实际费用还取决于请求类型、管理员认证、免费额度,以及当时生效的 App Engine 计费规则。

Billing history 也可能有结算延迟。如果刚完成上传就查看账单,可以等计费周期更新后再确认。如果经过数个结算周期后 CPU 费用仍为零,而且所有请求确实都来自经过管理员认证的 Remote API,通常说明这些请求采用了 Remote API 的特殊计费方式。

为什么达到预算后仍然出现 HTTP 503

在当时的 App Engine 模型中,max. daily budget 不只是信用卡扣款上限,也会影响应用当天可使用的付费资源配额。

系统判断 CPU 配额耗尽后,会在资源准入阶段拒绝请求。这个阶段早于最终账单结算,因此可能出现以下过程:

  1. Bulkloader 请求消耗 CPU。
  2. CPU 使用量计入应用的每日配额。
  3. 配额达到免费额度和 daily budget 共同决定的上限。
  4. 后续请求被拒绝,并返回 HTTP 503。
  5. 结算系统随后根据 Remote API 请求的计费属性计算费用。
  6. CPU 最终可能不收费,但之前触发的配额限制不会随之撤销。

也就是说,HTTP 503 表示当前无法继续分配资源,并不表示已经产生了等额账单。

处理方法

先在管理控制台中确认 503 是否确实由 CPU quota 引起,同时排除 Datastore 配额、请求超时和瞬时服务故障。重点检查:

  • CPU quota 是否达到 100%。
  • Datastore 写入相关配额是否已经耗尽。
  • 503 是否只在当天剩余时间出现,并在配额重置后恢复。
  • 应用日志中是否出现 OverQuotaError、deadline exceeded 或 Datastore 错误。
  • Billing history 是否已经完成延迟结算。

如果确认是 CPU quota,可以采取以下措施:

  • 在能够承担相应费用风险的前提下提高 daily budget。
  • 减少每个批次的实体数量,避免资源在短时间内集中消耗。
  • 降低 Bulkloader 的并发度。
  • 遇到 HTTP 503 时使用指数退避重试。
  • 将超大数据集拆分到多个配额周期中导入。
  • 避免重复写入同一实体或执行不必要的查询。

简单的指数退避逻辑如下:

import random
import time

def retry_with_backoff(operation, max_attempts=8):
    delay = 1.0

    for attempt in range(max_attempts):
        try:
            return operation()
        except ServiceUnavailableError:
            if attempt == max_attempts - 1:
                raise

            time.sleep(delay + random.uniform(0, delay * 0.25))
            delay = min(delay * 2, 60.0)

如果 503 是因为每日配额已经完全耗尽,持续重试无法立即解决问题。此时只能等待配额重置,或提高允许使用的资源额度。指数退避更适合处理短暂过载和瞬时 503。

RemoteApiServlet 配置注意事项

RemoteApiServlet 应仅允许管理员访问。例如,Java 应用常见的 web.xml 配置如下:

<servlet>
    <servlet-name>remoteapi</servlet-name>
    <servlet-class>
        com.google.apphosting.utils.remoteapi.RemoteApiServlet
    </servlet-class>
</servlet>

<servlet-mapping>
    <servlet-name>remoteapi</servlet-name>
    <url-pattern>/remote_api</url-pattern>
</servlet-mapping>

<security-constraint>
    <web-resource-collection>
        <web-resource-name>Remote API</web-resource-name>
        <url-pattern>/remote_api</url-pattern>
    </web-resource-collection>
    <auth-constraint>
        <role-name>admin</role-name>
    </auth-constraint>
</security-constraint>

不要为了方便导入而公开 /remote_api。Remote API 通常可以直接读写生产环境中的 Datastore,配置不当会带来严重的数据安全风险。

需要注意的版本差异

这个问题对应的是 App Engine 早期以 CPU hours 和 daily budget 为核心的计费方式。此后,Google 多次调整 App Engine 的运行时、配额、实例计费和 Datastore 计费规则。因此,上述行为不能直接套用到当前的 App Engine、Cloud Datastore 或 Firestore。

对于仍在运行的旧应用,应以项目当时使用的运行时、账单明细和配额页面为准。判断时需要分别核对:

  • 资源使用页面记录了哪些消耗。
  • 配额页面因为什么原因拒绝请求。
  • 最终账单实际结算了哪些费用。

这三个页面的用途不同,显示的数字不一定能一一对应。

备注:内容仅供参考。