如何使用数百万次写入来调试长时间运行的mapreduce作业?

时间:2013-05-10 18:58:15

标签: google-app-engine mapreduce

我正在使用appengine-mapreduce库的简单control.start_map()函数来启动mapreduce作业。此作业成功完成,并在生成的/mapreduce/detail?mapreduce_id=<my_id>页面上显示~43M映射器调用。但是,这个页面没有提到我认为仍在运行的reduce步骤或任何底层的appengine-pipeline进程。有没有办法返回这个调用的管道ID,所以我可以查看底层管道来帮助调试这个长期运行的工作?我想检索足够的信息来提取此页面:/mapreduce/pipeline/status?root=<guid>

以下是我用来启动mapreduce作业的代码示例:

from third_party.mapreduce import control
mapreduce_id = control.start_map(
    name="Backfill",
    handler_spec="mark_tos_accepted",
    reader_spec=(
        "third_party.mapreduce.input_readers.DatastoreInputReader"),
    mapper_parameters={
        "input_reader": {
            "entity_kind": "ModelX"
        },
    },
    shard_count=64,
    queue_name="backfill-mapreduce-queue",
 )

这是映射函数:

# This is where we keep our copy of appengine-mapreduce
from third_party.mapreduce import operation as op

def mark_tos_accepted(modelx):
    # Skip users who have already been marked
    if (not modelx
        or modelx.tos_accepted == myglobals.LAST_MATERIAL_CHANGE_TO_TOS):
    return

    modelx.tos_accepted = user_models.LAST_MATERIAL_CHANGE_TO_TOS
    yield op.db.Put(modelx)

以下是ModelX的相关部分:

class BackupModel(db.Model):
    backup_timestamp = db.DateTimeProperty(indexed=True, auto_now=True)

class ModelX(BackupModel):
    tos_accepted = db.IntegerProperty(indexed=False, default=0)

有关更多上下文,我正在尝试调试我在数据仓库中显示的写入问题。

2013年3月23日,我们用~43M实体在db.Model(让我们称之为A)上启动了MapReduce作业(让我们称之为ModelX)。 7个小时后,作业“完成”,/mapreduce/detail页面显示我们已成功映射到所有实体,如下所示。

mapper-calls: 43613334 (1747.47/sec avg.)

2013年3月31日,我们在ModelX上启动了另一个MapReduce作业(我们称之为B)。 12小时后,作业状态成功完成,/mapreduce/detail页面显示我们已成功映射到所有实体,如下所示。

mapper-calls: 43803632 (964.24/sec avg.)

我知道MR作业A写给所有ModelX个实体,因为我们引入了一个以前没有包含任何实体的新属性。 ModelX包含auto_add属性,如此。

backup_timestamp = ndb.DateTimeProperty(indexed=True, auto_now=True)

我们的数据仓库流程在ModelX上运行查询,以查找在某一天更改的实体,然后下载这些实体并将其存储在单独的(AWS)数据库中,以便我们可以对它们进行分析。此查询的一个示例是:

db.GqlQuery('select * from ModelX where backup_timestamp >= DATETIME(2013, 4, 10, 0, 0, 0) and backup_timestamp < DATETIME(2013, 4, 11, 0, 0, 0) order by backup_timestamp')

我希望我们的数据仓库在MR工作完成的每一天都有大约4300万个实体,但实际上更像~3M,随后的每一天都显示出增加,如此进展所示:< / p>

3/16/13 230751
3/17/13 193316
3/18/13 344114
3/19/13 437790
3/20/13 443850
3/21/13 640560
3/22/13 612143
3/23/13 547817
3/24/13 2317784  // Why isn't this ~43M ?
3/25/13 3701792  // Why didn't this go down to ~500K again?
3/26/13 4166678
3/27/13 3513732
3/28/13 3652571

这让我觉得虽然mapreduce作业发出的op.db.Put()调用仍然在某个管道或队列中运行并导致这种涓流效应。

此外,如果我查询具有旧backup_timestamp的实体,我可以返回相当远但仍然获得大量实体,但我希望所有这些查询都返回0:

In [4]: ModelX.all().filter('backup_timestamp <', 'DATETIME(2013,2,23,1,1,1)').count()
Out[4]: 1000L

In [5]: ModelX.all().filter('backup_timestamp <', 'DATETIME(2013,1,23,1,1,1)').count()
Out[5]: 1000L

In [6]: ModelX.all().filter('backup_timestamp <', 'DATETIME(2012,1,23,1,1,1)').count()
Out[6]: 1000L

但是,存在这种奇怪的行为,其中查询返回不应该执行的实体:

In [8]: old = ModelX.all().filter('backup_timestamp <', 'DATETIME(2012,1,1,1,1,1)')

In [9]: paste
for o in old[1:100]:
  print o.backup_timestamp
## -- End pasted text --
2013-03-22 22:56:03.877840
2013-03-22 22:56:18.149020
2013-03-22 22:56:19.288400
2013-03-22 22:56:31.412290
2013-03-22 22:58:37.710790
2013-03-22 22:59:14.144200
2013-03-22 22:59:41.396550
2013-03-22 22:59:46.482890
2013-03-22 22:59:46.703210
2013-03-22 22:59:57.525220
2013-03-22 23:00:03.864200
2013-03-22 23:00:18.040840
2013-03-22 23:00:39.636020

这让我觉得索引只需要很长时间才能更新。

我还绘制了我们的数据仓库下载的实体数量,并注意到一些悬崖般的下降,这让我觉得在某些地方有一些我无法通过任何诊断看到的幕后限制在appengine仪表板上公开的工具。例如,当我们开始执行mapreduce工作时,此图表显示3/23的相当大的峰值,但此后不久就出现了戏剧性的下降。

此图显示每天每10分钟间隔BackupTimestamp GqlQuery返回的实体数。请注意,当MapReduce作业旋转时,紫色线显示出一个巨大的峰值,然后随着节流的进入而大幅下降~1小时。该图表还显示似乎存在一些基于时间的限制。

Datastore reads per 10-min increment, per day

3 个答案:

答案 0 :(得分:2)

我认为你没有任何减速器功能,因为你所做的只是启动一个映射器。要完成mapreduce,您必须显式实例化MapReducePipeline并在其上调用start。作为奖励,它会回答您的问题,因为它会返回您可以在状态网址中使用的管道ID。

答案 1 :(得分:0)

试图了解具体问题。您是否期望AWS数据库中有更多实体?我怀疑问题在于将旧的ModelX实体下载到AWS数据库的过程,它以某种方式不能捕获所有更新的实体。

AWS下载过程是否以任何方式修改ModelX?如果没有,那么为什么你会发现具有旧modified时间戳的实体会感到惊讶? modified只会在写入时更新,而不是在读取操作上更新。

有点无关 - 关于限制我通常会发现一个受限制的任务队列成为问题,所以可能会检查你的任务有多大,或者你的应用程序是否由于大量的错误而受到限制别的地方。

答案 2 :(得分:0)

control.start_map不使用管道,也没有shuffle / reduce步骤。当mapreduce状态页面显示已完成时,所有与mapreduce相关的任务队列任务应该已经完成​​。您可以检查队列甚至暂停它。

我怀疑旧模型的旧索引或最终一致性存在问题。要调试MR,过滤警告/错误日志并按mr id搜索是很有用的。为了帮助您处理特定情况,查看Map处理程序可能很有用。