Skip to content

Commit df05803

Browse files
committed
MDEV-39491 Add test harness
This commit implements pseudo-parallel execution of SELECTs which allows to test the correctness of parallel algorithms. Eligible InnoDB tables that were planned to be either full-scanned or scanned on a set of ranges of the clustered index, are switched to the pseudo-parallel execution. That means the primary index is split into chunks, and those chunks are processed one after another by a single thread. This thread mimics the parallel execution by calling the parallel handler API and acting as both the coordinator and the worker. This mode is activated automatically, there is no need to set any variables before that. If an InnoDB table is set to be either full-scanned or scanned on a set of ranges of the primary index, the pseudo-parallel mode is employed. This harness allows to run MTR tests to catch possible bugs in the parallel logic implementation.
1 parent fdcb391 commit df05803

6 files changed

Lines changed: 183 additions & 3 deletions

File tree

sql/handler.cc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4116,6 +4116,10 @@ int handler::ha_parallel_get_next_row(Parallel_worker_ctx *ctx)
41164116
}
41174117
status_var_increment(table->in_use->status_var.ha_read_rnd_next_count);
41184118

4119+
/* Same sync point as ha_rnd_next(): a parallel scan is still a table scan
4120+
as far as the tests that park a scan mid-flight are concerned. */
4121+
DEBUG_SYNC(ha_thd(), "handler_rnd_next_end");
4122+
41194123
DBUG_RETURN(result);
41204124
}
41214125

sql/opt_range.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1403,6 +1403,8 @@ class QUICK_RANGE_SELECT : public QUICK_SELECT_I
14031403
void save_last_pos() override
14041404
{ file->position(record); }
14051405
int get_type() override { return QS_TYPE_RANGE; }
1406+
/* Number of disjoint key intervals this scan covers. */
1407+
uint num_ranges() const { return ranges.elements; }
14061408
void add_keys_and_lengths(String *key_names, String *used_lengths) override;
14071409
Explain_quick_select *get_explain(MEM_ROOT *alloc) override;
14081410
#ifndef DBUG_OFF

sql/records.cc

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -852,3 +852,14 @@ int read_record_func_for_rr_and_unpack(READ_RECORD *info)
852852

853853
return error;
854854
}
855+
856+
int parallel_rr_next(READ_RECORD *info)
857+
{
858+
TABLE *table = info->table;
859+
handler *file = table->file;
860+
861+
int err = file->ha_parallel_get_next_row(info->parallel_worker_ctx);
862+
if (err == HA_ERR_END_OF_FILE)
863+
return -1;
864+
return err == 0 ? 0 : rr_handle_error(info, err);
865+
}

sql/records.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,8 +83,9 @@ struct READ_RECORD
8383
*/
8484
Copy_field *copy_field;
8585
Copy_field *copy_field_end;
86+
Parallel_worker_ctx *parallel_worker_ctx;
8687
public:
87-
READ_RECORD() : table(NULL), cache(NULL) {}
88+
READ_RECORD() : table(NULL), cache(NULL), parallel_worker_ctx(NULL) {}
8889
~READ_RECORD() { end_read_record(this); }
8990
};
9091

sql/sql_select.cc

Lines changed: 163 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2792,6 +2792,149 @@ JOIN::optimize_inner()
27922792
DBUG_RETURN(0);
27932793
}
27942794

