在使用 Google App Engine (Python) 读取大量数据时,我们需要将数据重新打包并存储到 App Engine 数据存储中。对于一个大型帐户,可能会包含大约 50,000 个条目。每次从 API 获取一些条目时,我们都会将 500 个条目作为一批存储在临时表中,然后将处理任务发送到队列。为了防止在一个队列中塞入太多任务,我们总共使用了 6 个队列:
count = 0
worker_number = 6
for folder, property in entries:
data[count] = {
# repackaging data here
}
count = (count + 1) % 500
if count == 0:
cache = ClientCache(parent=user_key, data=json.dumps(data))
cache.put()
params = {
'access_token': access_token,
'client_key': client.key.urlsafe(),
'user_key': user_key.urlsafe(),
'cache_key': cache.key.urlsafe(),
}
taskqueue.add(
url=task_url,
params=params,
target='dbworker',
queue_name='worker%d' % worker_number)
worker_number = (worker_number + 1) % 6
所有的任务都在后端运行。这样的结构在大多数情况下都可以正常工作。但是,有时我们会遇到以下错误:
2013-09-19 15:10:07.788
suspended generator transaction(context.py:938) raised TransactionFailedError(The transaction could not be committed. Please try again.)
W 2013-09-19 15:10:07.788
suspended generator internal_tasklet(model.py:3321) raised TransactionFailedError(The transaction could not be committed. Please try again.)
E 2013-09-19 15:10:07.789
The transaction could not be committed. Please try again.
似乎是向数据存储中写入数据过于频繁导致的问题。我们希望找到一种方法来平衡写入数据的速度,让工作者可以顺利运行。
解决方案
1. 使用 ndb.put_multi
批量写入数据
def write_in_batches(data):
"""Write data to the datastore in batches."""
batch_size = 500
for i in range(0, len(data), batch_size):
batch = data[i:i+batch_size]
ndb.put_multi(batch)
2. 使用 ndb.Key.put()
直接写入数据,而不是使用 get_or_insert()
def write_directly(data):
"""Write data to the datastore directly."""
for property in data.values():
key_name = '%s%s' % (property['key1'], property['key2'])
metadata = Metadata(
id=key_name,
parent=user_key,
client_key=client_key,
# ... other info
)
metadata.put()
3. 调整队列配置
- name: worker0
rate: 500/s
bucket_size: 1000
retry_parameters:
task_retry_limit: 3
4. 其他建议
- 考虑使用 memcache 或 Redis 等缓存系统来减少对数据存储的访问。
- 使用异步任务来处理数据,这可以提高应用程序的整体性能。
- 使用 Cloud Datastore 索引来提高查询性能。
- 使用 Cloud Datastore 强一致性来确保数据的一致性。