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

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions