BareOS 中的增量备份大小过大

BareOS 中的增量备份大小过大

我安装了 BareOS,对默认配置文件的修改很少。执行了完整、增量和差异备份。大多数客户端似乎都按预期进行了备份。

然而,我的一个客户似乎在每个增量周期中都重复备份超过 10% 的整个文件系统。

如何找到重复备份的最大文件和文件夹?

BAT 在这里似乎没什么用,因为它只列出了文件节点本身的大小,而不是整个文件夹的大小。我实际上是在寻找一个du在 BareOS 框架内适用于特定备份尝试的命令。

答案1

虽然我很欣赏 @damiano-verzulli 的努力,但在 FreeNode 上的 BareOS IRC 频道中的讨论却没有得到这样的回应:

事实证明,Kjetil Torgrim Homme 已经编写了一个脚本来执行此操作,名为bacula-du。(以及许多其他有用的脚本!)

它们都已列出并可从这里获取:

http://heim.ifi.uio.no/kjetilho/hacks/

具体bacula-du解释如下:

Usage: bacula-du [OPTIONS] -j JOBID 
       Summarize disk usage of directories included in the backup JOBID

Main options are:   
    -a, --all             write counts for all files, not just directories
    -S, --separate-dirs   do not include size of subdirectories   
    -t, --threshold=SIZE  skip output for files or directories with usage below SIZE.  default is 1 octet.   
    -L, --largest=NUM     only print NUM largest directories/files 

There is also an alternate mode which can be useful as a faster alternative to a verify job.

Usage: bacula-du --md5sum -j JOBID   --md5sum              
       output list of all files in job in md5sum format 

巴库拉杜(版本 1.4)

这里我要补充一点。要使此功能正常工作,它必须能够访问数据库(显然)。在默认配置中,它使用基于用户的安全机制,因此您必须以 bareos 用户身份运行该命令才能使其正常工作,例如

$ sudo -u bareos ./bacula-du -j 1429
done reading database.
   807160 /log/
     6372 /var/openldap-data/
     6372 /var/
   813532 /admin/
...
119983392 /

答案2

请注意重要的第一部分末尾添加了更新


不幸的是,即使可以很容易地检查特定备份的具体情况,但获取备份文件的文件大小却不那么容易。

让我们通过一些例子来更深入地了解。

就我而言,bconsole我可以通过以下方式获取最后一份工作的列表:

*list jobs
 Automatically selected Catalog: CatalogoBareos
 Using Catalog "CatalogoBareos"
 +-------+------------------------+---------------------+------+-------+------------+-------------------+-----------+
 | JobId | Name                   | StartTime           | Type | Level | JobFiles   | JobBytes          | JobStatus |
 +-------+------------------------+---------------------+------+-------+------------+-------------------+-----------+
 [...]
 | 7,060 | backup-XXXXXXXX        | 2016-01-02 16:00:50 | B    | I     |          2 |        74,225,116 | T         |
 | 7,062 | backup-YYY             | 2016-01-02 16:04:47 | B    | F     |    890,482 |   181,800,839,481 | T         |
 [...]
 +-------+------------------------+---------------------+------+-------+------------+-------------------+-----------+

从上面可以看出有两个工作:

  • 作业 7060:增量备份,涉及 2 个文件,总计 74MB 数据;
  • 作业 7062:一次“完整”备份,有趣的是 890492 个文件,总共 181GB 的数据;

让我们关注 Job 7060,因为它是一个增量作业。让我们检查一下哪个文件已备份:

*list files jobid=7060 
 c:/allXXX_Backup/PLONE/Backup_Plone.bkf
 c:/allXXX_Backup/PLONE/
+-------+-----------------+---------------------+------+-------+----------+------------+-----------+
| JobId | Name            | StartTime           | Type | Level | JobFiles | JobBytes   | JobStatus |
+-------+-----------------+---------------------+------+-------+----------+------------+-----------+
| 7,060 | backup-XXXXXXXX | 2016-01-02 16:00:50 | B    | I     |        2 | 74,225,116 | T         |
+-------+-----------------+---------------------+------+-------+----------+------------+-----------+
*

因此作业 7060 涉及一个文件(Backup_Plone.bkf)和一个目录(包含文件夹)。

不幸的是,正如你所看到的,list files jobid=7060输出不是提供您需要的文件大小,所以.....希望它有用,但是不是解决你的问题。

让我们向前迈进一步。

我去过bareos 官方文档无法找到从 bconsole 中获取“文件大小”的正确方法。因此我决定采用更复杂的方法:直接通过 SQL 访问目录。

注意:直接访问目录时请务必小心,因为每一个不当操作都可能导致严重损坏和相关的数据丢失!

一旦连接到数据库引擎(在我的情况下是 MySQL......但这是一个细节,与 PostgreSQL 一样),我看到备份文件元数据存储在(...以及其他)中:

  • File表:它存储了大部分元数据,但以下情况除外……
  • Filename表:它存储备份文件的文件名
  • Path表:存储备份文件的完整路径

令我大吃一惊的是,我发现File桌子确实不是包含一个size字段。因此,通过简单的查询不可能获得我们需要的内容。无论如何,我发现了一个有趣的LStat字段(稍后会详细介绍)。

因此我启动了以下 SQL 查询:

select 
  f.JobId,f.LStat,f.MD5, fn.Name, p.Path
from
  Filename fn,
  File f,
  Path p
where
  f.JobId=7060 and
  fn.FilenameId = f.FilenameId and 
  p.PathId = f.PathId

并得到以下结果:

mysql> select f.JobId,f.LStat,f.MD5, fn.Name, p.Path
    -> from
    -> Filename fn,
    -> File f,
    -> Path p
    -> where
    -> f.JobId=7060 and
    -> fn.FilenameId = f.FilenameId and 
    -> p.PathId = f.PathId
    -> ;
+-------+------------------------------------------------------+------------------------+------------------+-------------------------+
| JobId | LStat                                                | MD5                    | Name             | Path                    |
+-------+------------------------------------------------------+------------------------+------------------+-------------------------+
|  7060 | A A IH/ B A A A EbJQA A A BWheFw BWheFw BTD/En A A L | 8ZuPGwdo9JYJileo+sVlfg | Backup_Plone.bkf | c:/all***_Backup/PLONE/ |
|  7060 | A A EH/ B A A A A A A BWhRjY BWgz4o BTD/En A A L     | 0                      |                  | c:/all***_Backup/PLONE/ |
+-------+------------------------------------------------------+------------------------+------------------+-------------------------+
2 rows in set (0.00 sec)

至于该LStat领域,在官方 BareOS 开发者指南我看见:

> Column Name   Data Type   Remark
> [...]
> LStat         tinyblob    File attributes in base64 encoding

所以,现在的问题是:

  • LStat 是否包含文件大小?

并且,我敢打赌,答案是“是的!绝对!”:

  • 如何从 LStat 字符串中检索 FileSize?

快速搜索“BareOS LStat”让我得到了几个结果。几秒钟后,我得到了线程,包括一些关于 LStat 字段的评论,包括一个用于正确解码它的小型 PERL 脚本。它在这里(*由 Brian McDonald 提供,2005 年*),稍加修改以更好地满足您的需要:

#!/usr/bin/perl

my $base64_digits =
    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
my $base64_values = { };
my $val = 0;
foreach my $letter ( split(//, $base64_digits ) )
    { $base64_values->{ $letter } = $val++; }

print "Please, enter your LSTAT string:";
my $lstat = <STDIN>;

my $vals = decode_stats($lstat);

print "Here are the results:\n";
foreach my $key (keys %{$vals}) {
   printf("%15s: %s\n",$key, $$vals{$key}) ;
}
exit;

sub decode_stats
{
  my $stats = shift;
  my $hash = { };

  # The stats data is in base64 format.  Yuck! - I mean Yay!
  # Each value is base64 encoded incorrectly, a deficiency we have
  # to correct here.  In particular, some values are encoded with a single
  # base64 character.  This results in a 6 bit value, and you have to
  # understand how bacula padded and manipulated those values before storing
  # them in the DB.

  # the fields are, in order:
  my @fields = qw(
    st_dev st_ino st_mode st_nlink st_uid st_gid st_rdev st_size
    st_blksize st_blocks st_atime st_mtime st_ctime LinkFI st_flags data
  );

  # Decoding this mess is based on reading src/lib/base64.c in bacula, in
  # particular the to_base64 function, which is how these got in the DB in
  # the first place.
  my $field_idx = 0;
  foreach my $element ( split( /\s+/, $stats ) )
  {
    my $result = 0;
    for ( my $i = 0; $i<length($element); $i++ )
    {
        if ($i) { $result <<= 6; }
        my $r = substr($element, $i, 1 );
        $result += $base64_values->{$r};
    }
    $hash->{ $fields[$field_idx] } = $result;
    $field_idx++;
  }

  return $hash;
}

当启动并输入 LSTAT 字符串时,它会报告大量数据,包括文件大小(st_size,输出的最后一个字段):

verzulli@iMac-Chiara:/tmp$ perl pp.pl 
Please, enter your LSTAT string:A A IH/ B A A A EbJQA A A BWheFw BWheFw BTD/En A A L
Here are the results:
     LinkFI: 0
   st_atime: 1451614576
     st_ino: 0
   st_flags: 0
   st_mtime: 1451614576
     st_dev: 0
   st_nlink: 1
 st_blksize: 0
  st_blocks: 0
       data: 11
     st_gid: 0
    st_mode: 33279
     st_uid: 0
    st_rdev: 0
   st_ctime: 1393553703
    st_size: 74224640

现在,我们有了文件大小,但不幸的是,它不是通过单个查询即可轻松找到单个备份作业的最大文件。

有几种解决方案:

  • 如果您运行的是 MySQL 5.6.1 或更高版本,或者支持 BASE_64 编码/解码的 DBMS 引擎,则可以查询 LSTAT 的 SUBSTR,然后要求 DB 引擎将其值解码为 Base64 值。例如,请参阅这里

  • 你可以写一个存储过程。实际上它应该已经存在于 PostgreSQL 中,至于(他们说:“...为 lstat 字段添加了示例 postgresql 存储过程....");

  • 你可以写一个小的 PERL 脚本,查询目录并进行解码

  • ...

希望这就足够了 ;-)


更新 1

我刚刚发现了虚拟文件系统 API,明确地“...主要面向希望为 Bareos 开发新 GUI 界面的开发人员...“。

这些 API 提供了一组新命令(所谓的“点命令”),其中包括一个有趣的功能.bvfs_lsfiles,它在控制台上显示一些元数据,包括 LSTAT字段。因此:

  1. 无需直接访问底层 DB/Catalog 即可获取 LSTAT 字段

此外,BareOS 15.2 引入了新的“API 模式 2”,增加了对 JSON 输出的支持。我刚刚测试了:

  1. 启用 V.2 API 后,返回的 JSON 对象.bvfs_lsfiles包含文件大小字段,已正确解码

下面是一个例子:

*.bvfs_update
Using Catalog "MyCatalog"
*.bvfs_lsfiles path=/var/log/2016/01/06/ jobid=79
107131  34080   3614785 79  P0A CCMR IGA B A A A H1V BAA BI BWjIkK BWjJAx BWjJAx A A C  shorewall.log
107131  34081   3614786 79  P0A CCMQ IGA B A A A BT1 BAA Q BWjIkK BWjI7p BWjI7p A A C   system.log
*.api 2
{
  "jsonrpc": "2.0",
  "id": null,
  "result": {
    "api": 2
  }
}*
*.bvfs_lsfiles path=/var/log/2016/01/06/ jobid=79
{
  "jsonrpc": "2.0",
  "id": null,
  "result": {
    "files": [
      {
        "type": "F",
        "stat": {
          "dev": 64768,
          "nlink": 1,
          "mode": 33152,
          "ino": 533265,
          "rdev": 0,
          "user": "root",
          "group": "root",
          "atime": 1452050698,
          "size": 32085,
          "mtime": 1452052529,
          "ctime": 1452052529
        },
        "pathid": 107131,
        "name": "shorewall.log",
        "fileid": 3614785,
        "filenameid": 34080,
        "jobid": 79,
        "lstat": "P0A CCMR IGA B A A A H1V BAA BI BWjIkK BWjJAx BWjJAx A A C",
        "linkfileindex": 0
      },
      {
        "type": "F",
        "stat": {
          "dev": 64768,
          "nlink": 1,
          "mode": 33152,
          "ino": 533264,
          "rdev": 0,
          "user": "root",
          "group": "root",
          "atime": 1452050698,
          "size": 5365,
          "mtime": 1452052201,
          "ctime": 1452052201
        },
        "pathid": 107131,
        "name": "system.log",
        "fileid": 3614786,
        "filenameid": 34081,
        "jobid": 79,
        "lstat": "P0A CCMQ IGA B A A A BT1 BAA Q BWjIkK BWjI7p BWjI7p A A C",
        "linkfileindex": 0
      }
    ]
  }
}*

因此,最终,使用最新版本的 BareOS,原始问题似乎可以解决没有直接访问目录。

答案3

添加到迪亚马诺·韦尔祖利的有用答案,这是一个像他的 perl 代码一样解码 LSTAT 字段的 SQL 存储过程;

它不是非常高效,因为每次运行时它都会重新定义变量。我预计对于大量文件来说,它的速度会比本机代码慢一个数量级。


USE bacula;
-- Function to add. (Only once)


-- OUTPUTS:  
-- 1: Device
-- 2: Inode
-- 3: Mode (unix permissions)
-- 4: Hard link count 
-- 5: UID 
-- 6: GID 
-- 7: DeviceType 
-- 8: Size in bytes 
-- 9: Block size (4k?)
-- 10: Last access 
-- 11: Last modified 
-- 12: Last changed 
-- 13/14/15 Link/flags/misc. 



DELIMITER $$
CREATE OR REPLACE FUNCTION base64_decode_lstat (lsIndex INT ,lstat VARCHAR(128)) RETURNS BIGINT
BEGIN 
    DECLARE i,j,partlen INT;
    DECLARE alphabet BINARY(64);
    DECLARE part VARCHAR(64);
    DECLARE iOut BIGINT;

    SET part =  TRIM(SUBSTRING_INDEX(SUBSTRING_INDEX(lstat, ' ', lsIndex), ' ', -1));
    SET alphabet = BINARY 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
    SET i = 0;
    SET iOut = 0;   
    
    SET partlen = LENGTH(part);
    WHILE i < partlen DO
        SET j = partlen - i - 1;
        -- Note: SUBSTRING is not zero-indexed, so +1 as otherwise off-by-1. 
        -- Note 2: LOCATE is also not zero-indexed, so subtract 1 to fix off-by-1.
        -- Note 3: j is made zero-indexed so it runs from n-1 to 0.
        -- This does addition in base-64.
        SET iOut = iOut + ((LOCATE(SUBSTRING(part, i + 1, 1), alphabet) - 1) << (6 * j));
        SET i = i + 1;
    END WHILE;
    RETURN iOut;
END $$
DELIMITER ;

使用示例(获取数据库中第一个文件的修改时间的 unix 时间戳);

SELECT base64_decode_lstat(11, (SELECT File.LStat FROM File LIMIT 1));

相关内容