【问题标题】:How to reduce the number of reads and writes to Google App Engine datastore如何减少对 Google App Engine 数据存储区的读写次数
【发布时间】:2014-10-23 14:30:54
【问题描述】:

如果您能在我非常简单的“应用程序”中帮助我尽量减少读写次数,我将不胜感激。我是新手,所以请温柔:)

我的应用程序基本上是一个测验,有 ~120 个问题分成 ~20 页,每页 ~6 个问题全部写入同一个“表格”。根据我的学生对某些问题的回答方式,他们将被送回之前的问题,或者后续问题的措辞可能会发生变化。在测验结束时,应用程序会根据正确回答问题的数量计算每个学生的分数。

当超过 40 人同时或同一天参加测验时,问题就开始了。我收到有关超出配额以及对数据存储的读写次数的错误消息。

如果我理解正确,我的数据库太大导致搜索爆炸:https://cloud.google.com/appengine/articles/

将数据库拆分为 6 个问题的 20 个数据库是否可以解决问题?

我必须在星期三之前让它工作,我自己没有办法测试这个解决方案。

谢谢!

这是我创建数据库的方法:

class ExpQuestionnaires(db.Model):
    created = db.DateTimeProperty(auto_now_add = True)
    username= db.StringProperty(required=False)
    nmi1 = db.StringProperty(required=False)
    nmi2 = db.StringProperty(required=False)
    nmi3 = db.StringProperty(required=False)
    nmi_happy = db.StringProperty(required=False)
    nmi_pleasant = db.StringProperty(required=False)
    nmi_good = db.StringProperty(required=False)
    nmi_interested = db.StringProperty(required=False)    
    ai_content = db.TextProperty(required=False)
    aic = db.TextProperty(required=False)
    dm1 = db.StringProperty(required=False)
    dm2 = db.StringProperty(required=False)
    dm3 = db.StringProperty(required=False)
    dm4 = db.StringProperty(required=False)
    dm5 = db.StringProperty(required=False)
    dm6 = db.StringProperty(required=False)
    dm7 = db.StringProperty(required=False)
    dm8 = db.StringProperty(required=False)
    dm9 = db.StringProperty(required=False)
    dm10 = db.StringProperty(required=False)
    dm11 = db.StringProperty(required=False)
    dm12 = db.StringProperty(required=False)
    dm13 = db.StringProperty(required=False)
    dm14 = db.StringProperty(required=False)
    dm15 = db.StringProperty(required=False)
    dm16 = db.StringProperty(required=False)
    dm17 = db.StringProperty(required=False)
    dm18 = db.StringProperty(required=False)
    dm19 = db.StringProperty(required=False)
    dm20 = db.StringProperty(required=False)
    dm21 = db.StringProperty(required=False)
    dm22 = db.StringProperty(required=False)
    dm23 = db.StringProperty(required=False)
    dm24 = db.StringProperty(required=False)
    dm25 = db.StringProperty(required=False)
    dm26 = db.StringProperty(required=False)
    dm27 = db.StringProperty(required=False)
    dm28 = db.StringProperty(required=False)
    dm29 = db.StringProperty(required=False)
    dm30 = db.StringProperty(required=False)
    dm31 = db.StringProperty(required=False)
    dm32 = db.StringProperty(required=False)
    dm33 = db.StringProperty(required=False)
    dm34 = db.StringProperty(required=False)
    dm35 = db.StringProperty(required=False)
    dm36 = db.StringProperty(required=False)
    soep1 = db.StringProperty(required=False)
    soep2 = db.StringProperty(required=False)
    soep3 = db.StringProperty(required=False)
    soep4 = db.StringProperty(required=False)
    RteM1 = db.StringProperty(required=False)
    RteM2 = db.StringProperty(required=False)
    RteM3 = db.StringProperty(required=False)
    RteM4 = db.StringProperty(required=False)
    loc1 = db.StringProperty(required=False)
    loc2 = db.StringProperty(required=False)
    loc3 = db.StringProperty(required=False)
    loc4 = db.StringProperty(required=False)
    loc5 = db.StringProperty(required=False)
    loc6 = db.StringProperty(required=False)
    loc7 = db.StringProperty(required=False)
    loc8 = db.StringProperty(required=False)
    loc9 = db.StringProperty(required=False)
    loc10 = db.StringProperty(required=False)
    loc11 = db.StringProperty(required=False)
    loc12 = db.StringProperty(required=False)
    loc13 = db.StringProperty(required=False)
    loc14 = db.StringProperty(required=False)
    loc15 = db.StringProperty(required=False)
    loc16 = db.StringProperty(required=False)
    loc17 = db.StringProperty(required=False)
    loc18 = db.StringProperty(required=False)
    loc19 = db.StringProperty(required=False)
    loc20 = db.StringProperty(required=False)
    loc21 = db.StringProperty(required=False)
    loc22 = db.StringProperty(required=False)
    loc23 = db.StringProperty(required=False)
    loc24 = db.StringProperty(required=False)
    loc25 = db.StringProperty(required=False)
    loc26 = db.StringProperty(required=False)
    loc27 = db.StringProperty(required=False)
    loc28 = db.StringProperty(required=False)
    loc29 = db.StringProperty(required=False)
    bis1 = db.StringProperty(required=False)
    bis2 = db.StringProperty(required=False)
    bis3 = db.StringProperty(required=False)
    bis4 = db.StringProperty(required=False)
    bis5 = db.StringProperty(required=False)
    bis6 = db.StringProperty(required=False)
    bis7 = db.StringProperty(required=False)
    bas1 = db.StringProperty(required=False)
    bas2 = db.StringProperty(required=False)
    bas3 = db.StringProperty(required=False)
    bas4 = db.StringProperty(required=False)
    rei1 = db.StringProperty(required=False)
    rei2 = db.StringProperty(required=False)
    rei3 = db.StringProperty(required=False)
    rei4 = db.StringProperty(required=False)
    rei5 = db.StringProperty(required=False)
    rei6 = db.StringProperty(required=False)
    rei7 = db.StringProperty(required=False)
    rei8 = db.StringProperty(required=False)
    rei9 = db.StringProperty(required=False)
    rei10 = db.StringProperty(required=False)
    imp1 = db.StringProperty(required=False)
    imp2 = db.StringProperty(required=False)
    imp3 = db.StringProperty(required=False)
    imp4 = db.StringProperty(required=False)
    imp5 = db.StringProperty(required=False)
    imp6 = db.StringProperty(required=False)
    imp7 = db.StringProperty(required=False)
    imp8 = db.StringProperty(required=False)
    imp9 = db.StringProperty(required=False)
    imp10 = db.StringProperty(required=False)
    imp11 = db.StringProperty(required=False)
    imp12 = db.StringProperty(required=False)
    demo1 = db.StringProperty(required=False)
    demo2 = db.StringProperty(required=False)
    demo3 = db.StringProperty(required=False)
    demo4 = db.StringProperty(required=False)
    demo5 = db.StringProperty(required=False)
    demo6 = db.StringProperty(required=False)
    demo7 = db.StringProperty(required=False)
    demo8 = db.StringProperty(required=False)
    demo9 = db.StringProperty(required=False)
    demo10 = db.StringProperty(required=False)
    pQuizAttempts = db.IntegerProperty(required=False)
    eQuizAttempts = db.IntegerProperty(required=False)

