我正在尝试使用GAE数据存储区来练习查询和计费机制。
我已经阅读了有关GAE的Oreilly书籍,并观看了有关数据存储的Google视频。我的问题是,最佳实践方法通常涉及更多读取而不是写入数据存储区。
我构建了一个超级简单的应用程序:
在这种情况下,由于用户可以选择他想要的任意数量的链接,只要他想要,我的应用程序写入数据存储区,远远超过读取次数(写入 - 当用户选择另一个链接时;读取 - 当用户打开网页以查看他的“链接提要”)
问题1: 我可以想到(至少)两个选项如何处理这个应用程序的数据:
选项A: - 使用用户详细信息,注册等维护每个用户的实体 - 维护每个用户的另一个实体,该实体持有他最近的10个选定链接,在他要求后将呈现给用户的网页
选项B: - 维护每个网址链接的实体 - 这意味着所有用户的所有网址都将存储为同一个对象 - 维护每个用户的实体细节(与选项A中相同),但在网址的大表中添加对用户网址的引用
什么是更好的方法?
问题2: 如果我想计算到今天所选择的网址总数,或者用户选择的每日网址数量,或任何其他计数 - 我应该将其与我的SDK工具一起使用,还是应该在我上面描述的实体中插入计数器? (我想尽可能多地减少数据存储区写入量)
编辑(回答@Elad的评论): 假设我只想保存每个用户的10个最后网址。其余的我想摆脱(所以不要用不必要的数据来填补我的数据库)。
编辑2:添加代码后 所以我尝试使用以下代码(尝试第一个Elad的方法):
这是我的班级:
class UserChannel(db.Model):
currentUser = db.UserProperty()
userCount = db.IntegerProperty(default=0)
currentList = db.StringListProperty() #holds the last 20-30 urls
然后我序列化了url&元数据到JSON字符串,用户从第一页POST。 这是POST的处理方式:
def post(self):
user = users.get_current_user()
if user:
logging messages for debugging
self.response.headers['Content-Type'] = 'text/html'
#self.response.out.write('<p>the user_id is: %s</p>' % user.user_id())
updating the new item that user adds
current_user = UserChannel.get_by_key_name(user.nickname())
dataJson = self.request.get('dataJson')
#self.response.out.write('<p>the dataJson is: %s</p>' % dataJson)
current_user.currentPlaylist.append(dataJson)
sizePlaylist= len(current_user.currentPlaylist)
self.response.out.write('<p>size of currentplaylist is: %s</p>' % sizePlaylist)
#whenever the list gets to 30 I cut it to be 20 long
if sizePlaylist > 30:
for i in range (0,9):
current_user.currentPlaylist.pop(i)
current_user.userCount +=1
current_user.put()
Updater().send_update(dataJson)
else:
self.response.headers['Content-Type'] = 'text/html'
self.response.out.write('user_not_logged_in')
其中Updater是我使用Feed更新Channel-API网页的方法。
现在,一切正常,我可以看到每个用户都有一个包含20-30个链接的ListProperty(当它达到30时,我用pop()将其降低到20),但是!价格相当高...... 每个POST就像这里的那个需要〜200ms,121 cpu_ms,cpm_usd = 0.003588。考虑到我所做的就是将一个字符串保存到列表中,这是非常昂贵的... 我认为问题可能是实体在大型ListProperty中变大了吗?
答案 0 :(得分:1)
回答1
将链接存储为单独的实体。还为每个用户存储一个实体,其中ListProperty具有最近20个链接的键。当用户选择更多链接时,您只需更新ListProperty键。 ListProperty维护订单,因此只要您遵循FIFO插入订单,就不必担心所选链接的时间顺序。
当您想要显示用户选择的链接(第2页)时,您可以通过一次获取(键)在一次通话中获取所有用户的链接。
回答2
绝对保持计数器,随着实体数量的增加,计数记录的复杂性将继续增加,但对于计数器,性能将保持不变。
答案 1 :(得分:1)
首先,你担心大量写入GAE数据存储区是正确的 - 我自己的经验是它们与读取相比非常昂贵。例如,我的应用程序除了在单个模型表中插入记录之外什么都没做,所以每天只需要几十万次写入,就会耗尽免费配额。因此,处理写入有效地直接转化为您的底线。
第一个问题
我不会将链接存储为单独的实体。数据存储区不是RDBMS,因此标准规范化实践不一定适用。对于每个用户实体,使用ListProperty存储最新的URL及其元数据(您可以将所有内容序列化为字符串)。
另一个优化想法:如果您的用户通常会在短时间内添加多个链接,您可以尝试批量编写,而不是单独编写。使用memcache存储新添加的用户URL,并使用Task Queue定期将该瞬态数据写入持久数据存储区。我不确定使用任务的资源成本是多少 - 你必须检查。 Here's a good article阅读有关该主题的内容。
第二个问题
使用计数器。请记住,它们在分布式环境中并不是微不足道的,所以请阅读 - 有关于此主题的许多GAE文章,食谱和博客文章 - 只是google appengine counters。在这里,使用memcache应该是一个很好的选择,以减少数据存储区写入的总数。