Skip to content

JOB bugs #13

@heriec

Description

@heriec

My English is not good, and then I'll switch to Chinese. Explaining in my native language won't lead to too many mistakes.

在跑JOB这个benchmark时候踩好几个坑,在issue里做一些说明,如果后面有人踩坑可以看看,大致的思路应该是对的,但是实现起来也不是很优雅,所以也就不pr了。

HASH_TABLE_SIZE

这个问题是在SUBQUERY_CARD_PULL_ANCHORCARD_PUSH_ANCHOR时会出现的,也就是获取subquery时候的报错,因为JOB中存在非常多的subquery,定义在src/backend/pilotscope/parse_json.c中的HASH_TABLE_SIZE定义的只有1023,肯定不够,我设置了16384是没有问题的,反正肯定要大于10k。

这个问题肯定会遇到,所以放第一个说,后面需要分条件说明了,就是是否设置了geqo = off

geqo

geqo是是否启用遗传算法来优化join order,默认为true

在subquery手动注入到pg的工作中,可以看到他们是关闭了geqo的:https://github.com/heriec/End-to-End-CardEst-Benchmark/blob/master/dockerfile/init_pgsql.sh

但是pilotscope是没有这样说的要求的,这在JOB中生成非常多的subquery时,会触发geqo(stats不会存在这个问题)。

geqo = on

具体调用路径在src/backend/optimizer/path/allpaths.c中的make_rel_from_joinlist

这个函数里面的

	if (levels_needed == 1)
	{
		/*
		 * Single joinlist node, so we're done.
		 */
		return (RelOptInfo *) linitial(initial_rels);
	}
	else
	{
		/*
		 * Consider the different orders in which we could join the rels,
		 * using a plugin, GEQO, or the regular join search code.
		 *
		 * We put the initial_rels list into a PlannerInfo field because
		 * has_legal_joinclause() needs to look at it (ugly :-().
		 */
		root->initial_rels = initial_rels;

		if (join_search_hook)
			return (*join_search_hook) (root, levels_needed, initial_rels);
		else if (enable_geqo && levels_needed >= geqo_threshold)
			return geqo(root, levels_needed, initial_rels);
		else
			return standard_join_search(root, levels_needed, initial_rels);
	}

当开启enable_geqo 同时levels_needed >= geqo_threshold就会调用geqo_eval()

也就是说:

  • 如果 表的数量 < geqo_threshold(默认 12) → 用动态规划算法。
  • 如果 表的数量 ≥ geqo_threshold → 启动 GEQO。

调用geqo() 会在 planner 决定用 GEQO 代替标准的动态规划 join search 时触发。

geqo 的调用在 src/backend/optimizer/geqo/geqo_main.c然后调用一些其他函数,直到调用到 位于src/backend/optimizer/geqo/geqo_eval.c

geqo_eval(PlannerInfo *root, Gene *tour, int num_gene)

就不贴详细代码了,自己看源码,具体就是遗传算法期间临时创建一个内存 cxt,用完会被释放

mycontext = AllocSetContextCreate(CurrentMemoryContext,
                                  "GEQO",
                                  ALLOCSET_DEFAULT_SIZES);

所以在 subquery 调用的时候会调用src/backend/pilotscope/utils/hashtable.ccreate_entry函数创建 entry:

Entry* entry = (Entry*)palloc(sizeof(Entry));
entry->key   = (char*)palloc(strlen(key) + 1);
entry->value = (char*)palloc(strlen(value) + 1);

分配内存空间会被分配到GEQO这个 cxt 上

mycontext = AllocSetContextCreate(CurrentMemoryContext,
									  "GEQO",
									  ALLOCSET_DEFAULT_SIZES);

如果没有使用就是使用MessageContextGEQO是他的 children(pg里的内存是树状的)

所以当 GEQO 的内存在释放后,之前创建的Entry是存在 count_table 的,但是GEQO会释放这块的内存,导致后面从count_table中get时候内存找不到对应数据,只是为了解决这个bug的话,我就把create_entry时单独处理了一下,感觉不是很好的处理方式,仅作思路参考:

// create entry
Entry* create_entry(const char* key, const char* value) 
{
    MemoryContext oldcxt = NULL;
    if (strcmp(CurrentMemoryContext->name, "MessageContext") != 0)
    {
        oldcxt = MemoryContextSwitchTo(CurrentMemoryContext->parent);
    }
    Entry* entry = (Entry*)palloc(sizeof(Entry));
    entry->key   = (char*)palloc(strlen(key) + 1);
    entry->value = (char*)palloc(strlen(value) + 1);
    entry->next  = NULL;
    strcpy(entry->key, key);
    strcpy(entry->value, value);
    if (oldcxt != NULL)
    {
        MemoryContextSwitchTo(oldcxt);
    }
    return entry;
}
geqo = off

好,你说我不这么麻烦了,我直接关了不就行了,哈哈,那你就遇到下一个坑,还是JOB过大的subquery导致的,在上面的HASH_TABLE_SIZE我们给count_table分配容量时候太小出现问题了,现在在CARD_PUSH_ANCHOR时存subquery2card又出现问题了,具体在调用函数store_aimodel_subquery2card()里面的,在src/backend/pilotscope/anchor2struct.c中:

int table_size = card_push_anchor->card_num * card_push_anchor->card_num;
table      = create_hashtable(table_size);

table是个全局对象

table_size定义为$card_num^2$,这就导致了card_num大于10k时,非常大,我们知道他是为了防止碰撞,但是这样需要分配太大的空间,毕竟都是简化实现的,这里也不存在什么动态扩容的代码(你要想实现你就自己写),所以也就选合理的table_size,保守一点就用「略大于 n 的素数」作为哈希表容量:

int is_prime(int n) {
    if (n < 2) return 0;
    if (n == 2) return 1;
    if (n % 2 == 0) return 0;
    for (int i = 3; i * i <= n; i += 2)
        if (n % i == 0)
            return 0;
    return 1;
}

int next_prime(int x) {
    if (x <= 2) return 2;
    if (x % 2 == 0) x++;
    while (!is_prime(x)) x += 2;
    return x;
}

使用:

// avoid hash confict
int n = card_push_anchor->card_num;
int table_size = next_prime(n * 2); 

多说一句,qeqo生成的subquery_2_card顺序和普通的dp生成的顺序是不一样的,具体可以自己debug看下

上面就是JOB所有踩坑的部分了,至少这么写能跑完JOB,如果有错误敬请指正

水平有限,本以为小bug,结果排查了很久。这个工作还是很有意思的,目前看只能大厂来做真正把AI4DB的东西放到db中,工作量太大了还不好发paper...

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions