数据存储区查询超时

时间:2019-02-22 10:01:18

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

我正在从Java8运行时环境中自动缩放的App Engine实例中使用objectify v5.1.11。

我有一个API,物联网设备会定期调用该API来上传统计信息。在此API中,我将一个实体插入数据存储区以存储统计信息。该实体使用自动生成的数据存储区ID。实体定义如下:

@Entity(name = "Stats")
public class StatsEntity {
    @Id
    private Long statisticsId;

    @Index
    private Long deviceId;

    @Index
    private String statsKey;

    @Index
    private Date creationTime;
}

但是我需要在插入实体之前检查重复项。我切换到自定义生成的(字符串)ID。我想出了一种机制,将设备提供的deviceId附加到statsKey(对于设备内的每个统计信息都是唯一的)字符串以生成ID。

如果我使用查询来检查实体是否已经存在,这将避免使用eventual consistency behaviour。由于按ID取得是高度一致的,因此我可以使用它来检查重复项。

还有另一个API可以获取设备上传的统计信息。在此API中,我通过对deviceId进行过滤并按creationTime的降序排列(最新的优先顺序)来列出实体,页面大小为100。此请求超时,因为该请求超过了60s的限制appengine。我在日志中看到以下异常:

Task was cancelled.
java.util.concurrent.CancellationException: Task was cancelled.
    at com.google.common.util.concurrent.AbstractFuture.cancellationExceptionWithCause(AbstractFuture.java:1355)
    at com.google.common.util.concurrent.AbstractFuture.getDoneValue(AbstractFuture.java:555)
    at com.google.common.util.concurrent.AbstractFuture.get(AbstractFuture.java:436)
    at com.google.common.util.concurrent.AbstractFuture$TrustedFuture.get(AbstractFuture.java:99)
    at com.google.appengine.tools.development.TimedFuture.get(TimedFuture.java:42)
    at com.google.common.util.concurrent.ForwardingFuture.get(ForwardingFuture.java:62)
    at com.google.appengine.api.utils.FutureWrapper.get(FutureWrapper.java:93)
    at com.google.appengine.api.datastore.FutureHelper.getInternal(FutureHelper.java:69)
    at com.google.appengine.api.datastore.FutureHelper.quietGet(FutureHelper.java:33)
    at com.google.appengine.api.datastore.BaseQueryResultsSource.loadMoreEntities(BaseQueryResultsSource.java:243)
    at com.google.appengine.api.datastore.BaseQueryResultsSource.loadMoreEntities(BaseQueryResultsSource.java:180)
    at com.google.appengine.api.datastore.QueryResultIteratorImpl.ensureLoaded(QueryResultIteratorImpl.java:173)
    at com.google.appengine.api.datastore.QueryResultIteratorImpl.hasNext(QueryResultIteratorImpl.java:70)
    at com.googlecode.objectify.impl.KeysOnlyIterator.hasNext(KeysOnlyIterator.java:29)
    at com.google.common.collect.Iterators$5.hasNext(Iterators.java:580)
    at com.google.common.collect.TransformedIterator.hasNext(TransformedIterator.java:42)
    at com.googlecode.objectify.impl.ChunkIterator.hasNext(ChunkIterator.java:39)
    at com.google.common.collect.MultitransformedIterator.hasNext(MultitransformedIterator.java:50)
    at com.google.common.collect.MultitransformedIterator.hasNext(MultitransformedIterator.java:50)
    at com.google.common.collect.Iterators$PeekingImpl.hasNext(Iterators.java:1105)
    at com.googlecode.objectify.impl.ChunkingIterator.hasNext(ChunkingIterator.java:51)
    at com.ittiam.cvml.dao.repository.PerformanceStatsRepositoryImpl.list(PerformanceStatsRepositoryImpl.java:154)
    at com.ittiam.cvml.service.PerformanceStatsServiceImpl.listPerformanceStats(PerformanceStatsServiceImpl.java:227)

设备提供的statsKey是基于时间的,因此单调增加(逐步增加15分钟),这对于此link来说是不好的。 但是我的访问量不足以保证这种行为。每个设备每15分钟发出2到3个请求,大约有300个设备。 当我尝试列出自从切换到自定义ID以来未发出任何请求的设备的实体时,仍然会看到此问题。

编辑

我列出实体的代码如下:

Query<StatsEntity> query = ofy().load().type(StatsEntity.class);

List<StatsEntity> entityList =
        new ArrayList<StatsEntity>();

query = query.filter("deviceId", deviceId);

query = query.order("-creationTime");

query = query.limit(100);

QueryResultIterator<StatsEntity> iterator = query.iterator();

while (iterator.hasNext()) {
    entityList.add(iterator.next());
}

1 个答案:

答案 0 :(得分:1)

通常会由于write contention.而发生此错误,如果您要进行多个事务(例如,同时从同一实体组中写入和读取某些内容),则此逻辑很简单。
解决此问题的方法有多种:

  • 查询仅保留30秒,但您可以通过将API转换为任务队列来扩展查询。通常,在处理此类写争用问题时,您应始终使用持续约10分钟的任务队列。
  • 如果可能,请缩小您的实体组。

您可以找到更多方法 here.

希望这能回答您的问题!