使用Python在Google app Engine的app start上加载全局可访问的单例

使用Python在Google app Engine的app start上加载全局可访问的单例,python,google-app-engine,Python,Google App Engine,使用Google app engine,是否可以在应用程序启动时初始化全局可访问的单例?我有一个大的静态树结构,我需要在每个请求上使用它,并希望预先初始化它。树结构太大(20+MB),无法放入Memcache,我正在尝试找出我还有哪些其他选择 编辑:只是根据我目前收到的答案来增加一些清晰度。我正在将单词词典加载到一个trie/前缀树结构中。trie是不变的,因为单词词典是固定的。我正在根据输入字符串生成字谜图,所以一个请求可能在单个请求上访问相当数量的trie,可能超过1MB,但我不确定 这是我

使用Google app engine,是否可以在应用程序启动时初始化全局可访问的单例?我有一个大的静态树结构,我需要在每个请求上使用它,并希望预先初始化它。树结构太大(20+MB),无法放入Memcache,我正在尝试找出我还有哪些其他选择

编辑:只是根据我目前收到的答案来增加一些清晰度。我正在将单词词典加载到一个trie/前缀树结构中。trie是不变的,因为单词词典是固定的。我正在根据输入字符串生成字谜图,所以一个请求可能在单个请求上访问相当数量的trie,可能超过1MB,但我不确定

这是我正在加载单词词典的python结构

class Node(object):

    def __init__(self, letter='', final=False):
        self.letter = letter
        self.final = final
        self.children = {}

    def add(self, letters):
        node = self
        for index, letter in enumerate(letters):
            if letter not in node.children:
                node.children[letter] = Node(letter, index==len(letters)-1)
            node = node.children[letter]

每个请求可能来自完全不同的进程、不同的服务器,甚至可能位于单独的数据中心(嘿,可能在不同的大陆)。除了数据存储之外,没有任何东西可以保证对同一应用程序的不同请求的处理程序“全局访问”(如果事情太忙,甚至memcache的条目也可能随时消失:毕竟它是一个缓存!-)

也许您可以将“静态树结构”保存在与应用程序代码一起上载的数据文件中,并从磁盘访问它,而不是“初始化”

编辑:根据要求,这里有一个“轻量级类映射树到数组”方法的粗略示例,我在一篇评论中提到了这个方法——没有经过调优,也没有经过仔细测试。我以一个具有整数有效载荷的二叉搜索树为例,并假设出于某种原因,在“轻”树中保持精确的结构很重要,就像在它所代表的“重”树中一样。即使进行了这些简化,仍然需要大量代码,但是,下面是:

import array
import random

def _doinsert(tree, payload):
  if tree is None: return HeavyTree(payload)
  tree.insert(payload)
  return tree

class HeavyTree(object):
  def __init__(self, payload):
    self.payload = payload
    self.left = self.right = None
  def insert(self, other):
    if other <= self.payload:
      self.left = _doinsert(self.left, other)
    else:
      self.right = _doinsert(self.right, other)
  def walk(self):
    if self.left:
      for x in self.left.walk(): yield x
    yield self.payload
    if self.right:
      for x in self.right.walk(): yield x
  def walknodes(self):
    yield self
    if self.left:
      for x in self.left.walknodes(): yield x
    if self.right:
      for x in self.right.walknodes(): yield x

data = [random.randint(0, 99) for _ in range(9)]
print 'data: ',
for x in data: print x,
print
theiter = iter(data)
thetree = HeavyTree(next(theiter))
for x in theiter: thetree.insert(x)

print
print 'Heavy tree:'
print 'nodes:',
for x in thetree.walknodes(): print x.payload,
print
print 'inord:',
for x in thetree.walk(): print x,
print

class LightTree(HeavyTree):
  def __init__(self, base, offset):
    self.base = base
    self.offset = offset
  @property
  def payload(self):
    return self.base[self.offset]
  @property
  def left(self):
    return self._astree(self.offset+1)
  @property
  def right(self):
    return self._astree(self.offset+2)
  def _astree(self, i):
    offset = self.base[i]
    if offset < 0: return None
    return LightTree(self.base, offset)

