我们需要聚合 NDB 数据存储中的数据。具体来说,我们需要创建一个 AggregateStatisticStore 模型来存储每天的 StatisticStore 聚合。StatisticStore 模型存储用户访问的链接的信息,而 AggregateStatisticStore 模型存储每天访问链接的总数和前 N 个最常访问的链接。
我们希望 AggregateStatisticStore 模型能够提供以下功能:
- 能够按日期查询聚合数据。
- 能够按用户查询聚合数据。
- 能够轻松地更新聚合数据。
我们考虑了使用 Task Queue API 来生成 AggregateStatisticStore 模型。但是,我们担心这可能会遇到内存问题,因为 StatisticStore 模型可能会有很多记录。
2、解决方案
为了解决这个问题,我们可以使用以下方法:
-
使用 MapReduce 来生成 AggregateStatisticStore 模型。MapReduce 是一个分布式计算框架,可以将一个大任务分解成多个小任务,然后在多个机器上并行执行这些小任务。这可以有效地减少内存使用量,并提高生成 AggregateStatisticStore 模型的速度。
-
使用 Backend Instances 来生成 AggregateStatisticStore 模型。Backend Instances 是独立于应用程序实例的计算实例。它们可以用于执行长时间运行的任务,而不影响应用程序的性能。
-
将聚合过程移到写入时间。这意味着每当创建一个 StatisticStore 记录时,我们也会更新相应的 AggregateStatisticStore 记录。这可以减少在生成 AggregateStatisticStore 模型时需要处理的数据量。
此外,我们建议使用 IntegerProperty 来存储日期,而不是 DateProperty。IntegerProperty 可以存储整数,而 DateProperty 只能存储日期。这可以简化查询并提高性能。
代码例子:
from google.appengine.ext import ndb
from google.appengine.ext importmapreduce
from google.appengine.api import taskqueue
class StatisticStore(ndb.Model):
user = ndb.KeyProperty(kind='User')
created = ndb.DateTimeProperty(auto_now_add=True)
kind = ndb.StringProperty()
properties = ndb.PickleProperty()
class AggregateStatisticStore(ndb.Model):
user = ndb.KeyProperty(kind='User')
date = ndb.IntegerProperty()
kinds_count = ndb.PickleProperty()
top_links = ndb.PickleProperty()
def generate_aggregate_statistic_store(start_date, end_date):
"""
Generates an AggregateStatisticStore for the given date range.
Args:
start_date: The start date of the date range.
end_date: The end date of the date range.
"""
stats = StatisticStore.query(
StatisticStore.created >= start_date,
StatisticStore.created <= end_date
)
links_dict = {}
# generate links_dict from stats
# keys are from the 'properties' property
aggregated_stat = AggregateStatisticStore(
user=stats[0].user,
date=start_date.date(),
kinds_count=kinds_count,
top_links=links_dict
)
aggregated_stat.put()
def update_aggregate_statistic_store(statistic_store):
"""
Updates the AggregateStatisticStore for the given StatisticStore.
Args:
statistic_store: The StatisticStore to update the aggregate for.
"""
date = statistic_store.created.date()
aggregate_stat = AggregateStatisticStore.query(
AggregateStatisticStore.user == statistic_store.user,
AggregateStatisticStore.date == date
).get()
if aggregate_stat is None:
generate_aggregate_statistic_store(date, date)
return
# update aggregate_stat with statistic_store
aggregate_stat.put()
@ndb.transactional
def record_statistic(user, kind, properties):
"""
Records a statistic for the given user.
Args:
user: The user to record the statistic for.
kind: The kind of statistic to record.
properties: The properties of the statistic.
"""
statistic_store = StatisticStore(
user=user.key,
kind=kind,
properties=properties
)
statistic_store.put()
update_aggregate_statistic_store(statistic_store)
def cleanup_old_statistic_stores():
"""
Deletes StatisticStore records that are older than 30 days.
"""
date = datetime.datetime.now() - timedelta(days=30)
StatisticStore.query(
StatisticStore.created < date
).delete_multi()
taskqueue.add(
url='/worker',
params={
'start_date': '2013-08-22',
'end_date': '2013-08-22'
}
)