拥有事务而不使用高复制数据库

时间:2011-12-04 13:23:25

标签: google-app-engine

我有一个执行以下任务的应用程序。

  1. 通过电子邮件和密码验证用户。
  2. 将上传的文件保存在BlobStore中。
  3. 检查DataStore中的用户信息,以查看是否存在与此用户关联的旧Blob。如果是,请从BlobStore中删除旧blob。
  4. 更新DataStore,将BlobStore中的新blob与此用户关联。
  5. 我尝试在交易中执行第2,3,4步。

    db.run_in_transaction(self.upload, email, checksum, version, content)
    

    但是,正如预期的那样,由于我访问了多个实体,因此出现以下错误。

    BadRequestError: can't operate on multiple entity groups in a single transaction.
    

    我不太高兴。至于什么是事务的用途,如果它不能跨多个表(实体)执行原子操作?

    我强制使用High Replication Database。 (在结算方面,这将花费我的费用)

    db.run_in_transaction_options(xg_on, self.upload, email, checksum, version, content)
    

    同样,我收到以下错误:

    BadRequestError: Only ancestor queries are allowed inside transactions.
    

    这在网上发生:

    blob_key = files.blobstore.get_blob_key(file_name)
    

    我的问题是: -

    1. 我们有什么方法可以跨多个“表”执行事务,就像我可以通过PostgresSQL做的那样,而不需要使用高复制数据存储区?只要成本受到关注,主/从数据存储就会让我满意。
    2. 如何将blob_key = files.blobstore.get_blob_key(file_name)转换为祖先查询?这样它会在内部交易中运作吗?或者,简而言之,我如何使def upload在交易中发挥作用?
    3. 我有完整的代码如下:


      import urllib
      import logging
      import model
      import zlib
      from google.appengine.api import urlfetch
      from google.appengine.ext import webapp
      from google.appengine.ext.webapp.util import run_wsgi_app
      from google.appengine.api import files
      from google.appengine.ext import db
      from google.appengine.ext import blobstore
      
      xg_on = db.create_transaction_options(xg=True)
      
      
      class Upload(webapp.RequestHandler):
          def post(self):
              email = self.request.get('Email')
              password = self.request.get('Passwd')
              checksum = int(self.request.get('Checksum'))
              version = int(self.request.get('Version'))
              logintoken = self.request.get('logintoken')
              logincaptcha = self.request.get('logincaptcha')
              content = self.request.get('file')
      
              if version == -1:
                  self.response.out.write('ERROR [invalid parameter(s)]')
                  return
      
              # Ensure the uploaded content is valid.
              if content is None or not content:
                  self.response.out.write('ERROR [no file is uploaded]')
                  return
      
              # Authentication.
              headers = {"Content-type": "application/x-www-form-urlencoded"}
              if logintoken and logincaptcha:
                  form_data = urllib.urlencode({
                      'accountType': 'HOSTED_OR_GOOGLE', 
                      'Email': email,
                      'Passwd': password,
                      'service': 'mail',
                      'source': 'JStock-1.05b',
                      'logintoken': logintoken,
                      'logincaptcha': logincaptcha
                  })
              else:
                  form_data = urllib.urlencode({
                      'accountType': 'HOSTED_OR_GOOGLE', 
                      'Email': email,
                      'Passwd': password,
                      'service': 'mail',
                      'source': 'JStock-1.05b'
                  })
              result = urlfetch.fetch(url='https://www.google.com/accounts/ClientLogin', payload=form_data, method=urlfetch.POST, headers={'Content-Type': 'application/x-www-form-urlencoded'})
              self.response.set_status(result.status_code)
              if result.status_code != 200:
                  # Fail. Either incorrect password or captcha information required.
                  self.response.out.write(result.content)
                  return
      
              # OK! This is a valid user. Let's proceed with checksum verification.
              ##if checksum != zlib.adler32(content):
              ##    self.response.out.write('ERROR [fail in checksum]')
              ##    return            
      
              #db.run_in_transaction(self.upload, email, checksum, version, content)
              db.run_in_transaction_options(xg_on, self.upload, email, checksum, version, content)
              #self.upload(email, checksum, version, content)
      
      
          def upload(self, email, checksum, version, content):
              # Create the file
              file_name = files.blobstore.create(mime_type='application/octet-stream', _blobinfo_uploaded_filename=email)
      
              # Open the file and write to it
              with files.open(file_name, 'a') as f:
                  f.write(content)
      
              # Finalize the file. Do this before attempting to read it.
              files.finalize(file_name)
      
              # Get the file's blob key
              blob_key = files.blobstore.get_blob_key(file_name)
      
              # Remove previous blob referenced by this human.
              query = model.Human.all()
              query.filter('email =', email)
              for q in query:
                  blobstore.delete(q.content.key())
      
              human = model.Human(key_name=email, email=email, checksum=checksum, version=version, content=blob_key)
              human.put()
      
      
      application = webapp.WSGIApplication([
          ('/upload.py', Upload)
      ], debug=True)
      
      
      def main():
          run_wsgi_app(application)
      
      
      if __name__ == '__main__':
          main()
      

2 个答案:

答案 0 :(得分:1)

我想我通过使用事务看到了您要完成的任务:要么创建blobstore对象又要创建数据存储对象(Human),要么不创建它们。但是你的方法导致了几个问题,其中之一就是你不能在事务内部进行非祖先查询。当你执行get_blob_key时,你会看到,但你也会查询Human。 (第一个错误隐藏了第二个错误。)然后就是创建一个全新的Human而不是更新现有的{{1}}的问题,这个问题将留下一个已删除blob的密钥。

最简单的方法是免除交易。存储blob,然后确定您是否知道此人类。如果是,请删除旧blob,然后更新Human。如果不是,请使用新存储的blob键创建一个新的Human。

答案 1 :(得分:1)

现在,HR数据存储区和M / S数据存储区在新计费下的价格相同。没有理由不使用HR数据存储区。

get_blob_key必须进行查询才能找到与文件名对应的blob。完成交易之外的所有工作,并且只在事务内部进行更新。但请注意,您无法做任何事情会使整个流程成为事务性的 - 因为blobstore更新本身不是。