2795+
enum class Parallel_scan_type
2796+
{
2797+
NONE,
2798+
FULL_SCAN,
2799+
RANGE_SCAN
2800+
};
2801+
2802+
static
2803+
Parallel_scan_type get_applicable_parallel_scan_type(JOIN_TAB *join_tab)
2804+
{
2805+
if (!(join_tab->type == JT_ALL &&
2806+
join_tab->read_first_record == join_init_read_record &&
2807+
join_tab->table->s->tmp_table == NO_TMP_TABLE &&
2808+
join_tab->table->s->blob_fields == 0 &&
2809+
!join_tab->table->fulltext_searched &&
2810+
#ifdef WITH_PARTITION_STORAGE_ENGINE
2811+
!join_tab->table->part_info &&
2812+
#endif
2813+
(join_tab->table->file->ha_table_flags() & HA_CAN_PARALLEL_SCAN)))
2814+
{
2815+
return Parallel_scan_type::NONE;
2816+
}
2817+
2818+
/*
2819+
The plan may be relying on this table's rows arriving in index order to
2820+
satisfy ORDER BY or GROUP BY without a filesort, but a parallel scan
2821+
does not preserve that order. Reject parallelization in that case.
2822+
*/
2823+
if (join_tab->join->ordered_index_usage != JOIN::ordered_index_void)
2824+
return Parallel_scan_type::NONE;
2825+
2826+
if (join_tab->filesort || join_tab->filesort_result ||
2827+
join_tab->need_to_build_rowid_filter || join_tab->rowid_filter ||
2828+
join_tab->distinct ||
2829+
join_tab->table->file->keyread_enabled())
2830+
return Parallel_scan_type::NONE;
2831+
2832+
SQL_SELECT *sql_select= join_tab->select;
2833+
2834+
if (sql_select && sql_select->quick)
2835+
{
2836+
/*
2837+
The case of a range scan.
2838+
Only a plain range over the clustered index can be handed to the
2839+
parallel coordinator, and only if it has few enough intervals to be
2840+
worth splitting.
2841+
*/
2842+
const uint MAX_PARALLEL_SCAN_RANGES= 128;
2843+
if (sql_select->quick->get_type() == QUICK_SELECT_I::QS_TYPE_RANGE &&
2844+
sql_select->quick->index == join_tab->table->s->primary_key &&
2845+
join_tab->use_quick != 2 /*exclude dynamic range*/ &&
2846+
((QUICK_RANGE_SELECT*) sql_select->quick)->num_ranges() <=
2847+
MAX_PARALLEL_SCAN_RANGES)
2848+
{
2849+
return Parallel_scan_type::RANGE_SCAN;
2850+
}
2851+
else
2852+
{
2853+
return Parallel_scan_type::NONE;
2854+
}
2855+
}
2856+
else
2857+
{
2858+
return Parallel_scan_type::FULL_SCAN;
2859+
}
2860+
}
2861+
2862+
extern int parallel_rr_next(READ_RECORD *info);
2863+
2864+
/*
2865+
Snapshot the quick select's key intervals into the `ranges` array.
2866+
An empty result means "scan the whole table".
2867+
2868+
@return true on error (my_error() has been called)
2869+
*/
2870+
static bool parallel_build_key_ranges(JOIN_TAB *tab,
2871+
Dynamic_array<KEY_MULTI_RANGE> *ranges)
2872+
{
2873+
if (!tab->use_parallel_scan || !tab->select || !tab->select->quick)
2874+
return false;
2875+
2876+
QUICK_RANGE_SELECT *quick= (QUICK_RANGE_SELECT*) tab->select->quick;
2877+
range_seq_t seq= quick_range_seq_init(quick, 0, 0);
2878+
KEY_MULTI_RANGE range;
2879+
2880+
while (!quick_range_seq_next(seq, &range))
2881+
{
2882+
if (ranges->append(range))
2883+
return true;
2884+
}
2885+
return false;
2886+
}
2887+
2888+
int parallel_init_read_record(JOIN_TAB *tab)
2889+
{
2890+
TABLE *table = tab->table;
2891+
handler *file = table->file;
2892+
2893+
/*
2894+
parallel_init_coordinator() copies what it keeps, so these only have to
2895+
outlive the call. prealloc=0 defers the allocation until there is
2896+
something to store: a full scan contributes no intervals at all.
2897+
*/
2898+
Dynamic_array<KEY_MULTI_RANGE> ranges(PSI_INSTRUMENT_MEM, 0, 16);
2899+
if (parallel_build_key_ranges(tab, &ranges))
2900+
return 1;
2901+
2902+
const size_t ARBITRARY_WORKERS_NUM = 4;
2903+
int err= file->parallel_init_coordinator(ARBITRARY_WORKERS_NUM, ranges);
2904+
if (err == HA_ERR_UNSUPPORTED)
2905+
{
2906+
// Fall back to the serial record reader
2907+
tab->read_first_record= join_init_read_record;
2908+
tab->use_parallel_scan= false;
2909+
return join_init_read_record(tab);
2910+
}
2911+
if (err)
2912+
{
2913+
file->print_error(err, MYF(0));
2914+
return 1;
2915+
}
2916+
2917+
tab->read_record.table = tab->table;
2918+
tab->read_record.thd = tab->join->thd;
2919+
tab->read_record.read_record_func = parallel_rr_next;
2920+
tab->read_record.print_error = TRUE;
2921+
2922+
Parallel_worker_ctx *worker_ctx= file->parallel_get_worker_context(0);
2923+
DBUG_ASSERT(worker_ctx);
2924+
err= file->parallel_init_worker(worker_ctx);
2925+
if (err == HA_ERR_END_OF_FILE)
2926+
return -1; // No rows — read_first_record's "empty result" sentinel
2927+
if (err)
2928+
{
2929+
// Real error from the engine
2930+
file->print_error(err, MYF(0));
2931+
return 1;
2932+
}
2933+
tab->read_record.parallel_worker_ctx = worker_ctx;
2934+
2935+
// Fetch the first row before returning — this is what join_init_read_record does.
2936+
return tab->read_record.read_record();
2937+
}
27952938

27962939
/*
27972940
@brief
@@ -3581,6 +3724,20 @@ int JOIN::optimize_stage2()
35813724
if (init_range_rowid_filters())
35823725
DBUG_RETURN(1);
35833726

3727+
/*
3728+
Parallel-scan decision must come last: everything above can still
3729+
change the first table's access method.
3730+
*/
3731+
{
3732+
JOIN_TAB *first= first_linear_tab(this, WITH_BUSH_ROOTS,
3733+
WITHOUT_CONST_TABLES);
3734+
if (first && !(select_options & SELECT_DESCRIBE) &&
3735+
get_applicable_parallel_scan_type(first) != Parallel_scan_type::NONE)
3736+
{
3737+
first->use_parallel_scan= true;
3738+
first->read_first_record= parallel_init_read_record;
3739+
}
3740+
}
35843741
error= 0;
35853742

35863743
if (select_options & SELECT_DESCRIBE)
@@ -16326,7 +16483,6 @@ void JOIN_TAB::remove_redundant_bnl_scan_conds()
1632616483
set_cond(NULL);
1632716484
}
1632816485

16329-
1633016486
/*
1633116487
Plan refinement stage: do various setup things for the executor
1633216488

@@ -16662,7 +16818,6 @@ make_join_readinfo(JOIN *join, ulonglong options, uint no_jbuf_after)
1666216818
break;
1666316819
}
1666416820
}
16665-
1666616821
DBUG_RETURN(FALSE);
1666716822
}
1666816823

@@ -16796,6 +16951,12 @@ void JOIN_TAB::cleanup()
1679616951
table->file->ha_ft_end();
1679716952
else if (table->hlindex && table->hlindex->context)
1679816953
table->hlindex_read_end();
16954+
else if (use_parallel_scan)
16955+
{
16956+
table->file->parallel_end_worker();
16957+
table->file->parallel_end_coordinator();
16958+
read_record.parallel_worker_ctx= NULL;
16959+
}
1679916960
else
1680016961
table->file->ha_index_or_rnd_end();
1680116962
preread_init_done= FALSE;

sql/sql_select.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -428,6 +428,7 @@ typedef struct st_join_table {
428428
bool shortcut_for_distinct;
429429
bool sorted;
430430
bool cached_pfs_batch_update;
431+
bool use_parallel_scan;
431432

432433
/*
433434
If it's not 0 the number stored this field indicates that the index

0 commit comments

Comments
 (0)