如何在 Postgres 中获取当前可用磁盘 space?
How to get the current free disk space in Postgres?
我需要确保至少有 1Gb 的可用磁盘 space 才能开始在我的数据库中进行一些工作。我正在寻找这样的东西:
select pg_get_free_disk_space();
可能吗? (我在文档中一无所获)。
PG:9.3 & OS:Linux/Windows
PostgreSQL 目前没有直接公开磁盘的功能 space。
一方面,哪个磁盘?生产 PostgreSQL 实例通常如下所示:
/pg/pg94/
: BBU RAID 控制器上 WB 模式快速可靠存储的 RAID6,用于目录和最重要的数据
/pg/pg94/pg_xlog
:快速可靠的 RAID1,用于事务日志
/pg/tablespace-lowredundancy
:RAID10 的快速廉价存储,用于索引和 UNLOGGED
table 等您不关心丢失的东西,因此您可以使用低冗余存储
/pg/tablespace-bulkdata
:RAID6 或类似的慢速近线磁存储,用于旧审计日志、历史数据、主要写入数据和其他访问速度较慢的内容。
- postgreSQL 日志通常又在其他地方,但如果填满,系统可能仍会停止。 Where 取决于许多配置设置,其中一些您根本无法从 PostgreSQL 中看到,例如系统日志选项。
然后 "free" space 并不一定意味着 PostgreSQL 可以使用它(想想:磁盘配额,系统保留磁盘 space),而且事实是free blocks/bytes 不是唯一的限制,因为许多文件系统也对文件(inodes)数量有限制。
SELECT pg_get_free_disk_space()
是如何报告的?
了解空闲磁盘 space 可能是一个安全问题。如果支持,它至少只会向超级用户公开。
你可以做的是使用像plpythonu
这样不受信任的程序语言来进行操作系统调用来询问主机OS磁盘space 信息,使用针对 pg_catalog.pg_tablespace
的查询并使用来自 pg_settings
的 data_directory
设置来发现 PostgreSQL 在主机 OS 上保存内容的位置。您还必须检查挂载点 (unix/Mac) / 连接点 (Windows) 以发现 pg_xlog
等是否在单独的存储中。不过,这仍然无法真正帮助您 space 获取日志。
我非常想要一个 SELECT * FROM pg_get_free_diskspace
来报告主数据目录 space,以及其中的任何挂载点或连接点,例如 pg_xlog
或 pg_clog
,并且还报告了每个 tablespace 及其中的任何挂载点。这将是一个集合返回函数。足够关心的人将不得不为所有目标平台实施它,但现在,没有人想要它来完成这项工作。
与此同时,如果您愿意将您的需求简化为:
- 一个文件系统
- 目标 OS 与 UNIX/POSIX-compatible 相似 Linux
- 没有启用配额系统
- 没有根保留块百分比
- inode 耗尽不是问题
然后你可以 CREATE LANGUAGE plpython3u;
和 CREATE FUNCTION
一个 LANGUAGE plpython3u
函数来做类似的事情:
import os
st = os.statvfs(datadir_path)
return st.f_bavail * st.f_frsize
在一个 returns bigint
的函数中,或者将 datadir_path
作为参数,或者通过从 PL/Python.[=] 中执行类似 SELECT setting FROM pg_settings WHERE name = 'data_directory'
的 SPI 查询来发现它35=]
如果您也想支持 Windows,请参阅 Cross-platform space remaining on volume using python。不过,我会使用 Windows 管理接口 (WMI) 查询而不是使用 ctypes 来调用 Windows API。
或者您可以 use this function someone wrote in PL/Perlu 使用 df
和 mount
命令输出解析来完成它,这可能只适用于 Linux,但是嘿,它是预先写好的。
C
版本适用于那些仍然想要一个工具在 postgresql 服务器上免费检查 space 的人。
目前仅适用于 Linux 和 FreeBSD,需要为其他操作系统添加适当的 headers 和定义。
#if defined __FreeBSD__
# include <sys/param.h>
# include <sys/mount.h>
#elif defined __linux__
# define _XOPEN_SOURCE
# define _BSD_SOURCE
# include <sys/vfs.h>
#else
# error Unsupported OS
#endif
#include <postgres.h>
#include <catalog/pg_type.h>
#include <funcapi.h>
#include <utils/builtins.h>
/* Registration:
CREATE FUNCTION disk_free(path TEXT) RETURNS TABLE (
size BIGINT, free BIGINT, available BIGINT, inodes INTEGER, ifree INTEGER, blksize INTEGER
) AS '$pglib/pg_df.so', 'df' LANGUAGE c STRICT;
*/
#ifdef PG_MODULE_MAGIC
PG_MODULE_MAGIC;
#endif
PG_FUNCTION_INFO_V1(df);
Datum df(PG_FUNCTION_ARGS)
{
TupleDesc tupdesc;
AttInMetadata *attinmeta;
HeapTuple tuple;
Datum result;
char **values;
struct statfs sfs;
const char* path = text_to_cstring(PG_GETARG_TEXT_P(0));
if(get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("function returning record called in context that cannot accept type record")));
attinmeta = TupleDescGetAttInMetadata(tupdesc);
if(0 != statfs(path, &sfs))
ereport(ERROR, (errcode(ERRCODE_INTERNAL_ERROR), errmsg("statfs() system call failed: %m")));
values = (char **) palloc(6 * sizeof(char *));
values[0] = (char *) palloc(20 * sizeof(char));
values[1] = (char *) palloc(20 * sizeof(char));
values[2] = (char *) palloc(20 * sizeof(char));
values[3] = (char *) palloc(10 * sizeof(char));
values[4] = (char *) palloc(10 * sizeof(char));
values[5] = (char *) palloc(10 * sizeof(char));
int64 df_total_bytes = sfs.f_blocks * sfs.f_bsize;
int64 df_free_bytes = sfs.f_bfree * sfs.f_bsize;
int64 df_avail_bytes = sfs.f_bavail * sfs.f_bsize;
snprintf(values[0], 20, "%lld", df_total_bytes);
snprintf(values[1], 20, "%lld", df_free_bytes);
snprintf(values[2], 20, "%lld", df_avail_bytes);
snprintf(values[3], 10, "%d", sfs.f_files);
snprintf(values[4], 10, "%d", sfs.f_ffree);
snprintf(values[5], 10, "%d", sfs.f_bsize);
tuple = BuildTupleFromCStrings(attinmeta, values);
return HeapTupleGetDatum(tuple);
}
这是我们使用了一段时间的 plpython2u 实现。
-- NOTE this function is a security definer, so it carries the superuser permissions
-- even when called by the plebs.
-- (required so we can access the data_directory setting.)
CREATE OR REPLACE FUNCTION get_tablespace_disk_usage()
RETURNS TABLE (
path VARCHAR,
bytes_free BIGINT,
total_bytes BIGINT
)
AS $$
import os
data_directory = plpy.execute("select setting from pg_settings where name='data_directory';")[0]['setting']
records = []
for t in plpy.execute("select spcname, spcacl, pg_tablespace_location(oid) as path from pg_tablespace"):
if t['spcacl']:
# TODO handle ACLs. For now only show public tablespaces.
continue
name = t['spcname']
if name == 'pg_default':
path = os.path.join(data_directory, 'default')
elif name == 'pg_global':
path = os.path.join(data_directory, 'global')
else:
path = t['path']
# not all tablespaces actually seem to exist(?) in particular, pg_default.
if os.path.exists(path):
s = os.statvfs(path)
total_bytes = s.f_blocks * s.f_frsize
bytes_free = s.f_bavail * s.f_frsize
records.append((path, bytes_free, total_bytes))
return records
$$ LANGUAGE plpython2u STABLE SECURITY DEFINER;
用法类似于:
SELECT path, bytes_free, total_bytes FROM get_tablespace_disk_usage();
这里有一个简单的获取空闲磁盘的方法space无需任何扩展语言,只需使用pgsql定义一个函数即可。
CREATE OR REPLACE FUNCTION sys_df() RETURNS SETOF text[]
LANGUAGE plpgsql $$
BEGIN
CREATE TEMP TABLE IF NOT EXISTS tmp_sys_df (content text) ON COMMIT DROP;
COPY tmp_sys_df FROM PROGRAM 'df | tail -n +2';
RETURN QUERY SELECT regexp_split_to_array(content, '\s+') FROM tmp_sys_df;
END;
$$;
用法:
select * from sys_df();
sys_df
-------------------------------------------------------------------
{overlay,15148428,6660248,7695656,46%,/}
{overlay,15148428,6660248,7695656,46%,/}
{tmpfs,65536,0,65536,0%,/dev}
{tmpfs,768284,0,768284,0%,/sys/fs/cgroup}
{/dev/sda2,15148428,6660248,7695656,46%,/etc/resolv.conf}
{/dev/sda2,15148428,6660248,7695656,46%,/etc/hostname}
{/dev/sda2,15148428,6660248,7695656,46%,/etc/hosts}
{shm,65536,8,65528,0%,/dev/shm}
{/dev/sda2,15148428,6660248,7695656,46%,/var/lib/postgresql/data}
{tmpfs,65536,0,65536,0%,/proc/kcore}
{tmpfs,65536,0,65536,0%,/proc/timer_list}
{tmpfs,65536,0,65536,0%,/proc/sched_debug}
{tmpfs,768284,0,768284,0%,/sys/firmware}
(13 rows)
使用 df $PGDATA | tail -n +2
而不是 df | tail -n +2
,同时将所有数据保存在磁盘上的相同路径中。在这种情况下,该函数仅 return $PGDATA 路径使用一行磁盘。
安全注意事项
PROGRAM 可以运行 shell 任何命令,它就像两刃剑。最好使用固定的命令字符串,或者至少避免在其中传递任何用户输入。 See detail on document.
我需要确保至少有 1Gb 的可用磁盘 space 才能开始在我的数据库中进行一些工作。我正在寻找这样的东西:
select pg_get_free_disk_space();
可能吗? (我在文档中一无所获)。
PG:9.3 & OS:Linux/Windows
PostgreSQL 目前没有直接公开磁盘的功能 space。
一方面,哪个磁盘?生产 PostgreSQL 实例通常如下所示:
/pg/pg94/
: BBU RAID 控制器上 WB 模式快速可靠存储的 RAID6,用于目录和最重要的数据/pg/pg94/pg_xlog
:快速可靠的 RAID1,用于事务日志/pg/tablespace-lowredundancy
:RAID10 的快速廉价存储,用于索引和UNLOGGED
table 等您不关心丢失的东西,因此您可以使用低冗余存储/pg/tablespace-bulkdata
:RAID6 或类似的慢速近线磁存储,用于旧审计日志、历史数据、主要写入数据和其他访问速度较慢的内容。- postgreSQL 日志通常又在其他地方,但如果填满,系统可能仍会停止。 Where 取决于许多配置设置,其中一些您根本无法从 PostgreSQL 中看到,例如系统日志选项。
然后 "free" space 并不一定意味着 PostgreSQL 可以使用它(想想:磁盘配额,系统保留磁盘 space),而且事实是free blocks/bytes 不是唯一的限制,因为许多文件系统也对文件(inodes)数量有限制。
SELECT pg_get_free_disk_space()
是如何报告的?
了解空闲磁盘 space 可能是一个安全问题。如果支持,它至少只会向超级用户公开。
你可以做的是使用像plpythonu
这样不受信任的程序语言来进行操作系统调用来询问主机OS磁盘space 信息,使用针对 pg_catalog.pg_tablespace
的查询并使用来自 pg_settings
的 data_directory
设置来发现 PostgreSQL 在主机 OS 上保存内容的位置。您还必须检查挂载点 (unix/Mac) / 连接点 (Windows) 以发现 pg_xlog
等是否在单独的存储中。不过,这仍然无法真正帮助您 space 获取日志。
我非常想要一个 SELECT * FROM pg_get_free_diskspace
来报告主数据目录 space,以及其中的任何挂载点或连接点,例如 pg_xlog
或 pg_clog
,并且还报告了每个 tablespace 及其中的任何挂载点。这将是一个集合返回函数。足够关心的人将不得不为所有目标平台实施它,但现在,没有人想要它来完成这项工作。
与此同时,如果您愿意将您的需求简化为:
- 一个文件系统
- 目标 OS 与 UNIX/POSIX-compatible 相似 Linux
- 没有启用配额系统
- 没有根保留块百分比
- inode 耗尽不是问题
然后你可以 CREATE LANGUAGE plpython3u;
和 CREATE FUNCTION
一个 LANGUAGE plpython3u
函数来做类似的事情:
import os
st = os.statvfs(datadir_path)
return st.f_bavail * st.f_frsize
在一个 returns bigint
的函数中,或者将 datadir_path
作为参数,或者通过从 PL/Python.[=] 中执行类似 SELECT setting FROM pg_settings WHERE name = 'data_directory'
的 SPI 查询来发现它35=]
如果您也想支持 Windows,请参阅 Cross-platform space remaining on volume using python。不过,我会使用 Windows 管理接口 (WMI) 查询而不是使用 ctypes 来调用 Windows API。
或者您可以 use this function someone wrote in PL/Perlu 使用 df
和 mount
命令输出解析来完成它,这可能只适用于 Linux,但是嘿,它是预先写好的。
C
版本适用于那些仍然想要一个工具在 postgresql 服务器上免费检查 space 的人。
目前仅适用于 Linux 和 FreeBSD,需要为其他操作系统添加适当的 headers 和定义。
#if defined __FreeBSD__
# include <sys/param.h>
# include <sys/mount.h>
#elif defined __linux__
# define _XOPEN_SOURCE
# define _BSD_SOURCE
# include <sys/vfs.h>
#else
# error Unsupported OS
#endif
#include <postgres.h>
#include <catalog/pg_type.h>
#include <funcapi.h>
#include <utils/builtins.h>
/* Registration:
CREATE FUNCTION disk_free(path TEXT) RETURNS TABLE (
size BIGINT, free BIGINT, available BIGINT, inodes INTEGER, ifree INTEGER, blksize INTEGER
) AS '$pglib/pg_df.so', 'df' LANGUAGE c STRICT;
*/
#ifdef PG_MODULE_MAGIC
PG_MODULE_MAGIC;
#endif
PG_FUNCTION_INFO_V1(df);
Datum df(PG_FUNCTION_ARGS)
{
TupleDesc tupdesc;
AttInMetadata *attinmeta;
HeapTuple tuple;
Datum result;
char **values;
struct statfs sfs;
const char* path = text_to_cstring(PG_GETARG_TEXT_P(0));
if(get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("function returning record called in context that cannot accept type record")));
attinmeta = TupleDescGetAttInMetadata(tupdesc);
if(0 != statfs(path, &sfs))
ereport(ERROR, (errcode(ERRCODE_INTERNAL_ERROR), errmsg("statfs() system call failed: %m")));
values = (char **) palloc(6 * sizeof(char *));
values[0] = (char *) palloc(20 * sizeof(char));
values[1] = (char *) palloc(20 * sizeof(char));
values[2] = (char *) palloc(20 * sizeof(char));
values[3] = (char *) palloc(10 * sizeof(char));
values[4] = (char *) palloc(10 * sizeof(char));
values[5] = (char *) palloc(10 * sizeof(char));
int64 df_total_bytes = sfs.f_blocks * sfs.f_bsize;
int64 df_free_bytes = sfs.f_bfree * sfs.f_bsize;
int64 df_avail_bytes = sfs.f_bavail * sfs.f_bsize;
snprintf(values[0], 20, "%lld", df_total_bytes);
snprintf(values[1], 20, "%lld", df_free_bytes);
snprintf(values[2], 20, "%lld", df_avail_bytes);
snprintf(values[3], 10, "%d", sfs.f_files);
snprintf(values[4], 10, "%d", sfs.f_ffree);
snprintf(values[5], 10, "%d", sfs.f_bsize);
tuple = BuildTupleFromCStrings(attinmeta, values);
return HeapTupleGetDatum(tuple);
}
这是我们使用了一段时间的 plpython2u 实现。
-- NOTE this function is a security definer, so it carries the superuser permissions
-- even when called by the plebs.
-- (required so we can access the data_directory setting.)
CREATE OR REPLACE FUNCTION get_tablespace_disk_usage()
RETURNS TABLE (
path VARCHAR,
bytes_free BIGINT,
total_bytes BIGINT
)
AS $$
import os
data_directory = plpy.execute("select setting from pg_settings where name='data_directory';")[0]['setting']
records = []
for t in plpy.execute("select spcname, spcacl, pg_tablespace_location(oid) as path from pg_tablespace"):
if t['spcacl']:
# TODO handle ACLs. For now only show public tablespaces.
continue
name = t['spcname']
if name == 'pg_default':
path = os.path.join(data_directory, 'default')
elif name == 'pg_global':
path = os.path.join(data_directory, 'global')
else:
path = t['path']
# not all tablespaces actually seem to exist(?) in particular, pg_default.
if os.path.exists(path):
s = os.statvfs(path)
total_bytes = s.f_blocks * s.f_frsize
bytes_free = s.f_bavail * s.f_frsize
records.append((path, bytes_free, total_bytes))
return records
$$ LANGUAGE plpython2u STABLE SECURITY DEFINER;
用法类似于:
SELECT path, bytes_free, total_bytes FROM get_tablespace_disk_usage();
这里有一个简单的获取空闲磁盘的方法space无需任何扩展语言,只需使用pgsql定义一个函数即可。
CREATE OR REPLACE FUNCTION sys_df() RETURNS SETOF text[]
LANGUAGE plpgsql $$
BEGIN
CREATE TEMP TABLE IF NOT EXISTS tmp_sys_df (content text) ON COMMIT DROP;
COPY tmp_sys_df FROM PROGRAM 'df | tail -n +2';
RETURN QUERY SELECT regexp_split_to_array(content, '\s+') FROM tmp_sys_df;
END;
$$;
用法:
select * from sys_df();
sys_df
-------------------------------------------------------------------
{overlay,15148428,6660248,7695656,46%,/}
{overlay,15148428,6660248,7695656,46%,/}
{tmpfs,65536,0,65536,0%,/dev}
{tmpfs,768284,0,768284,0%,/sys/fs/cgroup}
{/dev/sda2,15148428,6660248,7695656,46%,/etc/resolv.conf}
{/dev/sda2,15148428,6660248,7695656,46%,/etc/hostname}
{/dev/sda2,15148428,6660248,7695656,46%,/etc/hosts}
{shm,65536,8,65528,0%,/dev/shm}
{/dev/sda2,15148428,6660248,7695656,46%,/var/lib/postgresql/data}
{tmpfs,65536,0,65536,0%,/proc/kcore}
{tmpfs,65536,0,65536,0%,/proc/timer_list}
{tmpfs,65536,0,65536,0%,/proc/sched_debug}
{tmpfs,768284,0,768284,0%,/sys/firmware}
(13 rows)
使用 df $PGDATA | tail -n +2
而不是 df | tail -n +2
,同时将所有数据保存在磁盘上的相同路径中。在这种情况下,该函数仅 return $PGDATA 路径使用一行磁盘。
安全注意事项
PROGRAM 可以运行 shell 任何命令,它就像两刃剑。最好使用固定的命令字符串,或者至少避免在其中传递任何用户输入。 See detail on document.