数据库迁移是一种流行的模式,特别是Ruby on Rails。由于迁移指定了如何模拟旧数据以适应新模式,因此当您拥有必须快速可靠地转换的生产数据时,它们会很有用。
但是在App Engine中迁移模型很困难,因为顺序处理所有实体很困难,并且没有脱机操作可以在一个大事务中有效地迁移所有实体。
答案 0 :(得分:12)
这就是我的工作。
我有一个MigratingModel类,我的所有模型都继承自该类。这是migrating_model.py:
"""Models which know how to migrate themselves"""
import logging
from google.appengine.ext import db
from google.appengine.api import memcache
class MigrationError(Exception):
"""Error migrating"""
class MigratingModel(db.Model):
"""A model which knows how to migrate itself.
Subclasses must define a class-level migration_version integer attribute.
"""
current_migration_version = db.IntegerProperty(required=True, default=0)
def __init__(self, *args, **kw):
if not kw.get('_from_entity'):
# Assume newly-created entities needn't migrate.
try:
kw.setdefault('current_migration_version',
self.__class__.migration_version)
except AttributeError:
msg = ('migration_version required for %s'
% self.__class__.__name__)
logging.critical(msg)
raise MigrationError, msg
super(MigratingModel, self).__init__(*args, **kw)
@classmethod
def from_entity(cls, *args, **kw):
# From_entity() calls __init__() with _from_entity=True
obj = super(MigratingModel, cls).from_entity(*args, **kw)
return obj.migrate()
def migrate(self):
target_version = self.__class__.migration_version
if self.current_migration_version < target_version:
migrations = range(self.current_migration_version+1, target_version+1)
for self.current_migration_version in migrations:
method_name = 'migrate_%d' % self.current_migration_version
logging.debug('%s migrating to %d: %s'
% (self.__class__.__name__,
self.current_migration_version, method_name))
getattr(self, method_name)()
db.put(self)
return self
MigratingModel
拦截从原始数据存储区实体到完整db.Model实例的转换。如果current_migration_version
落后于班级的最新migration_version
,那么它会运行一系列migrate_N()
方法来完成繁重的工作。
例如:
"""Migrating model example"""
# ...imports...
class User(MigratingModel):
migration_version = 3
name = db.StringProperty() # deprecated: use first_name and last_name
first_name = db.StringProperty()
last_name = db.StringProperty()
age = db.IntegerProperty()
invalid = db.BooleanProperty() # to search for bad users
def migrate_1(self):
"""Convert the unified name to dedicated first/last properties."""
self.first_name, self.last_name = self.name.split()
def migrate_2(self):
"""Ensure the users' names are capitalized."""
self.first_name = self.first_name.capitalize()
self.last_name = self.last_name.capitalize()
def migrate_3(self):
"""Detect invalid accounts"""
if self.age < 0 or self.age > 85:
self.invalid = True
在繁忙的站点上,如果db.put()
失败,则migrate()方法应重试,如果迁移不起作用,则可能会记录严重错误。
我还没有到达那里,但在某些时候我可能会将我的迁移混合在一个单独的文件中。
很难在App Engine上进行测试。在测试环境中很难访问生产数据,此时很难做到连贯的快照备份。因此,对于重大更改,请考虑制作使用完全不同的型号名称的新版本,该版本从旧模型导入并根据需要进行迁移。 (例如,User2
而不是User
)。这样,如果您需要回退到以前的版本,您就可以有效地备份数据。