Способ переноса моделей App Engine

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

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

Каковы ваши методы изменения «схемы» db.Model и переноса данных в соответствии с новой схемой?


person JasonSmith    schedule 14.10.2009    source источник


Ответы (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 перехватывает преобразование необработанного объекта хранилища данных в полный экземпляр 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

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

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

Последние мысли

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

person JasonSmith    schedule 14.10.2009
comment
+1 за последние мысли. Это Google, это BigTable, используйте новое название модели. Кажется, лучшее решение. - person Terry G Lorber; 18.10.2009