数据存储区查询超时
Timeouts in datastore queries
我在 java8 运行时环境中使用来自自动缩放应用引擎实例的 objectify v5.1.11。
我有一个 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。此请求超时,因为请求超过Appengine 的 60 年代限制。我在日志中看到以下异常:
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.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());
}
此错误通常是由于 write contention. 如果您有多个事务(例如同时从同一实体组写入和读取某些内容),则此背后的逻辑很简单。
有多种方法可以解决这个问题:
- 查询仅存在 30 秒,但您可以通过将 API 转换为任务队列来延长它。通常处理此类写入争用问题,您应该始终使用持续约 10 分钟的任务队列。
- 如果可能,请缩小您的实体组。
你可以找到更多的方法here.
希望这能回答您的问题!!!
我在 java8 运行时环境中使用来自自动缩放应用引擎实例的 objectify v5.1.11。
我有一个 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。此请求超时,因为请求超过Appengine 的 60 年代限制。我在日志中看到以下异常:
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.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());
}
此错误通常是由于 write contention. 如果您有多个事务(例如同时从同一实体组写入和读取某些内容),则此背后的逻辑很简单。
有多种方法可以解决这个问题:
- 查询仅存在 30 秒,但您可以通过将 API 转换为任务队列来延长它。通常处理此类写入争用问题,您应该始终使用持续约 10 分钟的任务队列。
- 如果可能,请缩小您的实体组。
你可以找到更多的方法here.
希望这能回答您的问题!!!