为什么数据库爱上对象存储这块洼地? pg_parquet 来了!
参考文档点击文末阅读原文打开; 推荐《最好的PostgreSQL学习镜像》;
数据库为什么偏爱对象存储这块洼地? pg_parquet 来了!
PostgreSQL rust写的插件(采用pgrx框架) pg_parquet
https://github.com/CrunchyData/pg_parquet
pg_parquet是CrunchyData开源的PG插件, 用于将数据导入s3对象存储的parquet文件中, 或从s3对象存储的parquet文件中将数据导入到数据库表内. 并没有查询parquet数据/JOIN PG本地表等功能, 仅仅用于数据导入导出, 仅适合数据交换和归档场景.
说起来的话功能不如pg_duckdb/pg_mooncake齐全.
后期将集成到宇宙最强PostgreSQL学习镜像中:
《2023-PostgreSQL/DuckDB/MySQL/PolarDB-X Docker镜像学习环境 ARM64版, 已集成热门插件和工具》
《2023-PostgreSQL/DuckDB/MySQL/PolarDB-X Docker镜像学习环境 AMD64版, 已集成热门插件和工具》
安装 pg_parquet (rust写的PG插件基本上都是类似安装步骤)
cd /tmp
git clone --depth 1 https://github.com/CrunchyData/pg_parquet/
# rustup self uninstall # 如有必要, 可以删除rust老版本后重新安装rust
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
. "/root/env"
# grep pgrx Cargo.toml 查看依赖的pgrx版本
cargo install --locked --version 0.12.6 cargo-pgrx
cargo pgrx init # 创建PGRX_HOME后立即 Ctrl^C 退出
cargo pgrx init --pg14=`which pg_config`
PGRX_IGNORE_RUST_VERSIONS=y cargo pgrx install --pg-config `which pg_config`
echo "shared_preload_libraries = 'pg_parquet'" >> $PGDATA/postgresql.auto.conf
Usage
There are mainly 3 things that you can do with pg_parquet:
You can export Postgres tables/queries to Parquet files, You can ingest data from Parquet files to Postgres tables, You can inspect the schema and metadata of Parquet files.
COPY to/from Parquet files from/to Postgres tables
You can use PostgreSQL's COPY command to read and write Parquet files. Below is an example of how to write a PostgreSQL table, with complex types, into a Parquet file and then to read the Parquet file content back into the same table.
-- create composite types
CREATE TYPE product_item AS (id INT, name TEXT, price float4);
CREATE TYPE product AS (id INT, name TEXT, items product_item[]); -- create a table with complex types
CREATE TABLE product_example (
id int,
product product,
products product[],
created_at TIMESTAMP,
updated_at TIMESTAMPTZ
);
-- insert some rows into the table
insert into product_example values (
1,
ROW(1, 'product 1', ARRAY[ROW(1, 'item 1', 1.0), ROW(2, 'item 2', 2.0), NULL]::product_item[])::product,
ARRAY[ROW(1, NULL, NULL)::product, NULL],
now(),
'2022-05-01 12:00:00-04'
);
-- copy the table to a parquet file
COPY product_example TO '/tmp/product_example.parquet' (format 'parquet', compression 'gzip');
-- show table
SELECT * FROM product_example;
-- copy the parquet file to the table
COPY product_example FROM '/tmp/product_example.parquet';
-- show table
SELECT * FROM product_example;
Inspect Parquet schema
You can call SELECT * FROM parquet.schema(<uri>) to discover the schema of the Parquet file at given uri.
SELECT * FROM parquet.schema('/tmp/product_example.parquet') LIMIT 10;
uri | name | type_name | type_length | repetition_type | num_children | converted_type | scale | precision | field_id | logical_type
------------------------------+--------------+------------+-------------+-----------------+--------------+----------------+-------+-----------+----------+--------------
/tmp/product_example.parquet | arrow_schema | | | | 5 | | | | |
/tmp/product_example.parquet | id | INT32 | | OPTIONAL | | | | | 0 |
/tmp/product_example.parquet | product | | | OPTIONAL | 3 | | | | 1 |
/tmp/product_example.parquet | id | INT32 | | OPTIONAL | | | | | 2 |
/tmp/product_example.parquet | name | BYTE_ARRAY | | OPTIONAL | | UTF8 | | | 3 | STRING
/tmp/product_example.parquet | items | | | OPTIONAL | 1 | LIST | | | 4 | LIST
/tmp/product_example.parquet | list | | | REPEATED | 1 | | | | |
/tmp/product_example.parquet | items | | | OPTIONAL | 3 | | | | 5 |
/tmp/product_example.parquet | id | INT32 | | OPTIONAL | | | | | 6 |
/tmp/product_example.parquet | name | BYTE_ARRAY | | OPTIONAL | | UTF8 | | | 7 | STRING
(10 rows)
Inspect Parquet metadata
You can call SELECT * FROM parquet.metadata(<uri>) to discover the detailed metadata of the Parquet file, such as column statistics, at given uri.
SELECT uri, row_group_id, row_group_num_rows, row_group_num_columns, row_group_bytes, column_id, file_offset, num_values, path_in_schema, type_name FROM parquet.metadata('/tmp/product_example.parquet') LIMIT 1;
uri | row_group_id | row_group_num_rows | row_group_num_columns | row_group_bytes | column_id | file_offset | num_values | path_in_schema | type_name
------------------------------+--------------+--------------------+-----------------------+-----------------+-----------+-------------+------------+----------------+-----------
/tmp/product_example.parquet | 0 | 1 | 13 | 842 | 0 | 0 | 1 | id | INT32
(1 row)
SELECT stats_null_count, stats_distinct_count, stats_min, stats_max, compression, encodings, index_page_offset, dictionary_page_offset, data_page_offset, total_compressed_size, total_uncompressed_size FROM parquet.metadata('/tmp/product_example.parquet') LIMIT 1;
stats_null_count | stats_distinct_count | stats_min | stats_max | compression | encodings | index_page_offset | dictionary_page_offset | data_page_offset | total_compressed_size | total_uncompressed_size
------------------+----------------------+-----------+-----------+--------------------+--------------------------+-------------------+------------------------+------------------+-----------------------+-------------------------
0 | | 1 | 1 | GZIP(GzipLevel(6)) | PLAIN,RLE,RLE_DICTIONARY | | 4 | 42 | 101 | 61
(1 row)
You can call SELECT * FROM parquet.file_metadata(<uri>) to discover file level metadata of the Parquet file, such as format version, at given uri.
SELECT * FROM parquet.file_metadata('/tmp/product_example.parquet')
uri | created_by | num_rows | num_row_groups | format_version
------------------------------+------------+----------+----------------+----------------
/tmp/product_example.parquet | pg_parquet | 1 | 1 | 1
(1 row)
You can call SELECT * FROM parquet.kv_metadata(<uri>) to query custom key-value metadata of the Parquet file at given uri.
SELECT uri, encode(key, 'escape') as key, encode(value, 'escape') as value FROM parquet.kv_metadata('/tmp/product_example.parquet');
uri | key | value
------------------------------+--------------+---------------------
/tmp/product_example.parquet | ARROW:schema | /////5gIAAAQAAAA ...
(1 row)
Object Store Support
pg_parquet supports reading and writing Parquet files from/to S3 object store. Only the uris with s3:// scheme is supported.
The simplest way to configure object storage is by creating the standard ~/.aws/credentials and ~/.aws/config files:
$ cat ~/.aws/credentials
[default]
aws_access_key_id = AKIAIOSFODNN7EXAMPLE
aws_secret_access_key = wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY $ cat ~/.aws/config
[default]
region = eu-central-1
Alternatively, you can use the following environment variables when starting postgres to configure the S3 client:
AWS_ACCESS_KEY_ID: the access key ID of the AWS accountAWS_SECRET_ACCESS_KEY: the secret access key of the AWS accountAWS_REGION: the default region of the AWS accountAWS_SHARED_CREDENTIALS_FILE: an alternative location for the credentials fileAWS_CONFIG_FILE: an alternative location for the config fileAWS_PROFILE: the name of the profile from the credentials and config file (default profile name isdefault)
[!NOTE]
To be able to write into a object store location, you need to grantparquet_object_store_writerole to your current postgres user.
Similarly, to read from an object store location, you need to grantparquet_object_store_readrole to your current postgres user.
Copy Options
pg_parquet supports the following options in the COPY TO command:
format parquet: you need to specify this option to read or write Parquet files which does not end with.parquet[.<compression>]extension. (This is the only option thatCOPY FROMcommand supports.),row_group_size <int>: the number of rows in each row group while writing Parquet files. The default row group size is122880,row_group_size_bytes <int>: the total byte size of rows in each row group while writing Parquet files. The default row group size bytes isrow_group_size * 1024,compression <string>: the compression format to use while writing Parquet files. The supported compression formats areuncompressed,snappy,gzip,brotli,lz4,lz4rawandzstd. The default compression format issnappy. If not specified, the compression format is determined by the file extension.compression_level <int>: the compression level to use while writing Parquet files. The supported compression levels are only supported forgzip,zstdandbrotlicompression formats. The default compression level is6forgzip (0-10),1forzstd (1-22)and1forbrotli (0-11).
Configuration
There is currently only one GUC parameter to enable/disable the pg_parquet:
pg_parquet.enable_copy_hooks: you can set this parameter toonoroffto enable or disable thepg_parquetextension. The default value ison.
Supported Types
pg_parquet has rich type support, including PostgreSQL's primitive, array, and composite types. Below is the table of the supported types in PostgreSQL and their corresponding Parquet types.
| PostgreSQL Type | Parquet Physical Type | Logical Type |
|---|---|---|
bool | BOOLEAN | |
smallint | INT16 | |
integer | INT32 | |
bigint | INT64 | |
real | FLOAT | |
oid | INT32 | |
double | DOUBLE | |
numeric(1) | FIXED_LEN_BYTE_ARRAY(16) | DECIMAL(128) |
text | BYTE_ARRAY | STRING |
json | BYTE_ARRAY | STRING |
bytea | BYTE_ARRAY | |
date (2) | INT32 | DATE |
timestamp | INT64 | TIMESTAMP_MICROS |
timestamptz (3) | INT64 | TIMESTAMP_MICROS |
time | INT64 | TIME_MICROS |
timetz(3) | INT64 | TIME_MICROS |
geometry(4) | BYTE_ARRAY |
Nested Types
| PostgreSQL Type | Parquet Physical Type | Logical Type |
|---|---|---|
composite | GROUP | STRUCT |
array | element's physical type | LIST |
crunchy_map(5) | GROUP | MAP |
[!WARNING]
(1) The numerictypes with <=38precision is represented asFIXED_LEN_BYTE_ARRAY(16)withDECIMAL(128)logical type. Thenumerictypes with >38precision is represented asBYTE_ARRAYwithSTRINGlogical type.(2) The datetype is represented according toUnix epochwhen writing to Parquet files. It is converted back according toPostgreSQL epochwhen reading from Parquet files.(3) The timestamptzandtimetztypes are adjusted toUTCwhen writing to Parquet files. They are converted back withUTCtimezone when reading from Parquet files.(4) The geometrytype is represented asBYTE_ARRAYencoded asWKBwhenpostgisextension is created. Otherwise, it is represented asBYTE_ARRAYwithSTRINGlogical type.(5) crunchy_mapis dependent on functionality provided by Crunchy Bridge. Thecrunchy_maptype is represented asGROUPwithMAPlogical type whencrunchy_mapextension is created. Otherwise, it is represented asBYTE_ARRAYwithSTRINGlogical type.
[!WARNING]
Any type that does not have a corresponding Parquet type will be represented, as a fallback mechanism, asBYTE_ARRAYwithSTRINGlogical type. e.g.enum
Postgres Support Matrix
pg_parquet supports the following PostgreSQL versions:
| PostgreSQL Major Version | Supported |
|---|---|
| 14 | ✅ |
| 15 | ✅ |
| 16 | ✅ |
| 17 | ✅ |
今日荐书
彩蛋:国产数据库周边生态
当然一款数据库要流行起来, 除了自己要强大, 还离不开生态. 用好周边生态工具, 管理水平战胜90%老司机!!! 下面简单介绍一下国产数据库周边生态.
1、管控软件
鸣嵩(前阿里云数据库总经理 / 研究员)等大佬们创业创办的云猿生, 核心产品是KubeBlocks. 他们的理念是让管理数据库和搭积木一样简单, 如果你要管理很多套并且种类(OLTP\OLAP\NoSQL\KV\TS\MQ等)很多的数据库产品, 推荐首选.
https://github.com/apecloud/kubeblocks
PG中文社区核心委员唐成老师的公司乘数开源的Clup, 专用管理PostgreSQL和PolarDB的集群管理软件, 如果你要管理很多套数据库, 推荐选择. 并且Clup还提供了企业版、自研的连接池、分布式存储、一体机、备份平台等, 是企业用户推荐之选.
https://www.csudata.com/
若航开源的pigsty, 集成了300多个PG插件的PG集群和PolarDB集群管理软件, 如果你要管理很多套PG或PolarDB数据库, 且对插件有特别多的需求, 推荐选择.
https://pigsty.cc/zh/
2、审计监控诊断优化
翟总(曾经是我背后的男人)到海信聚好看后研发的 DBdoctor, 采用ebpf技术, 在对数据库几乎没有影响的情况下实时监控数据库和服务器的各项指标, 发现和诊断问题根因非常方便.
https://www.dbdoctor.cn/
天舟老哥的核心产品Bytebase 是位于您和数据库之间的中间件。它是数据库 DevOps 的 GitLab/GitHub,专为开发人员、DBA 和平台工程师打造。
https://bytebase.cc/docs/introduction/what-is-bytebase/
PawSQL, SQL优化和诊断产品.
D-Smart, Oracle老前辈白老大出品, 专注企业级市场, 将业界顶级DBA经验的产品化作品, 产品功能包括数据库监控、诊断、优化等.
https://www.modb.pro/db/567140
3、国产数据库IDE
IDE是开发者的必备工具,例如社区有pgAdmin, 国产IDE则可以看看老程序猿达刚老师的DeskUI:
https://www.deskui.com
4、数据同步&迁移&备份恢复
NineData, 老领导出去创业做的产品, 产品涵盖了数据同步、迁移、备份、比对、devops、chatDBA等.
https://www.ninedata.cloud/home
DSG, 非常老牌的数据库同步迁移企业级产品, 支持各种数据库的异构和同构迁移, 用他们的话说, 没有dsg搞不定的迁移, 比goldengate还牛.
https://www.dsgdata.com/
公开课
如果你对PolarDB学习感兴趣可以阅读这个公开课系列:
除了PolarDB还非常值得关注的几款PG栈国产数据库:
HaloDB(基于PG兼容PostgreSQL、Oracle、MySQL. http://www.halodbtech.com/ )、 IvorySQL(基于开源PG兼容PG、Oracle. https://www.ivorysql.org/zh-cn/ )、 ProtonBase(云原生分布式数仓. https://protonbase.com/ )、 成都文武数据库(https://ww-it.cn)
参考文档点击阅读原文获得
感谢关注我的github (https://github.com/digoal/blog) 及视频号: