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

Posted

技术标签:

【中文标题】如何在 Postgres 中获取当前的可用磁盘空间?【英文标题】:How to get the current free disk space in Postgres? 【发布时间】:2015-06-30 15:05:01 【问题描述】:

在开始在我的数据库中做一些工作之前,我需要确保至少有 1Gb 的可用磁盘空间。我正在寻找这样的东西:

select pg_get_free_disk_space();

有可能吗? (我在文档中一无所获)。

PG:9.3 和操作系统:Linux/Windows

【问题讨论】:

最坏的情况,你可能会使用像 pl/perlU 这样不受限制的过程语言自己编写一个。另外,如果您还没有,请检查pgxn.org。 看到这个答案(类似的问题):***.com/questions/14346371/… 【参考方案1】:

PostgreSQL 目前没有直接暴露磁盘空间的功能。

一方面,哪个磁盘?生产 PostgreSQL 实例通常如下所示:

/pg/pg94/:在 WB 模式下,BBU RAID 控制器上的快速可靠存储 RAID6,用于目录和最重要的数据 /pg/pg94/pg_xlog:快速可靠的 RAID1,用于事务日志 /pg/tablespace-lowredundancy:一个 RAID10 的快速廉价存储,用于存储索引和 UNLOGGED 表等您不关心丢失的内容,因此您可以使用低冗余存储 /pg/tablespace-bulkdata:RAID6 或类似的慢速近线磁存储,用于旧审计日志、历史数据、主要写入数据和其他访问速度可能较慢的内容。 postgreSQL 日志通常会再次出现在其他位置,但如果填满,系统可能仍会停止。其中取决于许多配置设置,其中一些您根本无法从 PostgreSQL 中看到,例如 syslog 选项。

还有一个事实,即“空闲”空间并不一定意味着 PostgreSQL 可以使用它(想想:磁盘配额、系统保留的磁盘空间),以及空闲 /bytes 不是唯一的限制,因为许多文件系统也对文件(inode)的数量有限制。

aSELECT pg_get_free_disk_space() 如何报告此问题?

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

可以做的是使用不受信任的过程语言(例如 plpythonu)来调用操作系统以询问主机操作系统以获取磁盘空间信息,使用针对 pg_catalog.pg_tablespace 的查询并使用 @987654331 @ 来自pg_settings 的设置以发现 PostgreSQL 在主机操作系统上保存内容的位置。您还必须检查挂载点 (unix/Mac)/连接点 (Windows) 以发现 pg_xlog 等是否在单独的存储中。不过,这仍然不能真正帮助您为日志空间提供帮助。

我很想有一个SELECT * FROM pg_get_free_diskspace 报告主数据目录空间,以及其中的任何挂载点或连接点,例如pg_xlogpg_clog,并且还报告每个表空间和其中的任何挂载点它。这将是一个集合返回函数。足够关心的人将不得不费心为所有目标平台实现它,而现在,没有人想要它来完成这项工作。


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

一个文件系统 目标操作系统与 Linux 一样与 UNIX/POSIX 兼容 没有启用配额系统 没有根保留块百分比 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 查询来发现它。

如果您也想支持 Windows,请参阅 Cross-platform space remaining on volume using python 。我会使用 Windows 管理接口 (WMI) 查询而不是使用 ctypes 来调用 Windows API。

或者你可以use this function someone wrote in PL/Perlu 使用dfmount 命令输出解析来完成它,这可能只适用于Linux,但是嘿,它是预先编写的。

【讨论】:

另一个选项可能是创建文本文件的 cron 作业,例如每 x 分钟包含可用空间信息。然后可以通过 FDW 表提供该文本文件(或 cron 作业在包含信息的表中插入/更新行) 这是一个很好的答案!有很多事情要考虑。谢谢克雷格和“马”。【参考方案2】:

这里有一个无需任何扩展语言即可获得空闲磁盘空间的简单方法,只需使用 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 路径的一行磁盘使用情况。

安全注意事项

PROGRAM可以通过shell运行任何命令,就像两刃剑。最好使用固定的命令字符串,或者至少避免在其中传递任何用户输入。 See detail on document.

【讨论】:

【参考方案3】:

这是一个我们已经使用了一段时间的 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 中(见storage documentation)。【参考方案4】:

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

【讨论】:

以上是关于如何在 Postgres 中获取当前的可用磁盘空间?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 swift 2.0 中使用 AttributesOfFileSystemForpaths 获取总磁盘空间和可用磁盘空间

Azure 中如何获取未附加磁盘的磁盘容量和可用空间

在php中每秒回显当前日期和时间以及磁盘上的可用空间

如何获取 Posix 系统中的总可用磁盘空间?

如何查看Linux磁盘空间大小

Postgres的CREATE UNIQUE INDEX似乎停留了一段时间