2010-09-26 54 views
2

應用程序引擎數據存儲當然有downtime。但是,我希望有一個「故障安全」put,它在面對數據存儲錯誤時更加穩健(請參閱下面的動機)。當數據存儲不可用時,似乎任務隊列是延遲寫入的明顯位置。我不知道任何其他解決方案(除了通過urlfetch將數據發送給第三方)。應用程序引擎上的故障安全數據存儲更新

動機:我真的需要要被放置在數據存儲實體 - 只是顯示錯誤信息給用戶不會做。例如,也許有一些副作用發生,不容易撤銷(可能與第三方網站有一些互動)。

我想出了一個簡單的包裝(我認爲)提供了一個合理的「故障安全」放(見下文)。你有沒有看到這方面的問題,或有一個更強大的實施的想法? (注:由於張貼在由尼克·約翰遜和薩克森德魯斯答案的建議,這個帖子被編輯了一些改進的代碼)的任務

import logging 
from google.appengine.api.labs.taskqueue import taskqueue 
from google.appengine.datastore import entity_pb 
from google.appengine.ext import db 
from google.appengine.runtime.apiproxy_errors import CapabilityDisabledError 

def put_failsafe(e, db_put_deadline=20, retry_countdown=60, queue_name='default'): 
    """Tries to e.put(). On success, 1 is returned. If this raises a db.Error 
    or CapabilityDisabledError, then a task will be enqueued to try to put the 
    entity (the task will execute after retry_countdown seconds) and 2 will be 
    returned. If the task cannot be enqueued, then 0 will be returned. Thus a 
    falsey value is only returned on complete failure. 

    Note that since the taskqueue payloads are limited to 10kB, if the protobuf 
    representing e is larger than 10kB then the put will be unable to be 
    deferred to the taskqueue. 

    If a put is deferred to the taskqueue, then it won't necessarily be 
    completed as soon as the datastore is back up. Thus it is possible that 
    e.put() will occur *after* other, later puts when 1 is returned. 

    Ensure e's model is imported in the code which defines the task which tries 
    to re-put e (so that e can be deserialized). 
    """ 
    try: 
     e.put(rpc=db.create_rpc(deadline=db_put_deadline)) 
     return 1 
    except (db.Error, CapabilityDisabledError), ex1: 
     try: 
      taskqueue.add(queue_name=queue_name, 
          countdown=retry_countdown, 
          url='/task/retry_put', 
          payload=db.model_to_protobuf(e).Encode()) 
      logging.info('failed to put to db now, but deferred put to the taskqueue e=%s ex=%s' % (e, ex1)) 
      return 2 
     except (taskqueue.Error, CapabilityDisabledError), ex2: 
      return 0 

請求處理程序:

from google.appengine.ext import db, webapp 

# IMPORTANT: This task deserializes entity protobufs. To ensure that this is 
#   successful, you must import any db.Model that may need to be 
#   deserialized here (otherwise this task may raise a KindError). 

class RetryPut(webapp.RequestHandler): 
    def post(self): 
     e = db.model_from_protobuf(entity_pb.EntityProto(self.request.body)) 
     e.put() # failure will raise an exception => the task to be retried 

不要期待這個用於把 - 大部分時間,顯示錯誤信息就好了。對於每一個放置都使用它是很有誘惑力的,但是我認爲有時如果我告訴他們他們的改變將在以後出現(並且繼續向他們顯示舊數據直到數據存儲備份延期賣出執行)。

+0

一個相關的問題:是否有數據存儲和任務隊列停機之間有任何相關性?(http://stackoverflow.com/questions/3800252/datastore-and-task-queue-downtime-correlation) – 2010-09-26 23:25:45

回答

2

你的做法是合理的,但有幾個注意事項:

  • 默認情況下,放置操作將重試,直到它運行的時間。由於您有備份策略,因此您可能希望儘快放棄 - 在這種情況下,您應該爲put方法調用提供一個rpc參數,並指定一個自定義的截止日期。
  • 沒有必要設置明確的倒計時 - 任務隊列將以增加的時間間隔重試失敗的操作。
  • 你不需要使用pickle - 協議緩衝區有一個自然的字符串編碼,效率更高。有關如何使用它的演示,請參見this post。如Saxon指出的,任務隊列有效載荷被限制爲10千字節,因此您可能在大型實體中遇到問題。
  • 最重要的是,這將數據存儲一致性模型從「強一致性」更改爲「最終一致性」。也就是說,您入選任務隊列的投入可以在將來的任何時間應用,覆蓋在此期間所做的任何更改。任何數量的競爭條件都是可能的,如果在任務隊列中存在懸而未決的情況,則實質上使得事務無用。
+0

感謝您的詳細反饋意見;我肯定會納入這些想法。我設定倒計時的唯一原因是因爲我認爲這將確保任務隊列不會立即嘗試重新放置實體(因爲它只是失敗了,也許應該給它一小段時間[也許是如果問題是暫時性的,例如平板分割等,則默認60秒太多])。 – 2010-09-28 10:16:21

1

一個潛在的問題是tasks are limited to 10kb of data,所以如果你有一個大於一次酸洗的實體,這將不起作用。

+0

好點;幸運的是,我不必爲這個實體使用它而擔心這個問題。但是我會更新代碼的文檔字符串以反映這個限制。 – 2010-09-28 10:13:25