【问题标题】:Is there a limit on the number of entities you can query from the GAE datastore?您可以从 GAE 数据存储中查询的实体数量是否有限制?
【发布时间】:2015-07-06 18:16:31
【问题描述】:

我的 GCM 端点源自 /github.com/GoogleCloudPlatform/gradle-appengine-templates/tree/master/GcmEndpoints/root/src/main 中的代码。每个 Android 客户端设备 向端点注册。可以使用此代码向前 10 个注册的设备发送一条消息:

@Api(name = "messaging", version = "v1", namespace = @ApiNamespace(ownerDomain = "${endpointOwnerDomain}", ownerName = "${endpointOwnerDomain}", packagePath="${endpointPackagePath}"))
public class MessagingEndpoint {
private static final Logger log = Logger.getLogger(MessagingEndpoint.class.getName());

/** Api Keys can be obtained from the google cloud console */
private static final String API_KEY = System.getProperty("gcm.api.key");

/**
 * Send to the first 10 devices (You can modify this to send to any number of devices or a specific device)
 *
 * @param message The message to send
 */
public void sendMessage(@Named("message") String message) throws IOException {
    if(message == null || message.trim().length() == 0) {
        log.warning("Not sending message because it is empty");
        return;
    }
    // crop longer messages
    if (message.length() > 1000) {
        message = message.substring(0, 1000) + "[...]";
    }
    Sender sender = new Sender(API_KEY);
    Message msg = new Message.Builder().addData("message", message).build();
    List<RegistrationRecord> records = ofy().load().type(RegistrationRecord.class).limit(10).list();
    for(RegistrationRecord record : records) {
        Result result = sender.send(msg, record.getRegId(), 5);
        if (result.getMessageId() != null) {
            log.info("Message sent to " + record.getRegId());
            String canonicalRegId = result.getCanonicalRegistrationId();
            if (canonicalRegId != null) {
                // if the regId changed, we have to update the datastore
                log.info("Registration Id changed for " + record.getRegId() + " updating to " + canonicalRegId);
                record.setRegId(canonicalRegId);
                ofy().save().entity(record).now();
            }
        } else {
            String error = result.getErrorCodeName();
            if (error.equals(Constants.ERROR_NOT_REGISTERED)) {
                log.warning("Registration Id " + record.getRegId() + " no longer registered with GCM, removing from datastore");
                // if the device is no longer registered with Gcm, remove it from the datastore
                ofy().delete().entity(record).now();
            }
            else {
                log.warning("Error when sending message : " + error);
            }
        }
    }
}

}

上面的代码发送到前 10 个注册的设备。我想发送给所有注册的客户。根据http://objectify-appengine.googlecode.com/svn/branches/allow-parent-filtering/javadoc/com/googlecode/objectify/cmd/Query.html#limit(int) 设置limit(0) 来实现这一点。但我不相信由于内存限制或执行查询所需的时间,大量注册客户不会出现问题。 https://code.google.com/p/objectify-appengine/source/browse/Queries.wiki?repo=wiki 声明“游标让您在查询结果集中获取一个“检查点”,将检查点存储在其他地方,然后从您离开的地方继续。这通常与任务队列 API 结合使用以遍历大型数据集无法在单个请求的 60s 限制内处理”。

注意关于单个请求的 60s 限制的评论。

所以我的问题 - 如果我修改 /github.com/GoogleCloudPlatform/gradle-appengine-templates/tree/master/GcmEndpoints/root/src/main 中的示例代码以通过替换 limit( 10) 使用limit(0),这对于大量对象是否会失败?如果它会失败,大概有多少个对象?

【问题讨论】:

    标签: google-app-engine google-cloud-datastore objectify


    【解决方案1】:

    如果您尝试检索太多实体,您的查询将会超时。您需要在循环中使用游标。

    没有人能说在此超时之前可以检索到多少实体 - 这取决于您的实体的大小、查询的复杂性,以及最重要的是,您的循环中还会发生什么。例如,在您的情况下,您可以通过创建任务而不是在循环本身内构建和发送消息来显着加快循环(从而在超时之前检索更多实体)。

    请注意,默认情况下,查询以 20 个块为单位返回实体 - 如果您有大量实体,则需要增加块大小。

    【讨论】:

      【解决方案2】:

      这是一个糟糕的模式,即使使用光标也是如此。至少,您将达到单个请求的硬性 60 秒限制。由于您正在对RegistrationRecord 进行更新,因此您需要一个事务,这将进一步减慢进程。

      这正是任务队列的用途。最好的方法是在两个任务中完成:

      1. 您的 api 端点将“向所有人发送消息”排入队列并立即返回。
      2. 第一个任务是“映射器”,它使用仅键查询迭代RegistrationRecords。对于每个键,为“向该记录发送 X 消息”排队一个“reducer”任务。
      3. reducer 任务发送消息并(在事务中)执行您的记录更新。

      使用 Deferred 这实际上根本不需要太多代码。

      第一个任务立即释放您的客户端,并为您提供 10m 的时间来迭代 RegistrationRecord 键,而不是正常请求的 60 秒限制。如果你有分块权和批量队列提交,你应该能够每秒生成数千个 reducer 任务。

      这将毫不费力地扩展到数十万用户,并可能让您成为数百万用户。如果您需要更高的扩展性,您可以应用 map/reduce 方法来并行化映射。那么这只是一个问题,即你想在这个问题上抛出多少实例。

      在过去一次发送数百万条苹果推送通知时,我曾使用过这种方法取得了很大的效果。任务队列是你的朋友,请大量使用它。

      【讨论】:

      • 感谢stickfigure。假设我们有一个名为 reduceQueue 的“reducer”任务队列。您提到“映射器任务”应该进行分块和批处理队列提交。它会使用一个调用 reducerQueue.fetchStatistics.getNumTasks() 的循环来限制将“reducer”任务排入队列的速率吗?或者“映射器”任务是否有更好的方法来获取反馈以用于其分块和批处理算法?如果你有一个代码示例的链接,那就太好了!
      • 不幸的是,我没有任何公开的代码示例,但这真的很简单——你不需要做任何特别的 WRT 反馈。映射器的字面意思是“迭代键,为每个键排队一个reducertask”。从那个简单的算法开始。为了使其快速起泡,请使用 chunkSize 1000 进行查询,并在每个 API 调用中将 1000 个任务排入队列。
      猜你喜欢
      • 2012-12-30
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-10-27
      • 1970-01-01
      • 2013-06-21
      • 2016-05-04
      相关资源
      最近更新 更多