hbas怎么通过wal的日志恢复集群日志收集

hbase(2)
本文主要是从HBase应用程序设计与开发的角度,总结几种常用的性能优化方法。有关HBase系统配置级别的优化,这里涉及的不多,这部分可以参考:。
1. 表的设计
1.1 Pre-Creating Regions
默认情况下,在创建HBase表的时候会自动创建一个region分区,当导入数据的时候,所有的HBase客户端都向这一个region写数据,直到这个region足够大了才进行切分。一种可以加快批量写入速度的方法是通过预先创建一些空的regions,这样当数据写入HBase时,会按照region分区情况,在集群内做数据的负载均衡。
有关预分区,详情参见:,下面是一个例子:
public static boolean createTable(HBaseAdmin admin, HTableDescriptor table, byte[][] splits)
throws IOException {
admin.createTable(table, splits);
return true;
} catch (TableExistsException e) {
(&table & + table.getNameAsString() + & already exists&);
// the table already exists...
return false;
public static byte[][] getHexSplits(String startKey, String endKey, int numRegions) {
byte[][] splits = new byte[numRegions-1][];
BigInteger lowestKey = new BigInteger(startKey, 16);
BigInteger highestKey = new BigInteger(endKey, 16);
BigInteger range = highestKey.subtract(lowestKey);
BigInteger regionIncrement = range.divide(BigInteger.valueOf(numRegions));
lowestKey = lowestKey.add(regionIncrement);
for(int i=0; i & numRegions-1;i++) {
BigInteger key = lowestKey.add(regionIncrement.multiply(BigInteger.valueOf(i)));
byte[] b = String.format(&%016x&, key).getBytes();
splits[i] =
1.2 Row Key
HBase中row key用来检索表中的记录,支持以下三种方式:
通过单个row key访问:即按照某个row key键值进行get操作;通过row key的range进行scan:即通过设置startRowKey和endRowKey,在这个范围内进行扫描;全表扫描:即直接扫描整张表中所有行记录。
在HBase中,row key可以是任意字符串,最大长度64KB,实际应用中一般为10~100bytes,存为byte[]字节数组,一般设计成定长的。
row key是按照字典序存储,因此,设计row key时,要充分利用这个排序特点,将经常一起读取的数据存储到一块,将最近可能会被访问的数据放在一块。
举个例子:如果最近写入HBase表中的数据是最可能被访问的,可以考虑将时间戳作为row key的一部分,由于是字典序排序,所以可以使用Long.MAX_VALUE – timestamp作为row key,这样能保证新写入的数据在读取时可以被快速命中。
1.3 Column Family
不要在一张表里定义太多的column family。目前Hbase并不能很好的处理超过2~3个column family的表。因为某个column family在flush的时候,它邻近的column family也会因关联效应被触发flush,最终导致系统产生更多的I/O。感兴趣的同学可以对自己的HBase集群进行实际测试,从得到的测试结果数据验证一下。
1.4 In Memory
创建表的时候,可以通过HColumnDescriptor.setInMemory(true)将表放到RegionServer的缓存中,保证在读取的时候被cache命中。
1.5 Max Version
创建表的时候,可以通过HColumnDescriptor.setMaxVersions(int&maxVersions)设置表中数据的最大版本,如果只需要保存最新版本的数据,那么可以设置setMaxVersions(1)。
1.6 Time To Live
创建表的时候,可以通过HColumnDescriptor.setTimeToLive(int timeToLive)设置表中数据的存储生命期,过期数据将自动被删除,例如如果只需要存储最近两天的数据,那么可以设置setTimeToLive(2 * 24 * 60 * 60)。
1.7 Compact & Split
在HBase中,数据在更新时首先写入WAL 日志(HLog)和内存(MemStore)中,MemStore中的数据是排序的,当MemStore累计到一定阈值时,就会创建一个新的MemStore,并且将老的MemStore添加到flush队列,由单独的线程flush到磁盘上,成为一个StoreFile。于此同时, 系统会在zookeeper中记录一个redo point,表示这个时刻之前的变更已经持久化了(minor
compact)。
StoreFile是只读的,一旦创建后就不可以再修改。因此Hbase的更新其实是不断追加的操作。当一个Store中的StoreFile达到一定的阈值后,就会进行一次合并(major compact),将对同一个key的修改合并到一起,形成一个大的StoreFile,当StoreFile的大小达到一定阈值后,又会对 StoreFile进行分割(split),等分为两个StoreFile。
由于对表的更新是不断追加的,处理读请求时,需要访问Store中全部的StoreFile和MemStore,将它们按照row key进行合并,由于StoreFile和MemStore都是经过排序的,并且StoreFile带有内存中索引,通常合并过程还是比较快的。
实际应用中,可以考虑必要时手动进行major compact,将同一个row key的修改进行合并形成一个大的StoreFile。同时,可以将StoreFile设置大些,减少split的发生。
2. 写表操作
2.1 多HTable并发写
创建多个HTable客户端用于写操作,提高写数据的吞吐量,一个例子:
static final Configuration conf = HBaseConfiguration.create();
static final String table_log_name = “user_log”;
wTableLog = new HTable[tableN];
for (int i = 0; i & tableN; i++) {
wTableLog[i] = new HTable(conf, table_log_name);
wTableLog[i].setWriteBufferSize(5 * 1024 * 1024); //5MB
wTableLog[i].setAutoFlush(false);
2.2 HTable参数设置
2.2.1 Auto Flush
通过调用HTable.setAutoFlush(false)方法可以将HTable写客户端的自动flush关闭,这样可以批量写入数据到HBase,而不是有一条put就执行一次更新,只有当put填满客户端写缓存时,才实际向HBase服务端发起写请求。默认情况下auto flush是开启的。
2.2.2 Write Buffer
通过调用HTable.setWriteBufferSize(writeBufferSize)方法可以设置HTable客户端的写buffer大小,如果新设置的buffer小于当前写buffer中的数据时,buffer将会被flush到服务端。其中,writeBufferSize的单位是byte字节数,可以根据实际写入数据量的多少来设置该值。
2.2.3 WAL Flag
在HBae中,客户端向集群中的RegionServer提交数据时(Put/Delete操作),首先会先写WAL(Write Ahead Log)日志(即HLog,一个RegionServer上的所有Region共享一个HLog),只有当WAL日志写成功后,再接着写MemStore,然后客户端被通知提交数据成功;如果写WAL日志失败,客户端则被通知提交失败。这样做的好处是可以做到RegionServer宕机后的数据恢复。
因此,对于相对不太重要的数据,可以在Put/Delete操作时,通过调用Put.setWriteToWAL(false)或Delete.setWriteToWAL(false)函数,放弃写WAL日志,从而提高数据写入的性能。
值得注意的是:谨慎选择关闭WAL日志,因为这样的话,一旦RegionServer宕机,Put/Delete的数据将会无法根据WAL日志进行恢复。
2.3 批量写
通过调用HTable.put(Put)方法可以将一个指定的row key记录写入HBase,同样HBase提供了另一个方法:通过调用HTable.put(List&Put&)方法可以将指定的row key列表,批量写入多行记录,这样做的好处是批量执行,只需要一次网络I/O开销,这对于对数据实时性要求高,网络传输RTT高的情景下可能带来明显的性能提升。
2.4 多线程并发写
在客户端开启多个HTable写线程,每个写线程负责一个HTable对象的flush操作,这样结合定时flush和写buffer(writeBufferSize),可以既保证在数据量小的时候,数据可以在较短时间内被flush(如1秒内),同时又保证在数据量大的时候,写buffer一满就及时进行flush。下面给个具体的例子:
for (int i = 0; i & threadN; i++) {
Thread th = new Thread() {
public void run() {
while (true) {
sleep(1000); //1 second
} catch (InterruptedException e) {
e.printStackTrace();
synchronized (wTableLog[i]) {
wTableLog[i].flushCommits();
} catch (IOException e) {
e.printStackTrace();
th.setDaemon(true);
th.start();
3. 读表操作
3.1 多HTable并发读
创建多个HTable客户端用于读操作,提高读数据的吞吐量,一个例子:
static final Configuration conf = HBaseConfiguration.create();
static final String table_log_name = “user_log”;
rTableLog = new HTable[tableN];
for (int i = 0; i & tableN; i++) {
rTableLog[i] = new HTable(conf, table_log_name);
rTableLog[i].setScannerCaching(50);
3.2 HTable参数设置
3.2.1 Scanner Caching
通过调用HTable.setScannerCaching(int scannerCaching)可以设置HBase scanner一次从服务端抓取的数据条数,默认情况下一次一条。通过将此值设置成一个合理的值,可以减少scan过程中next()的时间开销,代价是scanner需要通过客户端的内存来维持这些被cache的行记录。
3.2.2 Scan Attribute Selection
scan时指定需要的Column Family,可以减少网络传输数据量,否则默认scan操作会返回整行所有Column Family的数据。
3.2.3 Close ResultScanner
通过scan取完数据后,记得要关闭ResultScanner,否则RegionServer可能会出现问题(对应的Server资源无法释放)。
3.3 批量读
通过调用HTable.get(Get)方法可以根据一个指定的row key获取一行记录,同样HBase提供了另一个方法:通过调用HTable.get(List)方法可以根据一个指定的row key列表,批量获取多行记录,这样做的好处是批量执行,只需要一次网络I/O开销,这对于对数据实时性要求高而且网络传输RTT高的情景下可能带来明显的性能提升。
3.4 多线程并发读
在客户端开启多个HTable读线程,每个读线程负责通过HTable对象进行get操作。下面是一个多线程并发读取HBase,获取店铺一天内各分钟PV值的例子:
public class DataReaderServer {
//获取店铺一天内各分钟PV值的入口函数
public static ConcurrentHashMap getUnitMinutePV(long uid, long startStamp, long endStamp){
long min = startS
int count = (int)((endStamp - startStamp) / (60*1000));
List lst = new ArrayList();
for (int i = 0; i &= i++) {
min = startStamp + i * 60 * 1000;
lst.add(uid + &_& + min);
return parallelBatchMinutePV(lst);
//多线程并发查询,获取分钟PV值
private static ConcurrentHashMap parallelBatchMinutePV(List lstKeys){
ConcurrentHashMap hashRet = new ConcurrentHashMap();
int parallel = 3;
List&List&String&& lstBatchKeys
if (lstKeys.size() & parallel ){
lstBatchKeys
= new ArrayList&List&String&&(1);
lstBatchKeys.add(lstKeys);
lstBatchKeys
= new ArrayList&List&String&&(parallel);
for(int i = 0; i & i++
List lst = new ArrayList();
lstBatchKeys.add(lst);
for(int i = 0 ; i & lstKeys.size() ; i ++ ){
lstBatchKeys.get(i%parallel).add(lstKeys.get(i));
List && futures = new ArrayList &&(5);
ThreadFactoryBuilder builder = new ThreadFactoryBuilder();
builder.setNameFormat(&ParallelBatchQuery&);
ThreadFactory factory = builder.build();
ThreadPoolExecutor executor = (ThreadPoolExecutor) Executors.newFixedThreadPool(lstBatchKeys.size(), factory);
for(List keys : lstBatchKeys){
Callable& ConcurrentHashMap & callable = new BatchMinutePVCallable(keys);
FutureTask& ConcurrentHashMap & future = (FutureTask& ConcurrentHashMap &) executor.submit(callable);
futures.add(future);
executor.shutdown();
// Wait for all the tasks to finish
boolean stillRunning = !executor.awaitTermination(
5000000, TimeUnit.MILLISECONDS);
if (stillRunning) {
executor.shutdownNow();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} catch (Exception e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
// Look for any exception
for (Future f : futures) {
if(f.get() != null)
hashRet.putAll((ConcurrentHashMap)f.get());
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} catch (Exception e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
return hashR
//一个线程批量查询,获取分钟PV值
protected static ConcurrentHashMap getBatchMinutePV(List lstKeys){
ConcurrentHashMap hashRet = null;
List lstGet = new ArrayList();
String[] splitValue = null;
for (String s : lstKeys) {
splitValue = s.split(&_&);
long uid = Long.parseLong(splitValue[0]);
long min = Long.parseLong(splitValue[1]);
byte[] key = new byte[16];
Bytes.putLong(key, 0, uid);
Bytes.putLong(key, 8, min);
Get g = new Get(key);
g.addFamily(fp);
lstGet.add(g);
Result[] res = null;
res = tableMinutePV[rand.nextInt(tableN)].get(lstGet);
} catch (IOException e1) {
logger.error(&tableMinutePV exception, e=& + e1.getStackTrace());
if (res != null && res.length & 0) {
hashRet = new ConcurrentHashMap(res.length);
for (Result re : res) {
if (re != null && !re.isEmpty()) {
byte[] key = re.getRow();
byte[] value = re.getValue(fp, cp);
if (key != null && value != null) {
hashRet.put(String.valueOf(Bytes.toLong(key,
Bytes.SIZEOF_LONG)), String.valueOf(Bytes
.toLong(value)));
} catch (Exception e2) {
logger.error(e2.getStackTrace());
return hashR
//调用接口类,实现Callable接口
class BatchMinutePVCallable implements Callable&{
public BatchMinutePVCallable(List lstKeys ) {
this.keys = lstK
public ConcurrentHashMap call() throws Exception {
return DataReadServer.getBatchMinutePV(keys);
3.5 缓存查询结果
对于频繁查询HBase的应用场景,可以考虑在应用程序中做缓存,当有新的查询请求时,首先在缓存中查找,如果存在则直接返回,不再查询HBase;否则对HBase发起读请求查询,然后在应用程序中将查询结果缓存起来。至于缓存的替换策略,可以考虑LRU等常用的策略。
3.6 Blockcache
HBase上Regionserver的内存分为两个部分,一部分作为Memstore,主要用来写;另外一部分作为BlockCache,主要用于读。
写请求会先写入Memstore,Regionserver会给每个region提供一个Memstore,当Memstore满64MB以后,会启动 flush刷新到磁盘。当Memstore的总大小超过限制时(heapsize * hbase.regionserver.global.memstore.upperLimit * 0.9),会强行启动flush进程,从最大的Memstore开始flush直到低于限制。
读请求先到Memstore中查数据,查不到就到BlockCache中查,再查不到就会到磁盘上读,并把读的结果放入BlockCache。由于BlockCache采用的是LRU策略,因此BlockCache达到上限(heapsize * hfile.block.cache.size * 0.85)后,会启动淘汰机制,淘汰掉最老的一批数据。
一个Regionserver上有一个BlockCache和N个Memstore,它们的大小之和不能大于等于heapsize * 0.8,否则HBase不能启动。默认BlockCache为0.2,而Memstore为0.4。对于注重读响应时间的系统,可以将 BlockCache设大些,比如设置BlockCache=0.4,Memstore=0.39,以加大缓存的命中率。
有关BlockCache机制,请参考这里:HBase的Block cache,HBase的blockcache机制,hbase中的缓存的计算与使用。
4.数据计算
4.1 服务端计算
Coprocessor运行于HBase RegionServer服务端,各个Regions保持对与其相关的coprocessor实现类的引用,coprocessor类可以通过RegionServer上classpath中的本地jar或HDFS的classloader进行加载。
目前,已提供有几种coprocessor:
Coprocessor:提供对于region管理的钩子,例如region的open/close/split/flush/compact等;
RegionObserver:提供用于从客户端监控表相关操作的钩子,例如表的get/put/scan/delete等;
Endpoint:提供可以在region上执行任意函数的命令触发器。一个使用例子是RegionServer端的列聚合,这里有代码示例。
以上只是有关coprocessor的一些基本介绍,本人没有对其实际使用的经验,对它的可用性和性能数据不得而知。感兴趣的同学可以尝试一下,欢迎讨论。
4.2 写端计算
4.2.1 计数
HBase本身可以看作是一个可以水平扩展的Key-Value存储系统,但是其本身的计算能力有限(Coprocessor可以提供一定的服务端计算),因此,使用HBase时,往往需要从写端或者读端进行计算,然后将最终的计算结果返回给调用者。举两个简单的例子:
PV计算:通过在HBase写端内存中,累加计数,维护PV值的更新,同时为了做到持久化,定期(如1秒)将PV计算结果同步到HBase中,这样查询端最多会有1秒钟的延迟,能看到秒级延迟的PV结果。
分钟PV计算:与上面提到的PV计算方法相结合,每分钟将当前的累计PV值,按照rowkey + minute作为新的rowkey写入HBase中,然后在查询端通过scan得到当天各个分钟以前的累计PV值,然后顺次将前后两分钟的累计PV值相减,就得到了当前一分钟内的PV值,从而最终也就得到当天各个分钟内的PV值。
4.2.2 去重
对于UV的计算,就是个去重计算的例子。分两种情况:
如果内存可以容纳,那么可以在Hash表中维护所有已经存在的UV标识,每当新来一个标识时,通过快速查找Hash确定是否是一个新的UV,若是则UV值加1,否则UV值不变。另外,为了做到持久化或提供给查询接口使用,可以定期(如1秒)将UV计算结果同步到HBase中。
如果内存不能容纳,可以考虑采用Bloom Filter来实现,从而尽可能的减少内存的占用情况。除了UV的计算外,判断URL是否存在也是个典型的应用场景。
4.3 读端计算
如果对于响应时间要求比较苛刻的情况(如单次http请求要在毫秒级时间内返回),个人觉得读端不宜做过多复杂的计算逻辑,尽量做到读端功能单一化:即从HBase RegionServer读到数据(scan或get方式)后,按照数据格式进行简单的拼接,直接返回给前端使用。当然,如果对于响应时间要求一般,或者业务特点需要,也可以在读端进行一些计算逻辑。
作为一个Key-Value存储系统,HBase并不是万能的,它有自己独特的地方。因此,基于它来做应用时,我们往往需要从多方面进行优化改进(表设计、读表操作、写表操作、数据计算等),有时甚至还需要从系统级对HBase进行配置调优,更甚至可以对HBase本身进行优化。这属于不同的层次范畴。
总之,概括来讲,对系统进行优化时,首先定位到影响你的程序运行性能的瓶颈之处,然后有的放矢进行针对行的优化。如果优化后满足你的期望,那么就可以停止优化;否则继续寻找新的瓶颈之处,开始新的优化,直到满足性能要求。&
原文地址: &/2012/03/hbase-performance-optimization#wrapper
参考知识库
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:2619次
排名:千里之外
原创:30篇
(2)(5)(3)(6)(3)(6)(2)(2)(2)(1)(2)博客访问: 139725
博文数量: 67
博客积分: 2263
博客等级: 大尉
技术积分: 715
注册时间:
IT168企业级官微
微信号:IT168qiye
系统架构师大会
微信号:SACC2013
本文为转载和任何包含珍贵数据的东西一样,PostgreSQL 数据库也应该经常备份。尽管这个过程相当简单, 但是我们还是应该理解做这件事所用的一些技巧和假设。
&&&& 备份 PostgreSQL 数据有三种完全不同的方法:
&&& SQL 转储
&&& 文件系统级别备份
&&& 在线备份
&&& 每种备份都有自己的优点和缺点。
&&&& SQL 转储
&&&& SQL 转储的方法采用的主意是创建一个文本文件,这个文本里面都是 SQL 命令,当把这个文件回馈给时,将重建与转储时状态一样的数据库。 PostgreSQL 为这个用途提供了应用工具 pg_dump。这条命令的基本用法是:
pg_dump dbname > outfile正如你所见,pg_dump 把结果输出到标准输出。 我们下面就可以看到这样做有什么好处。
&&& pg_dump 是一个普通的 PostgreSQL
客户端应用(尽管是个相当聪明的东西。)这就意味着你可以从任何可以访问该数据库的远端主机上面进行备份工作。 但是请记住 pg_dump
不会以任何特殊权限运行。具体说来, 就是它必须要有你想备份的表的读权限,因此,实际上你几乎总是要成为数据库超级用户。
&&& 要声明 pg_dump 应该以哪个用户身份进行联接,使用命令行选项 -h host 和 -p port。
缺省主机是本地主机或你的环境变量PGHOST声明的值。 类似,缺省端口是环境变量PGPORT或(如果它不存在的话)编译好了的缺省值。
(服务器通常有相同的缺省,所以还算方便。)
&&& 和任何其他 PostgreSQL 客户端应用一样, pg_dump 缺省时用与当前用户名同名的数据库用户名进行联接。 要覆盖这个名字,要么声明 -U 选项, 要么设置环境变量PGUSER。 请注意 pg_dump 的联接也和普通客户应用一样要通过客户认证机制。
&&& 由 pg_dump 创建的备份在内部是一致的, 也就是说,在pg_dump运行的时候对数据库的更新将不会被转储。 pg_dump 工作的时候并不阻塞其他的对数据库的操作。 (但是会阻塞那些需要排它锁的操作,比如 VACUUM FULL。)
&&& Important: 如果你的数据库结构依赖于 OID (比如说用做外键),那么你必须告诉 pg_dump 把 OID
也倒出来。 要倒 OID,可以使用 -o 命令行选项。 缺省时也不会转储"大对象"。如果你使用大对象,请参考 pg_dump 的命令手册页。
&&& 从转储中恢复
&&& pg_dump 生成的文本文件可以由 psql 程序读取。 从转储中恢复的常用命令是
psql dbname < infile&&& 这里的 infile 就是你给pg_dump命令的
outfile参数。这条命令不会创建数据库 dbname,你必须在执行psql 前自己从template0创建(也就是说,用命令
createdb -T template0 dbname)。 psql 支持类似 pg_dump 的选项用以控制数据库服务器位置和用户名。
参阅 psql 的手册获取更多信息。
&&& 在开始运行恢复之前,目标库和所有在转储出来的库中拥有对象的用户, 以及曾经在某些对象上被赋予权限的用户都必须已经存在。
如果这些不存在,那么恢复将失败,因为恢复过程无法把这些对象恢复成原有的所有权和/或权限。 (有时候你希望恢复权限,不过通常你不需要这么做。)
&&& 一旦完成恢复,在每个数据库上运行 ANALYZE 是明智的举动, 这样优化器就有有用的统计数据了。你总是可以运行 vacuumdb -a -z 来 VACUUM ANALYZE 所有数据库;这个等效于手工运行 VACUUM ANALYZE。
&&& pg_dump 和 psql 可以通过管道读写, 这样我们就可能从一台主机上将数据库目录转储到另一台主机上,比如
pg_dump -h host1 dbname | psql -h host2 dbname
&&& Important: pg_dump生成的转储输出是相对于template0的。这就意味着任何
加入到template1的语言,过程等都会经由 pg_dump 转储。结果是,在恢复的时候,如果你使用的是客户化的template1,
那么你必须从template0中创建空的数据库,就象我们上面的例子那样。
有关如何有效地向 PostgreSQL 里装载大量数据的建议.
&&& 使用 pg_dumpall
&&& 上面的方法在备份整个数据库集群的时候比较麻烦而且不方便。因此我们提供了 pg_dumpall 程序。 pg_dumpall 备份一个给出的集群中的每个数据库,同时还确保保留象用户和组这样的全局数据状态。 这个命令的基本用法是:
pg_dumpall > outfile生成的转储可以用 psql 恢复:
psql template1 < infile(实际上,你可以声明任意现有的数据库进行连接,但是如果你是向一个空的数据库装载,那么 template1 是你唯一的选择。) 恢复pg_dumpall的转储的时候通常需要数据库超级用户权限,因为我们需要它来恢复用户和组信息。
&&&& 处理大数据库
&&&& 因为 PostgreSQL 允许表的大小大于你的系统允许的最大文件大小, 可能把表转储到一个文件会有问题,因为生成的文件很可能比你的系统允许的最大文件大。 因为 pg_dump 输出到标准输出,你可以用标准的 Unix 工具绕开这个问题:
使用压缩的转储. 使用你熟悉的压缩程序,比如说 gzip。
pg_dump dbname | gzip > filename.gz用下面命令恢复:
createdb dbnamegunzip -c filename.gz | psql dbname或者
cat filename.gz | gunzip | psql dbname
&&& 使用 split。. split 命令允许你 你用下面的方法把输出分解成操作系统可以接受的大小。 比如,让每个块大小为 1 兆字节:
pg_dump dbname | split -b 1m - filename用下面命令恢复:
createdb dbnamecat filename* | psql dbname
&&& 使用客户化转储格式. 如果PostgreSQL是在一个安装了zlib 压缩库的系统上制作的,
那么客户化转储格式将在写入输出文件的时候压缩数据。 它会生成和使用 gzip 类似大小的转储文件,
但是还附加了一个优点:你可以有选择地恢复库中的表。 下面的命令用客户化转储格式转储一个数据库:
pg_dump -Fc dbname > filename客户化格式的转储不是脚本,不能用于 psql, 而是需要使用 pg_restore 转储。 请参考 pg_dump 和 pg_restore 的手册获取细节。
&&&& 注意出于向下兼容的考虑,缺省的时候 pg_dump 并不转储大对象。 要转储大对象,你必须使用客户化或者 tar
输出格式, 并且在 pg_dump 中使用-b选项。 参阅 pg_dump 手册获取详细信息。 在 PostgreSQL 源码树的
contrib/pg_dumplo 路径里也包含一个可以转储大对象的程序。
另一个备份的策略是直接拷贝PostgreSQL用于存放数据库数据的文件。
tar -cf backup.tar /usr/local/pgsql/data
&&& 不过,你要受到两个限制,令这个方法不那么实用,或者至少比 pg_dump 的方法逊色一些:
&&& 为了进行有效的备份,数据库必须被关闭。 象拒绝所有联接这样的折衷的方法是不行的,因为总是有一些缓冲区数据存在。 (主要因为 tar 和类似的工具在做备份的时候并不对文件系统的状态做原子快照)。
&&& 如果你曾经深入了解了数据库在文件系统布局的细节,你可能试图从对应的文件或目录里备份几个表或者数据库。
这样做是没用的,因为包含在这些文件里的信息只是部分信息。还有一半信息在提交日志文件 pg_clog/*里面,它包含所有事务的提交状态。
只有拥有这些信息,表文件的信息才是可用的。当然,试图只恢复表和相关的 pg_clog
数据也是徒劳的,因为这样会把数据库集群里的所有其他没有用的表的信息都拿出来。 所以文件系统的备份只适用于一个数据库集群的完整恢复。
&&& 另外一个文件系统备份的方法是给数据目录做一个"一致的快照",
条件是文件系统支持这个功能(并且你愿意相信它是实现正确的)。 典型的过程是制作一个包含数据库的卷的"冻结快照",
然后把整个数据库目录(不仅仅是部分,见上文)从快照拷贝到备份设备, 然后释放冻结快照。这样甚至在数据库在
运行的时候都可以运转。 不过,这样创建的备份会把数据库文件保存在一个没有恰当关闭数据库服务器的状态下;
因此,如果你在这个备份目录下启动数据库服务器, 它就会认为数据库服务器经历过崩溃并且重放 WAL
日志。这不是个问题,只要意识到它即可(并且确信在自己的备份中包含 WAL 文件)。
&&& 如果你的数据库分布在多个卷上(比如,数据文件和 WAL 日志在不同的磁盘上),那么可能就没有任何方法获取所有卷上准确的同步冻结快照。 在你新闻这样的情况下的一致性快照的技术之前,仔细阅读你的文件系统文档。 最的方法是关闭数据库服务器足够长的时间,以建立所有冻结快照。
&&& 还要说明的是,文件系统备份不会比SQL转储小。恰恰相反,大多数情况下它要大。 (比如pg_dump 不用倒出索引,只是创建它们的命令。)
在任何时候,PostgreSQL 都在集群的数据目录的 pg_xlog/ 子目录里维护着一套预写日志(WAL)。
这些日志记录着每一次对数据库的数据文件的修改的细节。这些日志存在是为了防止崩溃:如果系统崩溃,
数据库可以通过"重放"上次检查点以来的日志记录以恢复数据库的完整性。
但是,日志的存在让它还可以用于第三种备份数据库的策略:我们可以组合文件系统备份与 WAL 文件的备份。
如果需要恢复,我们就恢复备份,然后重放备份了的WAL文件,把备份恢复到当前的时间。
这个方法对管理员来说,明显比以前的方法更复杂,但是有非常明显的优势:
&&& 在开始的时候我们不需要一个非常完美的一致的备份。任何备份内部的不一致都会被日志重放动作修改正确 (这个和崩溃恢复时发生的事情没什么区别)。因此我们不需要文件系统快照的功能, 只需要 tar 或者类似的归档工具。
&&& 因为我们可以把无限长的 WAL 文件序列连接起来,所以连续的备份简化为连续地对 WAL 文件归档来实现。 这个功能对大数据库特别有用,因为大数据库的全备份可能并不方便。
&&& 我们可没说重放 WAL 记录的时候我们必须重放到结尾。我们可以在任意点停止重放, 这样就有一个在任意时间的数据库一致的快照。因此,这个技术支持即时恢复: 我们可以把数据库恢复到你开始备份以来的任意时刻的状态。
&&& 如果我们持续把 WAL 文件序列填充给其它装载了同样的基础备份文件的机器, 我们就有了一套"热备份"系统:在任何点我们都可以启动第二台机器, 而它拥有近乎当前的数据库拷贝。
&&& 和简单的文件系统备份技术一样,这个方法只能支持整个数据库集群的恢复,而不是一个子集。 同样,它还要求大量的归档:基础备份量可能很大,而且忙碌的系统将生成许多兆需要备份的的 WAL 流量。 但是,它仍然时在需要高可靠性的场合下的最好的备份技术。
&&& 要想从在线备份中成功恢复,你需要一套连续的 WAL 归档文件,它们最远回朔到你开始备份的时刻。 因此,要想开始备份,你应该在开始第一次基础备份之前设置并测试你的步骤。 根据我们讨论过的归档 WAL 文件的机制。
&&& 1. 设置 WAL 归档
&&& 抽象来看,一个运行着的 PostgreSQL 系统生成一个无限长的 WAL 日志序列。 系统物理上把这个序列分隔成
WAL段文件,通常一块时 16M 字节大 (在制作 PostgreSQL 的时候可以改变其大小)。
这些段文件的名字是数值命名的,这些数值反映他们在抽取出来的 WAL 序列中的位置。 在不适用 WAL
归档的时候,系统通常只是创建几个段文件然后"循环"使用它们, 方法是把不再使用的段文件的名字重命名为更高的段编号。
系统假设那些内容比前一次检查点更老的段文件是没用的了,然后就可以循环利用。
&&& 在归档 WAL 数据的时候,我们希望在每个段文件填充满之后捕获之,
并且把这些数据在段文件被循环利用之前保存在某处。根据应用以及可用的硬件的不同, 我们可以有许多不同的方法"把数据保存在某处":
我们可以把段文件拷贝到一个 NFS 装配的目录,把它们放到另外一台机器上, 或者把它们写入机
里(需要保证你有办法把文件恢复为原名), 或者把它们打成包,烧录到 CD 里,或者是其它的什么方法。
为了给数据库管理员提供最大可能性的灵活性,PostgreSQL 试图不对如何归档做任何假设。取而代之的是,PostgreSQL
让管理员声明一个 shell 命令执行来拷贝一个完整的段文件到它需要去的地方。 该命令可以简单得就是一个 cp,或者它可以调用一个复杂的
shell 脚本 — 所有都由管理员决定。
&&& 所使用的 shell 命令由配置参数 archive_command 声明, 它实际上总是放在 postgresql.conf
文件里的。 在这个字串里,任何 %p 都被要归档的文件的绝对路径代替,而任何 %f 只是被文件名代替。 如果你需要在命令里嵌入一个真正的
%,写 %%。 最简单的有用命令是类似下面这样的
archive_command = 'cp -i %p /mnt/server/archivedir/%f </dev/null'它将把 WAL 段拷贝到目录 /mnt/server/archivedir。 这个只是一个例子,并非我们建议的方法,可能不能在所有系统上都正确运行。
&&& 归档命令将在运行 PostgreSQL 的同一个用户的权限下执行。 因此被归档的 WAL 文件实际上包含你的数据库里的所有东西,所以你应该确保自己的归档数据不会被别人窥探; 比如,归档到一个没有组或者全局读权限的目录里。
&&& 有一点很重要:当且仅当归档命令成功时,它才返回零。在得到一个零值结果之后, PostgreSQL 将假设该 WAL
段文件已经成功归档, 因此它稍后将被删除或者被新的数据覆盖。但是,一个非零值告诉 PostgreSQL 该文件没有被归档;
因此它会周期性的重试直到成功。
&&&& 归档命令通常应该设计成拒绝覆盖已经存在的归档文件。这是一个非常重要的特性, 可以在管理员操作失误(比如把两个不同的的
输出发送到同一个归档目录)的时候保持你的归档的完整性。 我们建议你首先要测试你准备使用到归档命令,以保证它实际上不会覆盖现有的文件,
并且在这种情况下它返回非零状态。 我们发现,在这方面, cp -i 在某些平台上是正确的,而在其它平台上是不正确的。
如果选定的命令本身并不能正确处理这个问题,你应该增加一个命令预先探测归档文件是否存在。 比如,类似下面的东西。
archive_command = 'test ! -f .../%f && cp %p .../%f'在几乎所有的 Unix 变种上都工作正确。
&&& 在设计你的归档环境都时候,请考虑一下如果归档命令不停失败会发生什么情况, 因为有些方面要求操作者的干涉,或者是归档空间不够了。 比如,如果你往上
写,但是没有自动换带机,那么就有可能发生这种情况; 如果磁带满了,那就除非换磁带,否则啥事也做不了。
你应该确保人和错误条件或者人和要求操作员干涉带错误都会正确报告, 这样才能迅速解决这些问题。否则 pg_xlog/ 目录会不停地填充 WAL
段文件, 直到问题解决。
&&& 归档命令的速度并不要紧,只要它能跟上你的服务器生成 WAL 数据的平均速度即可。
即使归档进程落在了后面一点,正常的操作也会继续进行。 如果归档进程慢很多,就会增加灾难发生的时候丢失的数据量。 同时也意味着 pg_xlog/
目录包含大量未归档的日志段文件, 并且可能最后超出了磁盘空间。我们建议你监控归档进程,确保它是按照你的意识运转的。
&&& 如果你关心能够恢复到当前即时的状态,你可能需要采取几个额外的步骤以确保当前的, 部分填充的 WAL
段也拷贝到了某些地方。这条对于生成很少 WAL 流量的服务器 (或者在运行中有松弛阶段的)特别重要,因为在一个 WAL
段文件完全填充满进而可以归档之前, 可能需要很长时间。一个处理这些的可能的方法是设置一个 cron 作业, 周期性(比如每分钟一次)地标识当前
WAL 段文件然后把它们保存到某个安全的地方。 归档的 WAL 段和保存的当前段就足够保证你可以总是恢复到当前时间的一分钟之内。
这个行为目前还不是内置于 PostgreSQL 的,因为我们不想把 archive_command
的定义复杂化,因为那样就要要求它跟踪成功归档但是却又有不同时刻含义的同一个 WAL 文件。 archive_command
只是用于处理那些不再改变的 WAL 段文件; 除了错误重试之外,对于任何给出的文件名他都只被调用一次。
&&& 在写自己的归档命令的时候,你应该假设被归档的文件最多 64 个字符长并且可以包含 ASCII 字母,数字,以及点的任意组合。 我们不必要记住原始的全路径(%p),但是有必要记住文件名(%f)。
&&& 请注意尽管 WAL 归档允许你回复任何对你的 PostgreSQL 数据库的数据做的修改,
在最初的基础备份之后,它还是不会回复对配置文件的修改(也就是说,postgresql.conf,pg_hba.conf 和
pg_ident.conf),因为这些文件都是手工编辑的,而不是通过 SQL 操作来编辑的。
所以你可能会需要把你的配置文件放在一个日常文件系统备份过程即可处理到的地方。
&&& 2. 进行一次基础备份&&& 进行基础备份的过程相当简单:
确保 WAL 归档打开并且可以运转。
以数据库超级用户身份连接到数据库,发出命令
SELECT pg_start_backup('label');&&& 这里的 label
是任意你想使用的这次备份操作的唯一标识。 (一个好习惯是使用你想把备份转储文件放置的目的地的全路径。) pg_start_backup
用你的备份的信息,在你的集群目录里,创建一个备份标签文件, 叫做 backup_label。
&&& 至于你连接到集群中的那个数据库没什么关系。你可以忽略函数返回的结果; 但是如果它报告错误,那么在继续之前处理它。
&&& 执行备份,使用任何方便的文件系统工具,比如 tar 或者 cpio。 这些操作过程中既不需要关闭数据库,也不希望关闭数据库的操作。
&&& 再次以数据库超级用户身份连接数据库,然后发出命令
SELECT pg_stop_backup();如果这个返回成功,你的工作就完成了。
&&& 我们不需要太关心在 pg_start_backup 和开始实际的备份之间开销的时间, 也不需要太关心备份结束和 pg_stop_backup 之间的时间; 几分钟的延迟不会搞砸事情。不过,你必须确保这些操作是按顺序执行的而不是重叠执行的。
&&& 要保证你的备份转储包括所有数据库集群目录里的文件(比如,/usr/local/pgsql/data)。 如果你在使用并未放置在这个目录里的表空间,也要小心地包含它们 (并且要确保你的备份转储归档符号连接是符号连接,否则,恢复会把你的表空间搞乱)。
&&& 不过,你可以在备份转储文件里省略集群目录里的 pg_xlog/ 子目录。
这个略微复杂些的动作是值得的,因为它减少了恢复的时候的错误。 如果 pg_xlog/
是一个指向集群目录之外的一个符号连接,那么这件事情很容易处理, 出于性能考虑的时候经常这么做。
&&& 要使用这个备份,你需要保存所有备份开始以及之后的 WAL 段文件。 为了帮助你实现这个任务,pg_stop_backup
函数创建一个备份历史文件, 它马上存储到 WAL 归档区域。这个文件的名字是以你在使用备份的时候需要的第一个 WAL 段文件的名字命名的。
比如,如果开始 WAL 文件是 CD,那么备份历史文件将命名为类似
CD.007C9330.backup 这样的东西。 (这个文件名的第二部分表示在该 WAL
文件里面的准确位置,通常可以被忽略。) 一旦你安全地把备份转储文件归了档,那么你就可以删除所有那些数值名字在这个文件前面的归档的 WAL 段。
备份历史文件只是一个小的文本文件。它包含你给予 pg_start_backup 的标签字串,
以及备份的起始时间和终止时间。如果你使用这个标签来表示转储文件放在哪里, 如果需要的话,那么归档的历史文件就足够告诉你转储文件存放在哪里了。
&&& 因为你必须保留直到你最后一次基础备份的所有归档的 WAL 文件, 那么两次基础备份之间的间隔通常是根据你想在归档 WAL
文件上花多少存储空间来定的。 你还应该考虑你准备在恢复上花多少时间,如果需要恢复的话 — 系统将需要重放所有那些段,
而如果最后一次基础备份以来,时间已经很长了,那么那些动作可能会花掉好些时间。
&&& 还有一件事值得一提,那就是 pg_start_backup 函数在数据库集群目录里创建了一个叫 backup_label
的文件,它被 pg_stop_backup 删除。 这个文件当然也会作为你的备份转储文件的一部分归档。这个备份标签文件包含你给予
pg_start_backup 的标签字串, 以及 pg_start_backup 运行的时刻,以及起始 WAL 文件的名字。
如果有混淆,那么我们可以看看备份转储文件里面然后判断转储文件来自那个备份会话。
&&& 我们还可以在 postmaster 停止的时候制作一个备份转储。 在这种条件下,很明显你不能使用 pg_start_backup
或者 pg_stop_backup, 并且因此你必须靠自己的手段来跟踪备份转储文件都是那些,以及相关的 WAL 文件最远走到哪里。
通常使用上面的在线备份步骤更好些。
&&& 3. 从在线备份中恢复
&&& 好,最糟糕的事情发生了,现在你需要从备份中恢复。下面是步骤: &&& 停止 postmaster,如果它还在运行的话。
&&& 如果你还有足够的空间,把整个集群数据目录和所有表空间拷贝到一个临时位置,
以防万一你之后还需要它们。请注意这个预防措施要求你在系统里又足够的剩余空间来现有库的保持两份拷贝。
如果你没有足够的空间,那么你至少需要把集群数据目录的 pg_xlog 子目录的内容拷贝到安全的地方,
因为它们可能包含系统宕掉的时候还没有归档的日志。
&&&& 然后清理掉所有在该集群数据目录里的现存文件, 以及所有你使用的表空间里根目录下的现存文件。
&&& 从你的备份转储中恢复数据库文件。要小心用正确的所有者(数据库系统用户,而不是 root!)和权限恢复它们。 如果你使用了表空间,你可能需要核实在 pg_tblspc/ 里的符号连接都得到正确恢复。
&&& 删除任何目前还在 pg_xlog/ 里的文件;这些文件来自备份转储,因此它们可能比目前的老。 如果你就根本没有归档 pg_xlog/,那么重建之,要注意也要重建子目录 pg_xlog/archive_status/。
&&& 如果你有在步骤 2 里面保存的 WAL 段文件,那么把它们拷贝到 pg_xlog/。 (最好是拷贝它们,而不是把它们移动回来,这样即使发生了糟糕的事情,你需要重启的时候, 你也依然拥有未修改的文件。)
&&& 在集群数据目录里创建一个恢复命令文件 recovery.conf(参阅 Recovery Settings)。 你可能还需要临时修改 pg_hba.conf 以避免普通用户连接,直到你确信恢复已经正常了为止。
&&& 启动 postmaster。postmaster 将进入恢复模式并且继续读取它需要的归档的 WAL 文件。
在恢复过程完成后,postmaster 将把 recovery.conf 改名为 recovery.done
(以避免不小心因后面的崩溃再次进入恢复模式)然后开始正常的数据库操作。
&&& 检查数据库的内容以确保你已经恢复到你期望的位置。 如果还没有,回到步骤 1。如果全部正常,则恢复 pg_hba.conf 成正常状态,允许你的用户登录。
&&&& 所有这些操作的关键部分时设置一个恢复命令文件, 这个文件描述你希望如何恢复以及恢复应该走到哪里。 你可以使用
recovery.conf.sample(通常安装在安装目录的 share/ 子目录里)作为原型。 你必须在 recovery.conf
里面声明的一个东西是 restore_command, 它告诉系统如何拿回归档的 WAL 文件段。类似 archive_command,
这个是一个脚本命令字串。它可以包含 %f,这个变量会被需要的日志文件名替换, 以及 %p,它会被要拷贝去的日志文件的绝对路径代替。
如果需要在命令里替换真正的 %,写 %%。 最简单的有用命令是类似下面的东西
restore_command = 'cp /mnt/server/archivedir/%f %p'这个命令将把以前归档的 WAL 段从目录 /mnt/server/archivedir 拷贝过来。 你当然可以使用某些更复杂的东西,甚至是一个要求操作者装配合适的磁带的 shell 脚本。
&&& 重要的一点是:该命令在失败的时候返回非零值。如果日志文件没有出现在规档中,那么该系统将询问该命令; 在问到的时候,它必须返回非零。这个不是错误条件。还要注意 %p 路径的基础名将和 %f 不一样; 不要认为它们是可以互换的。
&&& 在归档中找不到的 WAL 段将被认为在 pg_xlog/ 里;这样就允许使用最近没有归档的段。 但是在归档中的段将比 pg_xlog/ 中的优先。在检索归档的文件的时候,系统将不会覆盖现有的 pg_xlog/ 内容。
&&& 通常,恢复将处理所有可用的 WAL 段,因此把数据库恢复到当前时间(或者是在所给出的可用 WAL 段数目的情况下,
我们能走到的最近的地方)。但是如果你想恢复到某些以前的时刻点(比如,就在菜鸟 DBA 删除你的主要事务表之前), 那么只需要在
recovery.conf 里声明要求的停止点。你可以通过日期/时间来声明, 也可以通过特定事务 ID
的结束来声明这个停止点,我们叫做"恢复目标"。 在我们写到这些的时候,只有日期/时间选项比较有用,
因为我们没有工具来帮助你精确地标识应该使用哪个事务 ID。
&&& 注意: 请注意停止点必须在备份的终止时间之后(也就是,pg_stop_backup 的时间)。 你无法使用一个基础备份恢复到备份正在进行中的某个时刻。 (要想恢复到该时刻,你必须回到你以前的基础备份,然后从那个位置向前滚动。)
&&& 3.1. 恢复设置
&&& 这些设置只能在 recovery.conf 里面使用,并且只是在恢复的过程中起作用。 在任何之后的恢复中,你必须重新设置他们。恢复过程开始后,它们的值无法改变。
&&& restore_command (string)&&& 执行检索归档 WAL 文件段序列的 shell 命令。这个参数是必须的。 字串中的任何 %f 都被从归档中检索出来的文件名替换, 而任何 %p 都被替换为拷贝过去的服务器上的绝对路径。 需要在命令里嵌入真正的 % 字符时,写 %%。
&&& 有一点很重要,那就是这个命令只有在成功的时候才返回零。 系统会向这条命令询问没有在归档里出现的文件名; 在这种情况下,它必须返回非零。比如:
restore_command = 'cp /mnt/server/archivedir/%f "%p"'restore_command = 'copy /mnt/server/archivedir/%f "%p"'& # Windows
recovery_target_time (timestamp)这个参数声明恢复执行到达的时间戳。最多可以声明一个 recovery_target_time 或 recovery_target_xid。缺省是恢复到 WAL 日志的结尾。 精确的停止点也受 recovery_target_inclusive 影响。
recovery_target_xid (string)这个参数声明恢复将到达的事务 ID。要注意的是,尽管事务 ID
在事务开始的时候是认为顺序的,
但是事务可以以不同的数值顺序完成。将要恢复的事务是那些在声明的这个事务之前(可以选择包括它提交的时候的)提交的。 最多可以声明一个
recovery_target_xid 或 recovery_target_time。 缺省是恢复到 WAL 日志的结尾。精确的停止点也受
recovery_target_inclusive 影响。
recovery_target_inclusive (boolean)声明我们是否在恢复目标之后(true),还是正好在其之前
(false)停止。 适用于 recovery_target_time 和 recovery_target_xid,
不管声明的是哪个。它分别表示具有准确的提交时间或者 ID 的那些(个)事务,是否将包含在恢复之中。 缺省是 true。
recovery_target_timeline (string)声明恢复到一个特定的时间线。缺省是恢复到进行基础备份时的当时的当前时间线上。 只是在复杂的重新恢复的情况下,你才需要设置这个参数,也就是在你需要恢复到一个本身是在即时恢复之后到达的状态下, 才需要这么做。 参阅 Section 22.3.4 进行讨论。
&&& 4. 时间线&&&&
能够把数据库恢复到以前的某个时间点的能力导致了一些类似科幻小说里的时间跟踪和并行宇宙这样的复杂情况。 在数据库的最初的历史里,可能你在周二下午
5:15 删除掉了一个非常关键的表。 然后有条不紊地拿出备份,恢复到周二晚上 5:14 即时备份。在这个数据库宇宙的历史里,
你从来没有删除过那个表。但是假如你后来认识到这么干并非绝好的主意,并且想回到最初的历史中的稍后的点。
你没法这么干,因为在数据库运行的时候,它覆盖了一些 WAL 段文件的序列,这些序列就是在你希望回去的区间里的。
因此你的确需要区分在你从那些原始数据库历史生成的 WAL 中完成即时恢复之后生成的 WAL 序列。
&&& 为了处理这些问题,PostgreSQL 有个叫时间线的概念。 每次你即时恢复到一个比 WAL
序列的结尾要早的时刻,那么就创建一个新的时间线, 以表示在该次恢复之后生成的 WAL 记录。(不过,如果恢复动作一尺处理到 WAL 的结尾,
我们就不会开始一个新的时间线:我们只是扩展现有个那个。)时间线 ID 号是 WAL 段文件名的一部分,
因此新的时间线并不会覆盖以前的时间线生成的 WAL 数据。实际上我们可以归档许多不同的时间线。
虽然这些看起来像没用的特性,但它却可能常常是救命稻草。考虑一下你并不很确信应该恢复到那个时刻的情况,
这个时候你不得不做好几次试验性即时恢复然后从中找到旧历史中最好的分支。 如果没有时间线,那么这个过程可能很快就会导致无法管理的混乱。
有了时间线,你可以恢复到任意以前的状态, 包括恢复到你后来放弃的时间线分支的状态。
&&& 每当创建一个新的时间线的时候,PostgreSQL 都创建一个"时间线历史"文件,
它显示自己从那个时间线分出来,以及何时分出来的。这些历史文件是在从包含多个时间线的规党中进行恢复时, 允许系统选取正确的 WAL
段文件的必要文件。因此,它们像 WAL 段文件一样归档到 WAL 归档里。
历史文件只是很小的文本文件(不想段文件很大),所以独立地保存他们代价很小,也值得做。
如果你喜欢,你可以在历史文件里加入注释,录自己为什么设置一个时间线以及如何设置的等信息。
这样的注释会在你有厚厚一堆不同的时间线需要选择和分析的时候特别有价值。
&&& 恢复的缺省的行为时沿着与备份基础备份的同一个时间线恢复。
如果你像恢复到某些子时间线(也就是,你想回到某些本身就是在开始恢复企图之后发生的状态), 你需要在 recovery.conf
里声明目标时间线 ID。你无法恢复到比基础备份更早的时间线分支。
&&& 5. 注意&&& 在我们写到这些的时候,在线备份技术还有几个局限。它们可能在将来的版本中修补:
&&& 在非 B-tree 索引上的操作(散列,R-tree,和 GiST 索引)目前没有用 WAL 记录日志, 所以重放就不会更新这些索引类型。我们建议的绕开方法是在完成恢复操作之后手工 REINDEX 每个这样的索引。
&&& 还要注意,目前的 WAL 格式占地非常大,因为它包含许多磁盘影像。
这么做对于崩溃恢复用途是合适的,因为我们可能需要修补部分写入的磁盘页。 但是对 PITR
操作却没必要存储如此多页面。将来开发的一个方面就是通过删除无用的页拷贝来压缩归档的 WAL 数据
阅读(1292) | 评论(0) | 转发(0) |
相关热门文章
给主人留下些什么吧!~~
请登录后评论。}

我要回帖

更多关于 postgresql wal 恢复 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信