JOB bugs
- Dominant language
- Python
- Stars
- 167
- Forks
- 27
- PR merge metrics
- No merged PRs in 30d
Description
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_ANCHOR`和`CARD_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`
这个函数里面的
```sql
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`的
```sql
geqo_eval(PlannerInfo *root, Gene *tour, int num_gene)
```
就不贴详细代码了,自己看源码,具体就是遗传算法期间临时创建一个内存 cxt,用完会被释放
```c
mycontext = AllocSetContextCreate(CurrentMemoryContext,
"GEQO",
ALLOCSET_DEFAULT_SIZES);
```
所以在 subquery 调用的时候会调用`src/backend/pilotscope/utils/hashtable.c`的`create_entry`函数创建 entry:
```c
Entry* entry = (Entry*)palloc(sizeof(Entry));
entry->key = (char*)palloc(strlen(key) + 1);
entry->value = (char*)palloc(strlen(value) + 1);
```
分配内存空间会被分配到`GEQO`这个 cxt 上
```c
mycontext = AllocSetContextCreate(CurrentMemoryContext,
"GEQO",
ALLOCSET_DEFAULT_SIZES);
```
如果没有使用就是使用`MessageContext`,`GEQO`是他的 children(pg里的内存是树状的)
所以当 GEQO 的内存在释放后,之前创建的Entry是存在 count_table 的,但是GEQO会释放这块的内存,导致后面从count_table中get时候内存找不到对应数据,只是为了解决这个bug的话,我就把create_entry时单独处理了一下,感觉不是很好的处理方式,仅作思路参考:
```c
// 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`中:
```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 的素数**」作为哈希表容量:
```c
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;
}
```
使用:
```c
// 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...
Contributor guide
No contributing guide indexed for this repository
Research direction
Start by reproducing the JOB benchmark, then inspect HASH_TABLE_SIZE in src/backend/pilotscope/parse_json.c and create_entry in src/backend/pilotscope/utils/hashtable.c. Compare the geqo paths through src/backend/optimizer/path/allpaths.c and src/backend/optimizer/geqo/geqo_eval.c, then inspect store_aimodel_subquery2card() in src/backend/pilotscope/anchor2struct.c. Done means JOB completes reliably with both geqo settings without lost entries or hash-table failures.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- c, postgresql
- Domain
- databases
- Issue type
- Bug
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Stale
- Clarity
- Needs clarification
- Newbie friendliness
- 30/100