以下是我如何保存问题 dm1、dm2、dm3、dm4、dm5、dm6 的答案:

class RteDM1(Handler):
    def get(self):
        self.render("RteDM1.html")

    def post(self):
        dm1 = self.request.get("DM1")
        dm2 = self.request.get("DM2")
        dm3 = self.request.get("DM3")
        dm4 = self.request.get("DM4")
        dm5 = self.request.get("DM5")
        dm6 = self.request.get("DM6")
        username = self.request.cookies.get('username', 0)

        dmdata = ExpQuestionnaires(username = username, dm1 = dm1, dm2 = dm2, dm3 = dm3, dm4 = dm4, dm5 = dm5, dm6 = dm6)
        dmdata.put()

        next = self.request.get("next")
        if next == "yes":
            self.redirect('/RteDM2')

我不依赖于将数据保存到 cookie 以使作弊变得更加困难(长话短说),并且在我的应用程序或学生的计算机崩溃时至少获得部分答案。

这是一个包含这些问题的网页:

<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<style>
        body {width:900px; margin:20px auto 10px;background-color:#F0F8FF; font-size:16px; font-family:Arial;line-height:1.5em; text-align:justify;}
    .screen { text-align:center; font-style: italic; }
    .bold { text-align:center; font-weight: bold; }
    .normal { text-align:center; font-weight: normal; line-height:30px}
    a.next { text-align:center; padding:2px 5px; margin:0 47%; border:3px outset #ddd; background-color:#DDD;text-decoration: none;}
    li {padding:5px 0}
    input {margin:7px 10px 5px 15px}
    input[type="submit"]{margin:15px 47%;}
    .button {margin:15px 47%; width:100px}
    .width1 {width:7% !important;text-align:center;padding:10px 2px;font-size:12px; }
    .width2 {width:50% !important;text-align:left;padding:2px;}
    td {margin:0 5px 0 0}
    table {font-size:12px;  border-collapse:collapse;width:100%;}
    table, th, td {border: 1px solid black;padding:3px 10px;text-align:center;}
   </style>
</head>

<body>
    <p style="text-align:right;font-size:12px">3/17</p>
<p>Instructions</p>

<form method="post">
<p></p><br>
<table>
        <tr>
        <td class="width2"></td>
        <td class="width1">1 <br/>very unlikely</td>
        <td class="width1">2 <br/>unlikely</td>
        <td class="width1">3 <br/>possibly unlikely</td>
        <td class="width1">4 <br/>hard to tell</td>
        <td class="width1">5 <br/>possibly likely</td>
        <td class="width1">6 <br/>likely</td>
        <td class="width1">7 <br/>very likely</td>
      </tr>
      <tr>
        <td class="width2">Scenario 1</td>
        <td><input type="radio" name="DM1" value="1"></td>
        <td><input type="radio" name="DM1" value="2"></td>
        <td><input type="radio" name="DM1" value="3"></td>
        <td><input type="radio" name="DM1" value="4"></td>
        <td><input type="radio" name="DM1" value="5"></td>
        <td><input type="radio" name="DM1" value="6"></td>
        <td><input type="radio" name="DM1" value="7"></td>    
      </tr>
      <tr>
        <td class="width2">Scenario 2</td>
        <td><input type="radio" name="DM2" value="1"></td>
        <td><input type="radio" name="DM2" value="2"></td>
        <td><input type="radio" name="DM2" value="3"></td>
        <td><input type="radio" name="DM2" value="4"></td>
        <td><input type="radio" name="DM2" value="5"></td>
        <td><input type="radio" name="DM2" value="6"></td>
        <td><input type="radio" name="DM2" value="7"></td>    
      </tr>
      <tr>
        <td class="width2">Scenario 3</td>
        <td><input type="radio" name="DM3" value="1"></td>
        <td><input type="radio" name="DM3" value="2"></td>
        <td><input type="radio" name="DM3" value="3"></td>
        <td><input type="radio" name="DM3" value="4"></td>
        <td><input type="radio" name="DM3" value="5"></td>
        <td><input type="radio" name="DM3" value="6"></td>
        <td><input type="radio" name="DM3" value="7"></td>    
      </tr>
            <tr>
        <td class="width2">Scenario 4</td>
        <td><input type="radio" name="DM4" value="1"></td>
        <td><input type="radio" name="DM4" value="2"></td>
        <td><input type="radio" name="DM4" value="3"></td>
        <td><input type="radio" name="DM4" value="4"></td>
        <td><input type="radio" name="DM4" value="5"></td>
        <td><input type="radio" name="DM4" value="6"></td>
        <td><input type="radio" name="DM4" value="7"></td>    
      </tr>
      <tr>
        <td class="width2">Scenario 5</td>
        <td><input type="radio" name="DM5" value="1"></td>
        <td><input type="radio" name="DM5" value="2"></td>
        <td><input type="radio" name="DM5" value="3"></td>
        <td><input type="radio" name="DM5" value="4"></td>
        <td><input type="radio" name="DM5" value="5"></td>
        <td><input type="radio" name="DM5" value="6"></td>
        <td><input type="radio" name="DM5" value="7"></td>    
      </tr>
      <tr>
        <td class="width2">Scenario 6</td
>        <td><input type="radio" name="DM6" value="1"></td>
        <td><input type="radio" name="DM6" value="2"></td>
        <td><input type="radio" name="DM6" value="3"></td>
        <td><input type="radio" name="DM6" value="4"></td>
        <td><input type="radio" name="DM6" value="5"></td>
        <td><input type="radio" name="DM6" value="6"></td>
        <td><input type="radio" name="DM6" value="7"></td>    
      </tr>
</table>
  <p class="button"><button type="submit" name="next" value="yes">Next</button></p> 

【问题讨论】:

  • 您需要Qiestions/Answers 的查询吗?如果您只需将indexed=False 添加到模型中不需要查询的每个属性,您将节省大量写入。或者甚至更好,而不是每个问题/答案都有一个属性 - 将它们保存在一个类似 JSON 的结构化属性中。

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


【解决方案1】:

写入与保存的实体数量有关,读取与您检索的实体数量有关。

我们无法为您重新编写您的申请,但这里有一些节省成本的提示:

  1. 仅索引您在查询中使用的属性。所有其他属性都应未编入索引(indexed=False)。此更改将显着减少数据存储操作的数量和您的数据大小,并且无需付出任何努力。

  2. 无需将所有问题作为属性放入单个实体中。现在,每次您想要保存对单个问题的回复时,您都会重写整个实体,从而产生所有相应的成本。每个问题都应该是一个单独的实体——可能是问卷实体的一个子实体。然后,您可以以最低成本单独保存每个响应。此更改很可能会将您的应用的数据存储操作次数减少 10-20 倍。

【讨论】:

  • 谢谢。只是为了确保我理解第二点,您是在建议转置我的问卷类型(表格)?也就是说,不是让实体(行)等于唯一名称,属性(字段)等于问题的答案,而是让实体(行)等于问题的答案,属性(字段)等于唯一名称。那是对的吗?这将减少写作的数量,基本上是因为学生比问题少?
  • 它会 :),但我建议一些非常不同的东西:每个响应应该是一个单独的实体。基本上,您将拥有一个包含三列的表:studentID(或学生姓名,如果您愿意)、问题 ID、响应。每个响应都将是此表中的一行 - 添加起来很便宜,因为只有 3 个属性(列)。
  • 谢谢!我选择了您的答案,因为它是最可行的。
【解决方案2】:

嗯 - 问题是,您是否需要在每次用户切换到下一页时保存答案? 您还可以将数据保留在服务器上(会话对象),并仅在进程结束时保存到数据库中。

同时确保您的索引属性保持在较低水平:

新实体放置(每个实体,与实体大小无关):2 次写入 + 每个索引属性值 2 次写入 + 每个复合索引值 1 次写入

没有(直接)需要对问题或页面进行索引。 当用户返回页面时,您只需检索所有问题,然后让您的后端代码将页面的正确问题发送到前端。

我不喜欢为每个问题编写一个单独的实体 - 这会导致您对您的存储库进行更多阅读。 此外 - 这是一种非常规范化的方法 - 可能不适用于您的应用。

为您的架构多考虑:您的应用程序是如何使用的? 什么对我来说最容易?每个用户的整个测验 1 个实体?每页1个实体?每个问题 1 个实体。 如果你想限制读写 => 根据上面的 writer 数选择你需要的设计。 想想你需要读多少次你的实体。 只需为每个设计写出 1 或 2 个场景并进行计算即可。

【讨论】:

    猜你喜欢
    • 2013-01-03
    • 1970-01-01
    • 2021-02-20
    • 1970-01-01
    • 2011-02-16
    • 2013-12-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多