如何在Postgres中获取当前可用的磁盘空间?

18

在开始进行数据库工作之前,我需要确保至少有1GB的可用磁盘空间。我正在寻找类似以下的内容:

select pg_get_free_disk_space();

有可能吗?(我在文档中没有找到任何相关信息。)

PG: 9.3 & OS: Linux/Windows


最坏的情况下,你可能可以使用像pl/perlU这样的无限制过程化语言自己编写一个。此外,如果你还没有,请查看http://pgxn.org。 - IMSoP
请参考此答案(类似问题): https://dev59.com/IGYq5IYBdhLWcg3wmR2a#46736787 - mountainclimber11
4个回答

20

PostgreSQL目前没有直接公开磁盘空间的功能。

首先,是哪个磁盘?一个生产环境下的PostgreSQL实例通常会像这样:

  • /pg/pg94/:快速可靠的RAID6存储,在BBU RAID控制器中以WB模式运行,用于目录和大部分重要数据
  • /pg/pg94/pg_xlog:快速可靠的RAID1,用于事务日志
  • /pg/tablespace-lowredundancy:快速便宜的RAID10,用于诸如索引和UNLOGGED表等不太重要的数据,可以使用低冗余存储
  • /pg/tablespace-bulkdata:类似于慢的近线性磁存储的RAID6,用于旧审计日志、历史数据、写入最多的数据和其他访问速度较慢的数据。
  • postgreSQL日志通常在别处,但如果填满了,系统仍可能停止。这取决于许多配置设置,其中一些您根本无法从PostgreSQL中看到,如syslog选项。

还有一个问题是“空闲”空间并不一定意味着PostgreSQL可以使用它(考虑:磁盘配额、系统保留的磁盘空间),而且空闲的块/字节并不是唯一的限制,因为许多文件系统还有文件数(inode)的限制。

SELECT pg_get_free_disk_space()如何报告这个信息?

知道可用磁盘空间可能会带来安全问题。如果支持,它只会向超级用户公开,至少如此。

您可以使用一个不受信任的过程性语言,如plpythonu,通过对pg_catalog.pg_tablespace的查询和使用pg_settings中的data_directory设置来对主机操作系统进行调用以获取磁盘空间信息。您还必须检查挂载点(unix/Mac)/连接点(Windows)以发现pg_xlog等是否在单独的存储设备上。但这仍然无法真正帮助您处理日志空间。

我很想有一个SELECT * FROM pg_get_free_diskspace,它报告主数据目录空间,以及其中的任何挂载点或连接点,例如pg_xlogpg_clog,并报告每个表空间及其中的任何挂载点。这将是一个返回集的函数。但有人必须去为所有目标平台实现它,而现在,没有人想要这么做。


与此同时,如果您愿意简化需求:

  • 一个文件系统
  • 目标操作系统是类似于Linux的UNIX/POSIX兼容系统
  • 没有启用配额系统
  • 没有根保留块百分比
  • inode耗尽不是一个问题

那么您可以CREATE LANGUAGE plpython3u;并创建一个LANGUAGE plpython3u函数,其像下面这样做:

import os
st = os.statvfs(datadir_path)
return st.f_bavail * st.f_frsize

在一个函数中,它返回bigint,并且接受datadir_path作为参数,或者通过执行PL/Python内的SPI查询(如SELECT setting FROM pg_settings WHERE name='data_directory')来发现它。

如果您还想支持Windows,请参见使用Python跨平台查看卷上剩余空间。我会使用Windows管理接口(WMI)查询,而不是使用ctypes调用Windows API。

或者您可以使用这个函数,有人用PL/Perlu编写了它,它使用dfmount命令输出解析,可能只适用于Linux,但是嘿,它是预先编写的。


2
另一个选择可能是使用 cron 作业创建一个文本文件,例如每 x 分钟包含可用空间信息。然后可以通过 FDW 表使该文本文件可用(或者 cron 作业插入/更新包含信息的表中的行)。 - user330315
这是一个很棒的答案!有很多东西需要考虑。谢谢Craig和“Horse”。 - Christian

12
在不使用任何扩展语言的情况下,可以通过定义一个使用 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,这样函数只会返回 $PGDATA 路径的一个行磁盘使用情况。

安全注意事项

程序可以通过 shell 运行任何命令,它类似于双刃剑。最好使用固定的命令字符串,或者至少避免在其中传递任何用户输入。详见文档


2

这是我们已经使用了一段时间的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();

不确定自你写这个答案以来是否有所改变,但是 pg_default 应该在 $PGDATA/base 而不是 $PGDATA/default 中(请参阅存储文档)。 - Bruno

0

C 版本适用于那些仍然想要一个工具来检查 postgresql 服务器上的可用空间。 目前仅适用于 Linux 和 FreeBSD,需要为其他操作系统添加适当的头文件和定义。

#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);
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接