def heavy_to_light(heavy):
  for i, node in enumerate(heavy.walknodes()):
    node.id = i * 3
  base = array.array('l', (i+1) * 3 * [-1])
  for node in heavy.walknodes():
    base[node.id] = node.payload
    if node.left: base[node.id+1] = node.left.id
    if node.right: base[node.id+2] = node.right.id
  return LightTree(base, 0)

print
print 'Light tree:'
light = heavy_to_light(thetree)
print 'nodes:',
for x in light.walknodes(): print x.payload,
print
print 'base :',
for x in light.base: print x,
print
print 'inord:',
for x in light.walk(): print x,
print
当然,由于数据是随机生成的,所以每次都会有详细的变化


也许这类事情对于不是从好的旧Fortran开始的人来说太麻烦了(因此不可避免地学会了如何将逻辑指针表示为数组的索引),就像我几十年前在EE学校做的那样;-)。但是,将这样的数组从文件直接加载到内存是非常快的(与unpickling之类的方法相比)…!-)

我认为谷歌为运行项目的每个实例提供300MB的本地内存。因此,您所要做的就是将树结构存储到某个模块中的变量中


每当谷歌为你的应用程序启动一个新的进程时,它都会运行一次代码来构建你的树,然后你就可以访问该进程处理的未来请求。只需确保构建这棵树所需的时间不超过30秒,因为它必须在任何随机请求的时间范围内发生,这使得谷歌决定启动一个新的进程。

一个请求需要访问这棵树的多少部分?你以什么方式提出质疑?它会改变吗


如果它是不可变的,那么实际上并不需要“单例”(singleton)——这意味着可变性——只需要一种访问每个实例上的数据的方法。根据您需要如何访问它,您可以将其存储为数据文件、blob或数据存储中的数据。

Memcache和数据存储是跨所有实例进行真正全局访问的最佳选择。但是,仍然可以使用全局变量在每个实例中缓存数据结构。trie结构不是很容易分解成块,这样每个块都可以放入memcache吗?一旦将trie放入memcache,无论何时从memcache访问trie块,都可以将其存储在该实例上的全局变量中。在几个请求过程中,您将在运行的每个实例上构建trie的完整副本。这有点复杂,但最终可能会给您带来最好的性能。

谢谢,Alex!我试图通过酸洗将静态树结构放入数据文件中,但取消酸洗过程与刚开始初始化树一样慢。还有其他建议吗?@james,这取决于树必须包含的内容——例如,如果它都是int或字符串,则可以将其映射到
数组中。数组
(使用int或字节序列作为偏移量/索引)封装到一个非常轻量级的类中;如果事情稍微复杂一点,那么这个类就不会那么轻量级,它可能需要一些文件/数组作为底层数据,但是这种通用方法可以扩展得相当广泛。关键是,从一个文件加载一个
array.array
是“尽可能快的”--基本上与读取文件字节的时间相同。Alex,你有没有像上面所说的那样在轻量级类中使用带有字符串的array.array的代码示例。我对python还是相当陌生的。谢谢@james,好的,刚刚编辑了我的答案,添加了一个示例(对于一个简单的案例,当然还有很多代码)。非常感谢您提供的代码示例,Alex。我会试试看。我的总体问题是,我需要在每个实例上访问的数据大小超过1MB。还有其他建议吗?我目前正在填充一个可以全局访问的模块中的变量。正如其他人所指出的,您不需要对每个请求的数据进行反序列化,只需要对新实例的第一个请求进行反序列化。为什么不这样做,并将其存储为模块成员?此外,您可能希望将trie转换为DAWG以节省空间。此时我正在使用一个模块成员,但由于GAE内存限制,我无法完全初始化它,因此我根据需要加载trie块,这样可以正常工作。我也放弃了反序列化。我还将尝试将我的trie转换为DAWG。谢谢你的帮助,尼克!
data:  27 79 90 60 82 80 3 94 76

Heavy tree:
nodes: 27 3 79 60 76 90 82 80 94
inord: 3 27 60 76 79 80 82 90 94

Light tree:
nodes: 27 3 79 60 76 90 82 80 94
base : 27 3 6 3 -1 -1 79 9 15 60 -1 12 76 -1 -1 90 18 24 82 21 -1 80 -1 -1 94 -1 -1
inord: 3 27 60 76 79 80 82 90 94