閱讀214 返回首頁    go 阿裏雲 go 技術社區[雲棲]


關於大數據量下Core Data的數據遷移

Core Data版本遷移基礎


通常,在使用Core Data的iOS App上,不同版本上的數據模型變更引發的數據遷移都是由Core Data來負責完成的。
這種數據遷移模式稱為Lightweight Migration(可能對於開發人員來說是lightweight),開發人員隻要在添加Persistent Store時設置好對應選項,其它的就交付給Core Data來做了:



從命名上可以看出這兩個選項分別代表:自動遷移Persistent Store,以及自動創建Mapping Model。

自動遷移Persistent Store很好理解,就是將數據從一個物理文件遷移到另一個物理文件,通常是因為物理文件結構發生了變化。
自動創建Mapping Model是為遷移Persistent Store服務的,所以當自動遷移Persistent Store選項NSMigratePersistentStoreAutomaticallyOption為@(YES)、且找不到Mapping Model時,coordinator會嚐試創建一份。
其它初始化場景可以參考Initiating the Migration Process

既然是嚐試創建,便有成功和失敗的不同結果。隻有當數據模型的變更屬於某些基本變化時,才能夠成功地自動創建出一份Mapping Model,比如:新增一個字段;刪除一個字段;必填字段轉換成可選字段;可選字段轉換成必填字段,同時提供了默認值等等。

因為可能創建Mapping Model失敗,所以考慮容錯性的話,可以事先判斷下能否成功推斷出一份Mapping Model:



利用如上類方法,如果無法創建一份Mapping Model,則會返回nil,並帶有具體原因。

以上都建立在Core Data能夠自動找到sourceModel和destinationModel的基礎上,如果無法找到對應的兩份Model,則需要開發人員手工創建NSMigrationManager來進行數據遷移(可以參考Use a Migration Manager if Models Cannot Be Found Automatically)。

版本遷移過程


那麼,數據遷移的過程是如何進行的?

首先,發生數據遷移需要三個基本條件:可以打開既有persistent store的sourceModel,新的數據模型destinationModel,以及這兩者之間的映射關係Mapping Model。

利用這三樣,當調用如下代碼時(addPersistentStore):



Core Data創建了兩個stack(分別為source stack和destination stack,可以參考Core Data stack),然後遍曆Mapping Model裏每個entity的映射關係,做以下三件事情:
     1. 基於source stack,Core Data先獲取現有數據,然後在destination stack裏創建當前entity的實例,隻填充屬性,不建立關係;
     2. 重新創建entity之間的關係;
     3. 驗證數據的完整性和一致性,然後保存。

考慮到第二步是重新建立entity之間的關係,那麼應該是在第一步就把所有entity的對象都創建好了,並且保留在內存中,為第二步服務(事實上也是如此)。

完成第二步後,所有數據還是維持在內存中(可能還有兩份,因為有兩個stack),在完成數據驗證後才真正保存。

這樣的話,會容易導致內存占用過多,因為Core Data在這個遷移過程中也沒有一種機製清理響應的context。所以在數據量較多時,App可能會遇到在數據遷移過程因為內存緊張而被係統幹掉。
針對這種情況,我們可以自定義遷移過程。

自定義數據遷移(解決內存問題)


自定義數據遷移的過程通暢分為三步:
第一步是判斷是否需要進行數據遷移:



第二步是創建一個Migration Manager對象:



第三步是真正發生數據遷移:



上麵三幅圖所展示的代碼在內存使用量上跟lightweight migration也沒什麼區別,無法解決內存峰值過高的問題。

雖然Core Data專家Marcus S. Zarra比較傾向堅持使用lightweight migration,不過對於上述內存占用過多的問題,Apple官方推薦使用Multiple Passes來解決。

關於Multiple Passes,官方文檔的說明很簡明扼要,如有需要,可以參考Stackoverflow上的這麼一篇帖子

用我的話往簡單裏說就是對數據模型進行劃分,把一份Mapping Model拆分成多份,然後分成多次遷移,從而降低內存峰值。這需要對數據庫進行全盤的考慮(甚至可能需要變更部分設計),然後通過合理的劃分把相關聯的Entity放在一份Mapping Model裏麵(因為要建立關聯)。

新的問題


采用上述方案來解決數據遷移過程中內存峰值的問題,我們仍然需要關注遷移所耗費的時間、內存,從而能夠在數據上驗證方案的有效性,並且在用戶交互方麵進行一些必要的更改(總不能讓用戶傻傻地在那邊等數據遷移吧)。

雖然可以解決內存峰值的問題,但也引進了其它問題。

1. 需要對數據模型進行劃分(以及變更),存在一定的工作量和風險;
2. 需要手工建立多份Mapping Model;
3. 需要手工編寫Multiple Passes遷移代碼;
4. 需要在每個版本變遷中都再次創建新的Mapping Model,且在跨版本遷移過程存在著其它問題;
5. 數據模型版本多起來,就麵臨著跨版本遷移的問題,是要為每個曆史版本創建到最新模型的Mapping Model,還是隻維護最近兩個版本的Mapping Model(更早的版本通過相鄰版本的Mapping Model依次遷移過來,比較耗時)?
6. 對數據模型重新劃分後,無關的Entity簡單變更也會引起整個store和model的不兼容,需要遷移,那麼是否考慮分庫?
7. 這麼大的動作服務的用戶數是很少的(隻有少數用戶會遇到,或者是很少),但卻是比較資深的(因為消息記錄多),疼。。。
8. 這無法解決單個Entity數據量過大的問題,針對這種場景,隻能自己手工編碼進行小批量的數據遷移;

Jason

2014.01.02 @ Hangzhou

Evernote

最後更新:2017-04-03 12:54:05

  上一篇:go awk 詳解+實例
  下一篇:go toggbutton