Способ миграции моделей App Engine

миграции баз данных являются популярным шаблоном, особенно с Ruby on Rails. Поскольку миграции определяют, как формировать старые данные в соответствии с новой схемой, они могут быть полезны при наличии производственных данных, которые должны быть преобразованы быстро и надежно.

но миграция моделей в App Engine затруднена, так как обработка всех объектов последовательно затруднена, и нет автономной операции для эффективной миграции всего в одной большой транзакции.

каковы ваши методы изменения БД.Модель "схема" и перенос данных в соответствии с новой схемой?

1 ответов


вот что я делаю.

у меня есть класс 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 перехватывает преобразование из объекта raw datastore в полную БД.Экземпляр модели. Если 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

на занятом сайте метод migrate() должен повторить если db.put() сбой и, возможно, войти критическую ошибку, если миграция не сработала.

Я еще не добрался туда, но в какой-то момент я, вероятно, смешаю свои миграции из отдельного файла.

заключение

это трудно проверить на App Engine. Трудно получить доступ к производственным данным в тестовой среде, и в это время трудно сделать согласованную резервную копию моментального снимка. Поэтому, для крупных изменений, рассмотрим создание новой версии, которая использует совершенно другое название модели который импортирует из старой модели и мигрирует по мере необходимости. (Например, User2 вместо User). Таким образом, если вам нужно вернуться к предыдущей версии, у вас есть эффективное резервное копирование данных.