如何将Google App Engine MapReduce与大量数据一起使用(> 1000000)

时间:2017-01-27 07:05:53

标签: python google-app-engine mapreduce

我实现了非常简单的mapreduce管道并堆积了一些问题。

场景是,云数据存储中的一种模型中有超过1000000个实体,我想检查所有实体是否每个实体都有不一致的属性。

这是我的代码段。

class User(ndb.model)
    parent = ndb.KeyProperty(Group) # want to check if this key property actually exist


class CheckKeyExistencePipeline(pipeline.Pipeline):

    def map(self, entity):
        logging.info(entity.urlsafe()) # added for debug
        prop = getattr(entity, 'parent')
        if not prop.get():
            yield 'parent does not exist: %s\n' % (entity.key.urlsafe())

    def run(self, modelname, shards):
        mapreduce_pipeline.MapperPipeline(
            'parent check',
            handler_spec='CheckKeyExistencePipeline.map',
            input_reader_spec='mapreduce.input_readers.DatastoreInputReader',
            output_writer_spec="mapreduce.output_writers.GoogleCloudStorageOutputWriter",
            params={
                'input_reader': {
                    'entity_kind': 'User',
                },
                'output_writer': {
                    'bucket_name': app_identity.get_default_gcs_bucket_name(),
                    'content_type': 'text/plain'
                }
            },
            shards=10)

问题是,它经常出现像下面这样的错误。

  

超过128 MB的软私有内存限制,133 MB后   为2个请求提供服务

使用大约10000个实体的数据运行此代码时没有问题。 什么是问题,如何正确配置此管道以应用大量数据?

EDIT1

我修改了不使用ndb缓存,但似乎没有任何改进。根据源代码,我猜缓存已经默认关闭。

https://github.com/GoogleCloudPlatform/appengine-mapreduce/blob/6e103ac52855c3214de3ec3721d6ec0e7edd5f77/python/src/mapreduce/util.py#L381-L383

def _set_ndb_cache_policy():
    """Tell NDB to never cache anything in memcache or in-process.
    This ensures that entities fetched from Datastore input_readers via NDB
    will not bloat up the request memory size and Datastore Puts will avoid
    doing calls to memcache. Without this you get soft memory limit exits,
    which hurts overall throughput.
    """
    ndb_ctx = ndb.get_context()
    ndb_ctx.set_cache_policy(lambda key: False)
    ndb_ctx.set_memcache_policy(lambda key: False)

我做了进一步调查以找出问题所在。我将mapper参数processing_rate中的一个设置为10,将shards设置为100,这样它就只为每个任务处理1个或2个实体。

这是mapreduce属性。图表似乎合理。 (此时管道尚未完成。)

mapreduce detail

但是当我检查其中一个worker任务的跟踪日志时,真的很奇怪。它显示了大量的/datastore_v3.Next/datastore_v3.Get,尽管事实上地图'函数只被调用两次(根据我的调试日志。)因为我没有更改batch_size,所以它应该是50.因此,根据我的理解,/datastore_v3.Next应该只调用一次/datastore_v3.Get两次。

tracelog

有谁知道为什么会触发这么多RPC数据库调用?

EDIT2

再一次,我做了进一步的调查并使代码变得简单。 map函数只需调用ndb.Key函数即可使用get获取数据。

class CheckKeyExistencePipeline(pipeline.Pipeline):

    def map(self, entity):
        logging.info('start')
        entity.parent.get()
        logging.info('end')


    def run(self):
        mapreduce_pipeline.MapperPipeline(
            'parent check',
            handler_spec='CheckKeyExistencePipeline.map',
            input_reader_spec='mapreduce.input_readers.DatastoreInputReader',
            output_writer_spec="mapreduce.output_writers.GoogleCloudStorageOutputWriter",
            params={
                'input_reader': {
                    'entity_kind': 'User',
                },
                'output_writer': {
                    'bucket_name': app_identity.get_default_gcs_bucket_name(),
                    'content_type': 'text/plain'
                }
            },
            shards=10)

Stackdriver的Tracelog是这样的。

trace log

它只是调用get,但它会在“开始”之间多次触发RPC调用。并且'结束'。这似乎有点奇怪,可能是这种内存消耗的原因之一。这是常规行为吗?

1 个答案:

答案 0 :(得分:0)

我想我终于找到了问题所在。

关键是MapReduce使用ndb.query.iter并使用eventloops来管理异步RPC调用。在我的MapReduce调用中,它触发两种类型的RPC调用,一种是由MapReduce库触发来获取数据库记录(A),另一种是由我的map函数(B)触发。

如果我在map函数中没有触发任何RPC调用,则没有地方可以触发下一个RPC调用。这意味着下一个(A)仅在批量处理50个记录后触发。但是,尝试(B)触发下一个RPC调用,并且由于RPC调用不是串行触发的(这意味着它不是FIFO队列),因此很可能(A)连续触发,直到它获取所有实体

我将shards设置为100,但仍有一个分片负责完全10000条记录。因此,这超出了软内存限制。

当我将shards增加到10000时,会发生另一个错误......

总之,没有办法将MapReduce与具有低内存实例的大数据一起使用。我想。

有关详细信息,请查看以下问题。

https://code.google.com/p/googleappengine/issues/detail?id=11648 https://code.google.com/p/googleappengine/issues/detail?id=9610(原始问题)