Рекомендуемые стратегии для резервного копирования хранилища данных appengine - PullRequest
7 голосов
/ 10 ноября 2011

Сейчас я использую remote_api и appcfg.py download_data, чтобы делать снимок моей базы данных каждую ночь. Это занимает много времени (6 часов) и стоит дорого. Без использования собственной резервной копии, основанной на изменениях (я был бы слишком напуган, чтобы делать что-то подобное), как лучше всего убедиться, что мои данные защищены от сбоев?

PS: я признаю, что данные Google, вероятно, намного безопаснее, чем мои. Но что, если однажды я случайно напишу программу, которая удалит все это?

1 Ответ

3 голосов
/ 10 ноября 2011

Я думаю, вы в значительной степени определили все свои варианты.

  1. Доверяйте Google, чтобы не потерять ваши данные, и надеемся, что вы случайно не поручили им уничтожить их.
  2. Выполните полное резервное копирование с помощью download_data, возможно, реже, чем один раз за ночь, если это слишком дорого.
  3. Сверните свое собственное решение для инкрементного резервного копирования.

Вариант 3 на самом деле интересная идея. Вам понадобится метка времени изменения для всех сущностей, и вы не будете ловить удаленные сущности, но в противном случае это очень выполнимо с remote_api и курсорами.

Редактировать

Вот простой инкрементный загрузчик для использования с remote_api. Опять же, предостережения в том, что он не будет замечать удаленные объекты и предполагает, что все объекты хранят время последнего изменения в свойстве updated_at. Используйте его на свой страх и риск.

import os
import hashlib
import gzip
from google.appengine.api import app_identity
from google.appengine.ext.db.metadata import Kind
from google.appengine.api.datastore import Query
from google.appengine.datastore.datastore_query import Cursor

INDEX = 'updated_at'
BATCH = 50
DEPTH = 3

path = ['backups', app_identity.get_application_id()]
for kind in Kind.all():
  kind = kind.kind_name
  if kind.startswith('__'):
    continue
  while True:
    print 'Fetching %d %s entities' % (BATCH, kind)
    path.extend([kind, 'cursor.txt'])
    try:
      cursor = open(os.path.join(*path)).read()
      cursor = Cursor.from_websafe_string(cursor)
    except IOError:
      cursor = None
    path.pop()
    query = Query(kind, cursor=cursor)
    query.Order(INDEX)
    entities = query.Get(BATCH)
    for entity in entities:
      hash = hashlib.sha1(str(entity.key())).hexdigest()
      for i in range(DEPTH):
        path.append(hash[i])
      try:
        os.makedirs(os.path.join(*path))
      except OSError:
        pass
      path.append('%s.xml.gz' % entity.key())
      print 'Writing', os.path.join(*path)
      file = gzip.open(os.path.join(*path), 'wb')
      file.write(entity.ToXml())
      file.close()
      path = path[:-1-DEPTH]
    if entities:
      path.append('cursor.txt')
      file = open(os.path.join(*path), 'w')
      file.write(query.GetCursor().to_websafe_string())
      file.close()
      path.pop()
    path.pop()
    if len(entities) < BATCH:
      break
...