2

我正在爲我們當前的生產App Engine應用程序編寫數據存儲遷移。是否有可能爲一種實體類型加載2個模型來支持數據遷移?

我們,所以我試圖把到位的架構,允許在未來更容易遷移做了一些數據模型相當廣泛的變化。這包括遷移腳本的測試套件和遷移腳本的通用類結構。

我遇到了我目前的戰略問題。對於遷移和測試腳本,我需要一種方法來將舊模式中的模型類和新數據模式中的模型類同時加載到內存中,並使用任何一種方式加載實體。

這裏是設定模式的一個例子。

rev1.py

class Account(db.Model): 
    _version  = db.IntegerProperty(default = 1) 
    user   = db.UserProperty(auto_current_user_add = True, required = True) 
    name   = db.StringProperty() 
    contact_email = db.EmailProperty() 

rev2.py

class Account(db.Model): 
    _version = db.IntegerProperty(default = 2) 
    auth_id = db.StringProperty() 
    name  = db.StringProperty() 
    pwd_hash = db.StringProperty(required = True, indexed = False) 

遷移腳本可能看起來像:

import rev1 
import rev2 

class MyMigration(...): 
    def isNeeded(self): 
     num_accounts = num_entities_with_version(rev1.Account, 1) 
     return num_accounts > 0 

    def run(self): 
     rev1_accounts = rev1.Account.all() 
     for account in [a for a in rev1_accounts if account._version == 1]: 
      auth_id = account.contact_email 
      if auth_id is None or auth_id == '': 
       auth_id = account.user.email() 

       new_account = rev2.Account.create(auth_id = auth_id, 
               name = account.name) 

和測試套件會是這個樣子:

import rev1 
import rev2 

class MyTest(...): 
    def testIt(self): 
     # Setup data 
     act1 = rev1.Account(name = '..', contact_email = '..') 
     act1.put() 
     act2 = rev1.Account(name = '..', contact_email = '..') 
     act2.put() 

     # Run migration 
     migration.run() 

     # Check results 
     accounts = rev2.Account.all().fetch(99) 

所以,你可以看到我在兩種方式使用舊版本。我在遷移中使用它作爲讀取舊格式數據並將其轉換爲新格式的方式。 (注意:由於諸如所需的pwd_hash字段和其他字段更改之類的內容,我無法以新格式讀取它)。在運行遷移之前,我正在測試套件中使用它來設置舊格式的測試數據。

這一切似乎在理論上很不錯,但在實踐中分崩離析,因爲GAE不允許被加載在同一種多個模型,或者更具體地說,查詢只返回最近定義的模型。

在開發服務器中,這似乎是由於實體的查詢(例如:Account.get(my_key))上調用get()的過程調用了構建結果模型對象的結果鉤子通過調用class_for_kind關於數據中的實體種類名稱。因此,儘管我可能會調用rev2.Account.get(),但它可能會構建rev1.Account模型對象,因爲類型'Account'映射到_kind_map字典中的rev1.Account。

這使我重新思考自己的遷移策略了一下,我想問問,如果任何人有想法。具體做法是:

  1. 這將是安全的在運行時可以手動覆蓋google.appengine.ext.db._kind_map測試和生產服務器上允許這種遷移方法的工作?
  2. 有沒有更好的方法來同時將兩個版本的模型保存在內存中?
  3. 是否有不同的遷移方法,可能是更智能的方式去做這項工作?
  4. 我也曾想過嘗試

其他方法包括:版本變化

  • 更改實體類型時。 (使用kind()來更改它)然後,當我們遷移時,我們將所有的類移動到新的類名稱。
  • 找到一種方法來查詢實體並找回尚未構建到完整對象中的「原始」對象(原始緩衝區)。 (不會與測試一起工作)
  • 'Just Do It Live':不要爲任何測試編寫測試,而只是嘗試使用最新的模式進行遷移,然後加載舊數據來解決問題。

回答

1

我認爲在更大的問題中實際存在幾個問題。這裏似乎有兩個關鍵問題,一個是如何測試,另一個是如何真正做到這一點。

我不會定義多次;正如你所指出的那樣,這樣做有細微之處,而且,如果你加載了錯誤的模型,你會遇到各種各樣的麻煩。也就是說,你完全可以操縱kind_map。我已經在一些特殊情況下做到了這一點,但我儘可能避免這種情況。

對於有大量模式更改的實時遷移,您有兩個選擇:使用Expando或使用lower level API。添加必填字段時,可能會發現使用Expando更容易,然後運行遷移以添加新信息,然後切換回普通db.Model。較低級別的API位於ext.db的正下方,它將實體呈現爲Python字典。這對操縱實體非常方便。使用任何你更舒適的方法。我更喜歡Expando,因爲它是一個更高層次的界面,但是這是一個兩步過程。

爲了測試,我個人建議您專注於實際的轉換例程。因此,不要從查詢的角度測試方法,而是測試以確保您的轉換例程本身正常工作。您甚至可以選擇將舊實體作爲Python字典傳入,然後返回新實體。

我會在這裏做一個其他的調整。我寧願使用查詢來查找我的所有rev 1帳戶。這是關於在模型上建立索引_version的好方法。你可以平凡地找到需要遷移的東西。

此外,請查看Google的article on updating schemas。這是舊的,但仍然很好。

+0

關於在存在重大模式更改的情況下創建新類型的想法的任何想法。看起來這會允許類似於使用Expando的東西,但允許遷移一次發生。 (即加載rev1.Account(kind:acct_v1)的所有舊實體,然後將它們保存到新模型rev2.Account(kind:acct_v2))。這對我來說似乎比較乾淨,然後暫時引入Expando,然後再將其拉出。它可能具有允許測試整個過程的額外好處。毫無疑問,我錯過了一些東西。 – Allen 2012-01-30 23:10:09

+0

我得說我喜歡改變種類來表示版本的想法。這可以很乾淨。如果這個槍太大,我會選擇暫時的Expando路線。您也可以暫時從新字段中刪除「必需」標誌,然後您可以就地更改實體,並且仍然具有某種類型安全性。但請注意,您無法以這種方式完全擺脫舊屬性 - 這需要暫時使用Expando。但是,在將所有實體轉換爲新模式後,您可以在離線運行的一次性映射/縮減作業中執行清理步驟。不要把W弄亂。親切的地圖。 – 2012-01-31 03:18:28

+0

我想在兩種情況下更新類型以表明版本可能是好的:1)真正的主要模式更改和2)當您沒有很多對該模型的引用時(或者它們全部是通過鍵名/ ID)。如果你有很多交叉引用,你可能會弄得一團糟。 – 2012-01-31 05:27:55

1

另一種方法是簡單地在版本2上進行遷移,在模型上保留舊屬性並在更新版本後將其設置爲無。這將清除他們使用的空間,但仍然會讓他們定義。然後在以下版本中,您可以將它們從模型中刪除。

該方法非常簡單,但確實需要兩個版本完全刪除舊屬性,所以更類似於棄用現有屬性。

+0

添加必填字段時不會失敗嗎?至少它似乎在我的測試用例中使用了新的password_hash字段。我可能會這樣做,所以第一次引入屬性時不需要,然後在所有內容都被遷移後在下面的版本中使用它,但我試圖避免一個多遍轉換過程。 – Allen 2012-01-30 23:13:18

+0

我想說不要立即要求這個領域。在您的業務邏輯中檢查它(至少現在是這樣)。 – 2012-01-31 05:29:51

相關問題