Parallel Execution
CUBRID provides parallel query execution capabilities to efficiently process large amounts of data. Parallel query execution divides a single query into multiple work units, which are then processed by multiple worker threads simultaneously, dramatically reducing response time.
Overview
Parallel queries provide the following key features:
Parallel Heap Scan: Multiple worker threads divide and scan heap regions, improving large table scanning performance.
Parallel Subquery Execution: Independent subqueries (uncorrelated subqueries) are processed simultaneously by individual workers, improving query response time.
Parallel Hash Join: Parallelizes both the build and probe phases, improving response time during hash join operations.
Parallel Sort: Divides data to be sorted among multiple worker threads, sorts in parallel, then merges the results, improving sort response time.
Configuration
Parallel query execution can be controlled through system parameters and SQL hints.
Setting the parallelism parameter to 2 or higher enables the optimizer to determine parallel query execution during query processing.
Use the PARALLEL ( degree ) hint to explicitly specify the degree of parallelism for each query. degree is the number of workers to use and must be an integer value of 2 or higher. Hint-specified values take precedence over the parallelism parameter setting.
The max_parallel_workers parameter sets the maximum number of parallel worker threads that can be executed simultaneously across the entire server (default: 100).
Note
The max_parallel_workers and parallelism parameters are set to default values of 100 and 4 respectively, so you can use parallel queries without additional configuration.
Parallel Heap Scan
Parallel Heap Scan is a feature that improves heap table scanning performance by using multiple worker threads when scanning large amounts of data. Performance can be significantly improved over single-threaded heap scanning, especially when selectivity is low (typically 0.05 or less) and processing large amounts of data.
Heap Scan Overview
Parallel heap scan divides large tables into logical units for simultaneous scanning by multiple worker threads, with each worker thread independently scanning assigned pages while processing filter conditions (predicates). The processed results are collected through a result queue, and the main thread integrates these results to generate the final result and returns it to the user.
The NO_PARALLEL_HEAP_SCAN hint can be used to disable parallel heap scan. When used together with the PARALLEL hint, the NO_PARALLEL_HEAP_SCAN hint takes precedence.
Note
The actual degree of parallelism for parallel heap scan is automatically optimized by throughput rules within the user-configured upper limit. For more details, see Parallel Query Throughput Rules.
Constraints
If any of the following conditions apply, parallel heap scan is not supported and executes in single-threaded mode:
Statements that do not support concurrent processing
When using stored procedures (JavaSP, PL/CSQL)
When referencing session variables
When using Recursive CTE or Connect By clauses
When using CUBRID object DBMS specific features
Cases requiring exclusive lock (X-LOCK) acquisition
SELECT … FOR UPDATE clause
When using incr() function
update, delete, merge statements
When not the first (driving) table in a JOIN
When it is a correlated subquery
When reading data through index scan
Note
The default values of the max_parallel_workers and parallelism parameters provide defaults that allow parallel queries without additional settings. Performance can be further optimized by modifying these values in the cubrid.conf file according to system resources and application workload.
# cubrid.conf
max_parallel_workers=200 # default: 100
parallelism=8 # default: 4
-- Examples where parallel heap scan is not applied
-- Disabled by hint
SELECT /*+ NO_PARALLEL_HEAP_SCAN */ *
FROM large_table;
-- When using index scan
SELECT /*+ PARALLEL(4) */ *
FROM large_table
WHERE indexed_column = 100 using index idx_large_table_indexed_column;
-- SELECT FOR UPDATE
SELECT /*+ PARALLEL(4) */ *
FROM large_table
FOR UPDATE;
-- Using session variables
SET @user_id = 123;
SELECT /*+ PARALLEL(4) */ *
FROM orders
WHERE customer_id = @user_id;
-- Using SERIAL
SELECT /*+ PARALLEL(4) */ *, order_seq.NEXT_VALUE
FROM orders;
Heap Scan Performance Considerations
Parallel heap scan has significant performance improvements in the following cases:
When large table data needs to be scanned (more effective with more table pages)
When selectivity is low (approximately 0.05 or less)
When sufficient CPU cores are available
When CPU processing is the bottleneck rather than disk I/O
On the other hand, performance may degrade in the following cases:
When scanning small table data
When index scan is more efficient
When system resources (CPU, memory) are insufficient
When using parallel queries, the max_parallel_workers parameter should be set appropriately to prevent system resource contention. It is generally recommended to set it to the level of the actual physical CPU core count.
Heap Scan Optimization (Mergeable List)
Parallel heap scan operates with “mergeable list” optimization when certain conditions are met. In this method, each worker thread does not pass temporary results to the main thread but directly returns the final processed results to the main thread, significantly improving processing performance.
Especially when processing large amounts of data (approximately 10 million records or more) with 8 or more cores, it shows much faster performance than the row-by-row method (receiving results one by one from each thread).
Constraints
When the following conditions are met, the mergeable list optimization is not applied and row-by-row processing is used:
When the heap scan includes conditions that cannot be evaluated while scanning the target table
When performing hash aggregation (hash group by)
When there is a stored procedure (JavaSP or PL/CSQL) in the select-list
When ROWNUM is used
When performing topn_sort (sorting to extract top N)
When there is a LIMIT clause
When result_cache is enabled
Representative Application Examples
-- Simple table full scan without join
SELECT /*+ PARALLEL(8) */ *
FROM large_table
WHERE status = 'active';
-- Table full scan followed by ORDER BY
SELECT /*+ PARALLEL(8) */ *
FROM large_table
WHERE created_date > '2024-01-01'
ORDER BY id;
-- Parallel heap scan in uncorrelated subquery
SELECT *
FROM orders
WHERE customer_id IN (
SELECT /*+ PARALLEL(8) */ customer_id
FROM customers
WHERE region = 'Asia'
);
-- Apply parallel heap scan to each sub-SELECT in UNION statement
SELECT /*+ PARALLEL(8) */ order_id, customer_id, order_date
FROM orders_2023
WHERE status = 'completed'
UNION
SELECT /*+ PARALLEL(8) */ order_id, customer_id, order_date
FROM orders_2024
WHERE status = 'completed';
-- Parallel heap scan on partitioned table
SELECT /*+ PARALLEL(8) */ *
FROM sales_partitioned
WHERE sale_date BETWEEN '2024-01-01' AND '2024-12-31';
-- INSERT SELECT statement (copying large amounts of data)
INSERT INTO archive_orders
SELECT /*+ PARALLEL(8) */ *
FROM orders
WHERE order_date < '2023-01-01';
COUNT Optimization
Parallel heap scan provides special optimization mechanisms for COUNT(*), COUNT(column), and COUNT(DISTINCT column) aggregate functions, which are the most frequently used. This method works by having each worker thread first calculate intermediate counts within their scanned range, and then finally summing the results.
Conditions for COUNT Optimization
COUNT-specific optimization is applied when all of the following conditions are met:
Contains only COUNT(*), COUNT(column), or COUNT(DISTINCT column) aggregate functions
No ROWNUM or stored procedures in the condition clause
Simple query without other joins or subqueries
COUNT Optimization Operation
COUNT(*): Each worker increments a simple counter, and finally the main thread sums all worker counts
COUNT(column): Each worker counts only non-NULL values, and finally the main thread sums all worker counts
COUNT(DISTINCT column): Each worker stores values in a separate list file to remove duplicates and passes them on, and the main thread merges all lists received from workers to calculate the total DISTINCT count
COUNT Optimization Examples
-- COUNT(*) optimization
SELECT /*+ PARALLEL(8) */ COUNT(*)
FROM large_table
WHERE status = 'active';
-- COUNT(column) optimization
SELECT /*+ PARALLEL(8) */ COUNT(customer_id)
FROM orders
WHERE order_date > '2024-01-01';
-- COUNT(DISTINCT) optimization
SELECT /*+ PARALLEL(8) */ COUNT(DISTINCT customer_id)
FROM orders;
-- Usage in UPDATE STATISTICS
UPDATE STATISTICS ON large_table WITH FULLSCAN;
Note
COUNT optimization is a specialized optimization for simple aggregation. When used with other aggregate functions (SUM, AVG, etc.) or when complex joins are included, it is not applied and processing uses the general parallel heap scan method (mergeable list or row-by-row).
Heap Scan SQL Trace
When parallel heap scan is performed, parallel processing details are additionally output in the SQL trace results.
csql> ;trace on
SELECT /*+ PARALLEL(4) RECOMPILE */ count(*)
FROM large_table
WHERE status = 'active';
Trace Statistics:
SELECT (time: 2405, fetch: 143277, fetch_time: 1287, ioread: 123467)
SCAN (table: dba.large_table), (heap time: 2395, fetch: 143277, ioread: 123467, readrows: 0, rows: 0)
(parallel workers: 8, heap time: 2390..2395, readrows: 1249989..1250011,
rows: 1249989..1250011, gather: mergeable list)
The description of parallel heap scan trace output items is as follows:
parallel workers: Number of worker threads used
heap time: Range of heap scan time for each worker (min..max, milliseconds)
readrows: Range of rows read by each worker (min..max)
rows: Range of rows returned by each worker (min..max)
gather: Result collection method
mergeable list: Optimized method that directly uses each worker’s results without separate merging
row-by-row: Basic method that collects and merges each worker’s results one by one
count: COUNT-specific optimization method where each worker performs local counting and merges final results
When the gather item shows mergeable list or count, it indicates that parallel heap scan optimization is applied, showing better performance.
Note
The time and number of rows for parallel workers are displayed as ranges (min..max), and ideally all workers should perform similar amounts of work. If the range is wide, you may suspect data distribution or system resource contention issues.
COUNT Optimization Trace Information Example
When COUNT optimization is applied, gather: count is displayed:
csql> ;trace on
SELECT /*+ PARALLEL(8) RECOMPILE */ COUNT(*)
FROM large_table;
Trace Statistics:
SELECT (time: 1500, fetch: 1, fetch_time: 10, ioread: 100000)
SCAN (table: dba.large_table), (heap time: 1490, fetch: 100000, ioread: 100000, readrows: 0, rows: 0)
(parallel workers: 8, heap time: 1485..1490, readrows: 1250000..1250000,
rows: 0..0, gather: count)
COUNT optimization shows rows as 0 because the result is a single row, and the actual count result is returned through the aggregate function.
Parallel Subquery Execution
Parallel Subquery Execution is a feature that improves query performance by using multiple worker threads to simultaneously execute subqueries that can run independently.
Subquery Execution Overview
Since subqueries can be executed independently of other subqueries, when there are multiple subqueries, executing them in parallel can reduce overall query response time. Each subquery is executed in an independent worker thread, and when all subqueries are completed, the results are merged to generate the final result.
Parallel execution of subqueries is possible if the parallelism parameter is set to 2 or higher, or if the degree of parallelism is specified to 2 or higher using the PARALLEL ( degree ) hint.
The NO_PARALLEL_SUBQUERY hint can be used to disable parallel execution of subqueries. When used together with the PARALLEL hint, the NO_PARALLEL_SUBQUERY hint takes precedence.
Execution Conditions
Parallel execution of subqueries is possible when all of the following conditions are met:
The max_parallel_workers parameter is set to 2 or higher, and available worker threads exist
The parallelism parameter is set to 2 or higher, or a PARALLEL (2) or higher hint is specified
The subquery is directly connected to the top-level query (top-level XASL)
The NO_PARALLEL_SUBQUERY hint is not used
-- parallelism parameter setting (cubrid.conf)
-- parallelism=4
-- Subquery parallel execution example
SELECT *
FROM orders
WHERE customer_id IN (
SELECT customer_id FROM customers WHERE region = 'Asia'
)
AND product_id IN (
SELECT product_id FROM products WHERE category = 'Electronics'
);
-- Example using hint
SELECT /*+ PARALLEL(4) */ *
FROM orders
WHERE customer_id IN (
SELECT customer_id FROM customers WHERE region = 'Asia'
)
AND product_id IN (
SELECT product_id FROM products WHERE category = 'Electronics'
);
Cases Not Applied
Parallel execution of subqueries is not applied if any of the following conditions apply:
The subquery is not directly connected to the top-level query (such as subqueries inside nested subqueries)
CTE (Common Table Expression) recursive part exists or there are references between CTEs
References between subqueries exist due to derived tables (inline views), etc.
Object DBMS features are used (such as path expressions, etc.)
JSON_TABLE or SET type table scans are included
The subquery condition clause contains stored procedures
When it is a correlated subquery
-- Examples where parallel execution is not applied
-- Using NO_PARALLEL_SUBQUERY hint
-- Two subqueries exist, but parallel execution is disabled by hint
SELECT /*+ NO_PARALLEL_SUBQUERY */ *
FROM orders
WHERE customer_id IN (
SELECT customer_id FROM customers WHERE region = 'Asia'
)
AND product_id IN (
SELECT product_id FROM products WHERE category = 'Electronics'
);
-- When there are references between CTEs
-- cte2 references cte1, so they are not independent
WITH cte1 AS (
SELECT * FROM table1
),
cte2 AS (
SELECT * FROM cte1 WHERE id > 100 -- references cte1
)
SELECT * FROM cte2;
-- Using JSON_TABLE
-- When JSON_TABLE is included, parallel execution is not applied even with 2+ subqueries
SELECT *
FROM orders,
JSON_TABLE(json_column, '$[*]' COLUMNS(id INT PATH '$.id')) AS jt
WHERE customer_id IN (
SELECT customer_id FROM customers WHERE region = 'Asia'
)
AND product_id IN (
SELECT product_id FROM products WHERE category = 'Electronics'
);
-- When stored procedure is included in condition clause
-- Two subqueries exist, but one contains a stored procedure, so no parallel execution
SELECT *
FROM orders
WHERE customer_id IN (
SELECT customer_id FROM customers WHERE check_region_sp(region) = 1
)
AND product_id IN (
SELECT product_id FROM products WHERE category = 'Electronics'
);
Subquery Performance Considerations
Parallel execution of subqueries has significant performance improvements in the following cases:
When multiple independent subqueries exist
When each subquery’s execution time is sufficiently long
When sufficient CPU cores are available
On the other hand, performance may degrade in the following cases:
When subquery execution time is very short (parallel processing overhead may be greater)
When only one subquery exists
When system resources (CPU, memory) are insufficient
When max_parallel_workers setting is inappropriate
Subquery Trace Information
When parallel execution of subqueries is performed, parallel processing details are additionally output in the SQL trace results.
csql> ;trace on
SELECT /*+ PARALLEL(4) RECOMPILE */ *
FROM orders
WHERE customer_id IN (
SELECT customer_id FROM customers WHERE region = 'Asia'
)
AND product_id IN (
SELECT product_id FROM products WHERE category = 'Electronics'
);
Trace Statistics:
SELECT (time: 1710, fetch: 51619, fetch_time: 5, ioread: 0)
SCAN (temp time: 0, fetch: 0, ioread: 0, readrows: 125, rows: 125)
SCAN (table: dba.orders), (heap time: 1677, fetch: 51500, ioread: 0, readrows: 12500000, rows: 25000)
SCAN (hash temp(m), build time: 0, time: 0, fetch: 0, ioread: 0, readrows: 350, rows: 17)
SUBQUERY (uncorrelated)
(parallel workers: 2, time: 0, fetch: 9, fetch_time: 0, ioread: 0)
SELECT (time: 0, fetch: 5, fetch_time: 0, ioread: 0)
SCAN (table: dba.customers), (heap time: 0, fetch: 4, ioread: 0, readrows: 1000, rows: 333)
ORDERBY (time: 0, sort: true, page: 0, ioread: 0)
SELECT (time: 0, fetch: 4, fetch_time: 0, ioread: 0)
SCAN (table: dba.products), (heap time: 0, fetch: 3, ioread: 0, readrows: 500, rows: 125)
ORDERBY (time: 0, sort: true, page: 0, ioread: 0)
The description of subquery parallel execution SQL trace output items is as follows:
SUBQUERY (uncorrelated): Indicates subquery execution
parallel workers: Number of worker threads used for parallel execution
time: Time spent on parallel execution (milliseconds)
Each subquery is displayed as an independent SELECT, and execution statistics for each are output
In the above example, 2 subqueries (customers table query, products table query) were executed in parallel using 2 worker threads.
Parallel Query Throughput Rules
Throughput Rules Overview
While parallel query execution dramatically reduces query response time, it also consumes significant server resources (CPU, memory, I/O, etc.). If a small number of queries excessively occupy server resources through parallel execution, the performance of other queries may degrade. To prevent this, CUBRID applies throughput rules to selectively allow parallel execution only for queries with significant parallel execution benefits.
The actual degree of parallelism for each parallel operation is determined by the following factors:
Throughput rules such as table size and number of partitions
Values explicitly specified by PARALLEL hints
Upper limit set by the parallelism parameter
Global worker pool size set by the max_parallel_workers parameter
The degree of parallelism calculated by throughput rules cannot exceed the parallelism parameter value. The degree of parallelism specified by hints can exceed the parallelism parameter value but cannot exceed the maximum value (the smaller of 32 or the number of system cores).
Heap Scan Throughput Rules
The degree of parallelism for parallel heap scan is determined by the number of pages in the target table to be scanned.
Activation Condition
Activated when the target table has 4,096 or more pages (approximately 64MB when db_page_size is 16K)
If this condition is not met, parallel heap scan is not activated even if the PARALLEL hint is present
Degree Determination
The degree of parallelism is determined according to the number of pages in the table as follows:
Number of Pages |
Throughput |
Throughput Rule Calculation |
|---|---|---|
2,048 |
32 MB |
2 |
4,096 |
64 MB |
3 |
8,192 |
128 MB |
4 |
16,384 |
256 MB |
5 |
32,768 |
512 MB |
6 |
65,536 |
1.0 GB |
7 |
131,072 |
2.0 GB |
8 |
262,144 |
4.0 GB |
9 |
524,288 |
8.0 GB |
10 |
1,048,576 |
16.0 GB |
11 |
2,097,152 |
32.0 GB |
12 |
4,194,304 |
64.0 GB |
13 |
8,388,608 |
128.0 GB |
14 |
Starting from 2,048 pages, the degree of parallelism calculated by throughput rule increases by 1 each time the number of pages doubles from the previous increase threshold.
The degree of parallelism determined by throughput rules cannot exceed the parallelism parameter value:
MIN (throughput rule calculation, parallelism parameter value)
For example, when parallelism=4 (default):
Page count 4,096 → throughput rule calculates 2 → MIN(2, 4) = 2 applied
Page count 65,536 → throughput rule calculates 6 → MIN(6, 4) = 4 applied (cannot exceed parallelism)
Note
When the degree of parallelism is explicitly specified using the PARALLEL hint, the throughput rules are not applied and the hint value is used.
Example
-- Create table and insert data
CREATE TABLE large_table (c1 INT);
INSERT INTO large_table
WITH RECURSIVE cte (n) AS (
SELECT 1
UNION ALL
SELECT n + 1 FROM cte WHERE n < 2000
)
SELECT ROWNUM FROM cte a, cte b, cte c LIMIT 2200000;
UPDATE STATISTICS ON large_table WITH FULLSCAN;
-- Check table statistics
-- Total pages in class heap: 4215 (approximately 66MB when db_page_size is 16K)
-- Total objects: 2200000
-- When parallelism parameter is set to 4
-- Page count 4215 is 4,096 or more, so degree of parallelism 2 is automatically applied
SELECT COUNT(*) FROM large_table;
-- Explicit specification with hint
SELECT /*+ PARALLEL(8) */ COUNT(*) FROM large_table;
Hash Join Throughput Rules
The degree of parallelism for parallel hash join is determined according to throughput rules, and the determined degree of parallelism should be less than or equal to the number of partitions.
Note
Detailed throughput rules for parallel hash join will be added in future versions.
Sort Throughput Rules
The degree of parallelism for parallel sort is determined according to throughput rules, and the determined degree of parallelism should be less than or equal to the number of input pages.
Note
Detailed throughput rules for parallel sort will be added in future versions.
Subquery Throughput Rules
Parallel execution of subqueries is activated when multiple subqueries have an independent structure where they do not reference each other’s results.
The degree of parallelism for subquery parallel execution is fixed at 2. For example, even if there are 4 independent subqueries within a query, the system allocates 2 parallel workers for processing.
Whether each subquery is executed in parallel is determined by the “throughput rules”.
When multiple independent subqueries exist, the effect of parallel execution is significant
-- Parallel subquery execution example
-- When parallelism=4, 2 subqueries are executed in parallel
SELECT *
FROM orders
WHERE customer_id IN (
SELECT customer_id FROM customers WHERE region = 'Asia'
)
AND product_id IN (
SELECT product_id FROM products WHERE category = 'Electronics'
);
Worker Thread Pool Management
If the parallel thread pool is insufficient, only some operations may be performed in parallel, or parallel execution may not be performed at all.
Set the maximum thread count for the global parallel processing worker pool with the max_parallel_workers parameter
Each worker thread reserves the required number of parallel workers from the parallel worker pool before parallel query execution, and returns them after task completion
If the reservation fails, the query is executed in the normal single-threaded manner
The sum of the degrees of parallelism used in the entire query can exceed the parallelism parameter value, but cannot exceed the max_parallel_workers value
-- cubrid.conf configuration example
max_parallel_workers=100 # Global worker pool size
parallelism=4 # Upper limit for single parallel operation
Throughput Performance Considerations
Optimization through parallel query throughput rules:
Prevents unnecessary parallel execution on small tables to reduce overhead
Automatically adjusts the degree of parallelism proportional to table size
Prevents system resource contention due to excessive parallel execution
Concentrates parallel resources on queries with significant benefits
Recommended Settings:
max_parallel_workers: Set considering the number of simultaneously executable parallel queries and the average degree of parallelism for each query
parallelism: Set considering the number of physical cores in the system (usually 4~8 is appropriate)
In environments with many large tables, set max_parallel_workers value high
In environments with many small tables, using default values is recommended