Data write efficiency and storage cost are two core requirements for databases in large-data scenarios. Based on the LSM-Tree storage architecture, OceanBase Database provides parallel data loading through direct load, which bypasses the SQL layer and writes data directly to the underlying storage. It also encodes and compresses data during compaction, significantly reducing storage usage without affecting write performance. Based on a sample dataset of one million rows, this topic helps you experience first-hand how OceanBase Database improves write efficiency and optimizes storage cost.
This topic covers the following experiences:
- Prepare batch sample data: Generate one million rows of test data in CSV format.
- Experience parallel import: Use direct load with parallelism for bulk data ingestion and compare the elapsed time with that of a regular import.
- Verify import results: Check the number of imported rows to confirm data is fully ingested.
- Experience data encoding and compression: Create tables with different compression configurations and compare storage usage before and after compression.
- Verify query availability: Confirm that compressed data can be queried normally and the results are consistent.
Note
This topic is based on OceanBase Database V5.0.1 (Community Edition). The commands and feature descriptions in this topic apply to V5.0.1. This topic uses a single-node environment deployed with the All-in-One package as described in Deploy OceanBase as an example. All operations can be completed in this single-node environment.
Prerequisites
Before you proceed, ensure the following conditions are met:
Item |
Description |
|---|---|
| OceanBase Database | The deployment has been completed with the All-in-One package and the instance has been started successfully (the deployment name is demo). For more information, see Deploy OceanBase. |
| Server resources | We recommend that you have at least four CPU cores, 16 GB of memory, and 5 GB of free disk space. |
| obclient | obclient is installed and can connect to OceanBase Database normally. It is installed automatically when you deploy with the All-in-One package. |
| Access to the sys tenant | You can log in to the sys tenant of the cluster as the root user to perform management operations. The password for the root user of the sys tenant is randomly generated during deployment and is printed in the deployment output; you can also run obd cluster display demo to view it. |
| Multi-tenancy basics | This topic creates a tenant for the experience. For information about tenants and resource pools, see Experience multi-tenancy and resource isolation. |
Introduction to parallel import and high compression
Direct load
Normal data import goes through parsing, optimization, and execution at the SQL layer, writing data to the MemTable, which is then flushed to static data (SSTable) by background tasks. Direct load is an import execution mode that can be enabled by adding the DIRECT(...) hint to statements like LOAD DATA or INSERT INTO SELECT. Direct load bypasses the SQL layer and MemTable, writing data directly to the underlying storage (SSTable), resulting in a shorter execution path. It is suitable for the initial import of large tables, data migration of 10 GB to TB scales, and scenarios with limited CPU and memory.
When you enable direct load, you can specify the degree of parallelism with the parallel(N) hint. The database splits the import task into multiple parallel subtasks, which further improves loading throughput.
Data encoding and compression
OceanBase Database's LSM-Tree architecture ensures that data compression occurs only during compaction, without affecting data writes. When data is persisted to disk as SSTables, it is compressed at the microblock granularity in two levels:
Level |
Mechanism |
Description |
|---|---|---|
| Data encoding | Detect data similarity and encode by column | Row and column data are stored together within microblocks. Column data is encoded using methods such as dictionary encoding, Run Length Encoding (RLE), and differential encoding. The optimal encoding method is automatically selected during compaction. |
| General compression | Compress the encoded microblocks as a whole with a compression algorithm | Supports algorithms such as lz4, snappy, and zstd. Among them, zstd provides a higher compression ratio, while lz4 offers faster compression and decompression. |
In MySQL-compatible mode, encoding and compression behavior is controlled by the ROW_FORMAT and COMPRESSION clauses during table creation. The main values are as follows:
Configuration |
Value |
Meaning |
|---|---|---|
ROW_FORMAT |
REDUNDANT / COMPACT |
Data is not encoded and is saved in flat format. |
DYNAMIC (default) |
Data is encoded and saved in encoding format. | |
CONDENSED |
Data is encoded, using only query-friendly selective encoding. | |
COMPRESSION |
none |
Data is not compressed. |
lz4_1.0 / snappy_1.0 / lz4_1.9.1 |
Algorithms with fast compression and decompression. | |
zstd_1.0 / zstd_1.3.8 (default) |
Algorithms with a high compression ratio. |
In other words, new tables have data encoding enabled and use the zstd_1.3.8 compression algorithm by default. This topic compares storage usage between the "no encoding, no compression" and the "default encoding and compression" configurations.
Note
For architectural principles on compression and encoding, see Compression and Encoding. For detailed descriptions and modification methods of each value, see Use Data Encoding and Compression.
Create a test tenant
Log in to the sys tenant of the cluster as the root user (the password is randomly generated during deployment; obtain it as described in the prerequisites above):
obclient -h127.0.0.1 -uroot@sys -P2881 -p -A oceanbaseCreate a resource unit, resource pool, and tenant named
load_tenantwith specifications of 2C4G:obclient [oceanbase]> CREATE RESOURCE UNIT load_unit MAX_CPU=2, MIN_CPU=2, MEMORY_SIZE='4G'; obclient [oceanbase]> CREATE RESOURCE POOL load_pool UNIT='load_unit', UNIT_NUM=1, ZONE_LIST=('zone1'); obclient [oceanbase]> CREATE TENANT IF NOT EXISTS load_tenant CHARSET='utf8mb4', PRIMARY_ZONE='zone1', RESOURCE_POOL_LIST=('load_pool') SET ob_tcp_invited_nodes='%';Tenant creation takes tens of seconds (about 6 seconds in our test environment). No output is displayed during this period, which is normal; just wait for the command to return.
Log in to
load_tenant(the default password for the root user of a newly created tenant is empty, so just press Enter when prompted). All subsequent operations are performed in this tenant:obclient -h127.0.0.1 -uroot@load_tenant -P2881 -p -A test
Prepare batch sample data
On the machine where OceanBase is deployed, run the following command to generate one million rows of test data:
seq 1 1000000 | awk '{printf "%d,user_%06d,city_%d,%d\n", $1, $1, $1%20, $1%1000}' > /tmp/load_sample.csvThis command generates a CSV file containing the following fields:
id(an incremental number),user_name(username),city(a cyclic value from 20 cities), andamount(amount). Thecityhas only 20 values,user_namehas the sameuser_prefix, and the data contains a large amount of duplicate and similar content, making it easy to observe the compression effect.Check the data file:
head -3 /tmp/load_sample.csv wc -l /tmp/load_sample.csv ls -lh /tmp/load_sample.csvThe first 3 rows of data returned by the
headcommand are as follows:1,user_000001,city_1,1 2,user_000002,city_2,2 3,user_000003,city_3,3wc -lreturning1000000confirms that the number of rows is correct, andls -lhshows that the file size is about 29 MB. Record this file size, because it is compared with the actual storage usage in the database later.
Experience parallel import
This section creates three test tables with the same structure but different configurations, and imports the same dataset into each of them by using a different method:
Table name |
Import method |
Compression configuration |
Purpose |
|---|---|---|---|
load_normal |
Regular import (LOAD DATA) |
Default (encoding + zstd_1.3.8) | Time baseline for a regular import |
load_direct |
Direct load (DIRECT hint + parallelism) |
Default (encoding + zstd_1.3.8) | Compare import time; also serves as the compressed-storage baseline |
load_none |
Direct load (DIRECT hint + parallelism) |
No encoding, no compression | Storage baseline without encoding or compression |
Procedure
Log in to
load_tenantand create three test tables:obclient [test]> CREATE TABLE load_normal ( id BIGINT PRIMARY KEY, user_name VARCHAR(32), city VARCHAR(32), amount INT ); obclient [test]> CREATE TABLE load_direct ( id BIGINT PRIMARY KEY, user_name VARCHAR(32), city VARCHAR(32), amount INT ); obclient [test]> CREATE TABLE load_none ( id BIGINT PRIMARY KEY, user_name VARCHAR(32), city VARCHAR(32), amount INT ) ROW_FORMAT=REDUNDANT COMPRESSION='none';load_nonedisables both data encoding and compression by settingROW_FORMAT=REDUNDANT COMPRESSION='none'. The other two tables use the default configurations (ROW_FORMAT=DYNAMICandCOMPRESSION='zstd_1.3.8').Set the import file path. For security reasons, the
secure_file_privsystem variable can only be modified via a local Unix Socket connection:# The socket path depends on the deployment method. For OBD demo deployment, you can find it using the following command: find / -name sql.sock 2>/dev/null # Connect using the found socket path (example): obclient -S /path/to/sql.sock -uroot@load_tenant -pWhen prompted for the password, press Enter directly (the default password for the newly created tenant's root user is empty).
obclient [(none)]> SET GLOBAL secure_file_priv = "/tmp"; Query OK, 0 rows affectedsecure_file_privrestricts the file access paths for statements likeLOAD DATA INFILE. This is a global variable, so changes take effect on new connections. In the following steps, the import statements executed viaobclient -ewill each establish a new connection automatically, requiring no additional action. After setting it, you can open a new connection and runSHOW VARIABLES LIKE 'secure_file_priv';to confirm the setting. A return value of/tmpindicates successful configuration.Note
For detailed information about
secure_file_priv, see secure_file_priv.Execute the following command in the operating system terminal to import data into
load_normalby using the regular import method, and record the elapsed time:time obclient -h127.0.0.1 -uroot@load_tenant -P2881 -A test -e "SET ob_query_timeout=3600000000; LOAD DATA INFILE '/tmp/load_sample.csv' INTO TABLE load_normal FIELDS TERMINATED BY ',';"After the command finishes, the
timecommand outputs three statistics:real(actual elapsed time),user(CPU time in user space), andsys(CPU time in kernel space). Record therealvalue as the import time of the regular import.Execute the following command to import data into
load_directby using the direct load method, and record the elapsed time:time obclient -h127.0.0.1 -uroot@load_tenant -P2881 -A test -e "SET ob_query_timeout=3600000000; LOAD DATA /*+ direct(true,1024,'full') parallel(4) */ INFILE '/tmp/load_sample.csv' INTO TABLE load_direct FIELDS TERMINATED BY ',';"Key parameters in the hint:
Hint parameterDescriptiondirect(true,1024,'full')Enables full direct load: data is sorted before it is written, up to 1024 erroneous rows are tolerated, and the fullmode is used (suitable for the initial loading of empty tables).parallel(4)Sets the import parallelism to 4.Use direct load to import data into
load_none:time obclient -h127.0.0.1 -uroot@load_tenant -P2881 -A test -e "SET ob_query_timeout=3600000000; LOAD DATA /*+ direct(true,1024,'full') parallel(4) */ INFILE '/tmp/load_sample.csv' INTO TABLE load_none FIELDS TERMINATED BY ',';"Notice
- The default password for the root user of a new tenant is empty. The preceding command can be executed directly. If a password has been set, append
-ppasswordto the command (for example,-p'******'). Writing the password on the command line avoids interactive password entry from affecting the timing results. - The
SET ob_query_timeout=3600000000in the command temporarily adjusts the statement timeout for this session to 1 hour, preventing execution failure due to batch imports exceeding the default statement timeout (10 seconds). For details, see ob_query_timeout.
- The default password for the root user of a new tenant is empty. The preceding command can be executed directly. If a password has been set, append
Return to the obclient session and verify the number of rows imported for the three tables:
obclient [test]> SELECT COUNT(*) FROM load_normal; obclient [test]> SELECT COUNT(*) FROM load_direct; obclient [test]> SELECT COUNT(*) FROM load_none;All three statements return
1000000, which matches the number of rows in the source data file, indicating that all data has been imported:+----------+ | COUNT(*) | +----------+ | 1000000 | +----------+ 1 row in setCompare the
realtime recorded in step 3 with that recorded in step 4. In this example, the data volume is one million rows (about 29 MB), and the two import methods usually take a similar amount of time (about 3 seconds each in this environment), so the difference is not significant. This is normal: for a small data volume, the SQL-layer overhead of a regular import is negligible, and the advantage of direct load becomes apparent only for large-scale data of several GBs or more. To observe the difference, you can increase the data volume and compare again.
Interpretation
Observation |
Expected behavior |
Description |
|---|---|---|
| Import time | For this data volume, the two methods take a similar amount of time. | For a small data volume (less than one million rows), the SQL-layer overhead of a regular import is negligible. The advantage of direct load becomes apparent only for large datasets of several GBs or more. |
| Number of rows imported | All three tables contain 1,000,000 rows. | The number of rows matches the source file, and all data has been imported successfully. |
| Table status during direct load | The table is read-only. | A table lock is acquired during direct load, so no write operations are allowed while the import is running. |
Note
- Parallel import splits data into multiple subtasks for concurrent execution. Each subtask is treated as an independent transaction. Therefore, for tables without a primary key, the data write order may differ from the original file.
- Full direct load (
'full') is suitable for the initial loading of empty tables. When the target table already contains data, incremental direct load ('inc') can be used. Parallel direct load can also be enabled usingINSERT INTO SELECTstatements combined with theenable_parallel_dmlandparallelhints. For details, see Overview of Direct Load. - If the client and database are not on the same machine, you can use
LOAD DATA LOCAL INFILEto import from a local file on the client. Add the--local-infileparameter when connecting. For details, see Import Data Using the LOAD DATA Statement.
Experience data encoding and compression
After the import is complete, the table's encoding and compression configurations are applied during compaction to generate SSTables. This section manually triggers a round of major compaction, then compares the actual storage usage of three tables under different compression configurations, and verifies that compressed data can be queried normally.
Procedure
Log in to the sys tenant of the cluster as the root user and initiate a major compaction for
load_tenant:obclient -h127.0.0.1 -uroot@sys -P2881 -p -A oceanbaseobclient [oceanbase]> ALTER SYSTEM MAJOR FREEZE TENANT = load_tenant; Query OK, 0 rows affectedNote
Before initiating a major compaction, ensure the global major compaction switch is enabled (cluster-level parameter
enable_major_freeze, enabled by default). For details on how to trigger a major compaction, see Manually trigger a major compaction.Query the compaction status in the session of
load_tenant:obclient [test]> SELECT STATUS FROM oceanbase.DBA_OB_MAJOR_COMPACTION;The status returns
COMPACTING(compacting) orVERIFYING(verifying checksum) during compaction, andIDLEafter compaction is complete:+------------+ | STATUS | +------------+ | COMPACTING | +------------+ 1 row in setRepeat the preceding query until
STATUSchanges toIDLEbefore proceeding to the next step. A major compaction typically takes several minutes (about 6 minutes in this environment); you can query every 10–30 seconds during this period. If the compaction is already complete when queried,IDLEis displayed directly.Query the actual storage usage of the three tables:
obclient [test]> SELECT t.TABLE_NAME, r.DATA_SIZE, r.REQUIRED_SIZE -> FROM oceanbase.DBA_OB_TABLE_LOCATIONS t -> JOIN oceanbase.DBA_OB_TABLET_REPLICAS r ON t.TABLET_ID = r.TABLET_ID -> WHERE t.TABLE_NAME IN ('load_normal', 'load_direct', 'load_none') -> ORDER BY t.TABLE_NAME;A sample return result is as follows (the actual size depends on the data content, database version, and cluster status, so refer to the actual query result. Focus on the relative relationship among the three
DATA_SIZEvalues):+-------------+-----------+---------------+ | TABLE_NAME | DATA_SIZE | REQUIRED_SIZE | +-------------+-----------+---------------+ | load_direct | 4057224 | 8114448 | | load_none | 63569253 | 127138506 | | load_normal | 4184897 | 6040515 | +-------------+-----------+---------------+ 3 rows in setThe query results are interpreted as follows:
DATA_SIZE: The actual size of the data, that is, the amount of data stored after compression.load_nonedisables encoding and compression, so itsDATA_SIZEis significantly larger than those of the other two tables.load_directandload_normalenable the default encoding and compression, so the storage usage is much smaller (about 1/15 of that ofload_nonein this example).REQUIRED_SIZE: The allocated space for the data, including storage overhead such as alignment. Therefore,REQUIRED_SIZE >= DATA_SIZE.
Verify that compressed data can be queried normally. Execute the same query on both
load_directandload_none:obclient [test]> SELECT city, COUNT(*), SUM(amount) FROM load_direct WHERE city IN ('city_0', 'city_1') GROUP BY city; obclient [test]> SELECT city, COUNT(*), SUM(amount) FROM load_none WHERE city IN ('city_0', 'city_1') GROUP BY city;The results of the two tables are identical and consistent with the distribution pattern of the source data (one million rows are evenly distributed across 20 cities, with 50,000 rows per city value; the output order of
GROUP BYis not guaranteed and may differ from the following example):+--------+----------+-------------+ | city | COUNT(*) | SUM(amount) | +--------+----------+-------------+ | city_1 | 50000 | 24550000 | | city_0 | 50000 | 24500000 | +--------+----------+-------------+ 2 rows in setPerform point queries to verify that row-level data can be decoded and read normally:
obclient [test]> SELECT * FROM load_direct WHERE id = 500000;+--------+-------------+--------+--------+ | id | user_name | city | amount | +--------+-------------+--------+--------+ | 500000 | user_500000 | city_0 | 0 | +--------+-------------+--------+--------+ 1 row in set
Interpretation
Observation |
Expected behavior |
Description |
|---|---|---|
| Storage usage (DATA_SIZE) | load_none is significantly larger than load_direct and load_normal. |
Data encoding eliminates redundancy column by column, and general compression further compresses the microblock data. |
| Same data, same compression configuration | load_direct and load_normal have similar sizes. |
Storage usage is determined by the data content and the compression configuration, and is independent of the import method. |
| Query availability | The query results of a compressed table are identical to those of an uncompressed table. | Encoding and compression are completely transparent to the business. |
| Write performance | Compression does not slow down the import. | In the LSM-tree architecture, compression occurs only during compaction and does not affect data writes. |
Note
- The compression effect is related to data characteristics: the more duplicate values and similar prefixes there are in the data, the more pronounced the effects of mechanisms such as dictionary encoding and run-length encoding will be. The sample data in this topic contains a large number of duplicate
cityvalues and the sameuser_prefix, making the compression effect more intuitive. - When modifying the compression options for a table with an existing SSTable, you must gradually rewrite all microblock data through progressive compaction (
progressive_merge_num) to complete the change. For more information, see Use data encoding and compression.
Next steps
After you complete the experiences in this topic, you can explore the following:
- Experience data protection and recovery: Use flashback queries to restore data to its state before an accidental operation, and learn about comprehensive data protection capabilities such as backup and recovery. For more information, see Experience data protection and recovery.
- Experience online DDL: Execute common DDL operations such as adding columns and creating indexes on a test table with continuous read and write activity, and observe business continuity. For more information, see Experience online DDL.
- Deepen your understanding of data loading and stress testing: Learn about high-concurrency performance under Sysbench and TPC-C stress tests. For more information, see Experience high-concurrency OLTP scenarios.
References
- For more information about direct load, including its principles and full/incremental modes, see Overview of direct load.
- For more information about the
LOAD DATAstatement, see Import data by using the LOAD DATA statement. - For more information about the architecture of compression and encoding, see Compression and encoding.
- For more information about how to configure data encoding and compression, see Use data encoding and compression.
- For more information about manually triggering compactions and viewing their progress, see Manually trigger a major compaction and View major compaction information.
- For more information about view fields, see DBA_OB_TABLET_REPLICAS and DBA_OB_TABLE_LOCATIONS.