diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/HBaseAdmin.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/HBaseAdmin.java index c6b11fd6760..95b5c072b36 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/HBaseAdmin.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/HBaseAdmin.java @@ -2626,6 +2626,26 @@ public class HBaseAdmin implements Admin { SnapshotDescription.Type.FLUSH); } + /** + * Create snapshot for the given table of given flush type. + *

+ * Snapshots are considered unique based on the name of the snapshot. Attempts to take a + * snapshot with the same name (even a different type or with different parameters) will fail with + * a {@link SnapshotCreationException} indicating the duplicate naming. + *

+ * Snapshot names follow the same naming constraints as tables in HBase. + * @param snapshotName name of the snapshot to be created + * @param tableName name of the table for which snapshot is created + * @param flushType if the snapshot should be taken without flush memstore first + * @throws IOException if a remote or network exception occurs + * @throws SnapshotCreationException if snapshot creation failed + * @throws IllegalArgumentException if the snapshot request is formatted incorrectly + */ + public void snapshot(final byte[] snapshotName, final byte[] tableName, + final SnapshotDescription.Type flushType) throws + IOException, SnapshotCreationException, IllegalArgumentException { + snapshot(Bytes.toString(snapshotName), Bytes.toString(tableName), flushType); + } /** public void snapshot(final String snapshotName, * Create a timestamp consistent snapshot for the given table. diff --git a/hbase-protocol/src/main/java/org/apache/hadoop/hbase/protobuf/generated/HBaseProtos.java b/hbase-protocol/src/main/java/org/apache/hadoop/hbase/protobuf/generated/HBaseProtos.java index 238db310766..9c0447eccf1 100644 --- a/hbase-protocol/src/main/java/org/apache/hadoop/hbase/protobuf/generated/HBaseProtos.java +++ b/hbase-protocol/src/main/java/org/apache/hadoop/hbase/protobuf/generated/HBaseProtos.java @@ -10476,6 +10476,10 @@ public final class HBaseProtos { * FLUSH = 1; */ FLUSH(1, 1), + /** + * SKIPFLUSH = 2; + */ + SKIPFLUSH(2, 2), ; /** @@ -10486,6 +10490,10 @@ public final class HBaseProtos { * FLUSH = 1; */ public static final int FLUSH_VALUE = 1; + /** + * SKIPFLUSH = 2; + */ + public static final int SKIPFLUSH_VALUE = 2; public final int getNumber() { return value; } @@ -10494,6 +10502,7 @@ public final class HBaseProtos { switch (value) { case 0: return DISABLED; case 1: return FLUSH; + case 2: return SKIPFLUSH; default: return null; } } @@ -16241,26 +16250,27 @@ public final class HBaseProtos { ",\n\rNameBytesPair\022\014\n\004name\030\001 \002(\t\022\r\n\005value\030" + "\002 \001(\014\"/\n\016BytesBytesPair\022\r\n\005first\030\001 \002(\014\022\016" + "\n\006second\030\002 \002(\014\",\n\rNameInt64Pair\022\014\n\004name\030" + - "\001 \001(\t\022\r\n\005value\030\002 \001(\003\"\256\001\n\023SnapshotDescrip" + + "\001 \001(\t\022\r\n\005value\030\002 \001(\003\"\275\001\n\023SnapshotDescrip" + "tion\022\014\n\004name\030\001 \002(\t\022\r\n\005table\030\002 \001(\t\022\030\n\rcre" + "ation_time\030\003 \001(\003:\0010\022.\n\004type\030\004 \001(\0162\031.Snap" + "shotDescription.Type:\005FLUSH\022\017\n\007version\030\005" + - " \001(\005\"\037\n\004Type\022\014\n\010DISABLED\020\000\022\t\n\005FLUSH\020\001\"}\n", - "\024ProcedureDescription\022\021\n\tsignature\030\001 \002(\t" + - "\022\020\n\010instance\030\002 \001(\t\022\030\n\rcreation_time\030\003 \001(" + - "\003:\0010\022&\n\rconfiguration\030\004 \003(\0132\017.NameString" + - "Pair\"\n\n\010EmptyMsg\"\033\n\007LongMsg\022\020\n\010long_msg\030" + - "\001 \002(\003\"\037\n\tDoubleMsg\022\022\n\ndouble_msg\030\001 \002(\001\"\'" + - "\n\rBigDecimalMsg\022\026\n\016bigdecimal_msg\030\001 \002(\014\"" + - "5\n\004UUID\022\026\n\016least_sig_bits\030\001 \002(\004\022\025\n\rmost_" + - "sig_bits\030\002 \002(\004\"K\n\023NamespaceDescriptor\022\014\n" + - "\004name\030\001 \002(\014\022&\n\rconfiguration\030\002 \003(\0132\017.Nam" + - "eStringPair\"$\n\020RegionServerInfo\022\020\n\010infoP", - "ort\030\001 \001(\005*r\n\013CompareType\022\010\n\004LESS\020\000\022\021\n\rLE" + - "SS_OR_EQUAL\020\001\022\t\n\005EQUAL\020\002\022\r\n\tNOT_EQUAL\020\003\022" + - "\024\n\020GREATER_OR_EQUAL\020\004\022\013\n\007GREATER\020\005\022\t\n\005NO" + - "_OP\020\006B>\n*org.apache.hadoop.hbase.protobu" + - "f.generatedB\013HBaseProtosH\001\240\001\001" + " \001(\005\".\n\004Type\022\014\n\010DISABLED\020\000\022\t\n\005FLUSH\020\001\022\r\n", + "\tSKIPFLUSH\020\002\"}\n\024ProcedureDescription\022\021\n\t" + + "signature\030\001 \002(\t\022\020\n\010instance\030\002 \001(\t\022\030\n\rcre" + + "ation_time\030\003 \001(\003:\0010\022&\n\rconfiguration\030\004 \003" + + "(\0132\017.NameStringPair\"\n\n\010EmptyMsg\"\033\n\007LongM" + + "sg\022\020\n\010long_msg\030\001 \002(\003\"\037\n\tDoubleMsg\022\022\n\ndou" + + "ble_msg\030\001 \002(\001\"\'\n\rBigDecimalMsg\022\026\n\016bigdec" + + "imal_msg\030\001 \002(\014\"5\n\004UUID\022\026\n\016least_sig_bits" + + "\030\001 \002(\004\022\025\n\rmost_sig_bits\030\002 \002(\004\"K\n\023Namespa" + + "ceDescriptor\022\014\n\004name\030\001 \002(\014\022&\n\rconfigurat" + + "ion\030\002 \003(\0132\017.NameStringPair\"$\n\020RegionServ", + "erInfo\022\020\n\010infoPort\030\001 \001(\005*r\n\013CompareType\022" + + "\010\n\004LESS\020\000\022\021\n\rLESS_OR_EQUAL\020\001\022\t\n\005EQUAL\020\002\022" + + "\r\n\tNOT_EQUAL\020\003\022\024\n\020GREATER_OR_EQUAL\020\004\022\013\n\007" + + "GREATER\020\005\022\t\n\005NO_OP\020\006B>\n*org.apache.hadoo" + + "p.hbase.protobuf.generatedB\013HBaseProtosH" + + "\001\240\001\001" }; com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner assigner = new com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner() { diff --git a/hbase-protocol/src/main/protobuf/HBase.proto b/hbase-protocol/src/main/protobuf/HBase.proto index 5622735b7b7..3e3d570d78c 100644 --- a/hbase-protocol/src/main/protobuf/HBase.proto +++ b/hbase-protocol/src/main/protobuf/HBase.proto @@ -159,6 +159,7 @@ message SnapshotDescription { enum Type { DISABLED = 0; FLUSH = 1; + SKIPFLUSH = 2; } optional Type type = 4 [default = FLUSH]; optional int32 version = 5; diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/snapshot/FlushSnapshotSubprocedure.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/snapshot/FlushSnapshotSubprocedure.java index a7a5186ad09..5a2c113a629 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/snapshot/FlushSnapshotSubprocedure.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/snapshot/FlushSnapshotSubprocedure.java @@ -48,6 +48,7 @@ public class FlushSnapshotSubprocedure extends Subprocedure { private final List regions; private final SnapshotDescription snapshot; private final SnapshotSubprocedurePool taskManager; + private boolean snapshotSkipFlush = false; public FlushSnapshotSubprocedure(ProcedureMember member, ForeignExceptionDispatcher errorListener, long wakeFrequency, long timeout, @@ -55,6 +56,10 @@ public class FlushSnapshotSubprocedure extends Subprocedure { SnapshotSubprocedurePool taskManager) { super(member, snapshot.getName(), errorListener, wakeFrequency, timeout); this.snapshot = snapshot; + + if (this.snapshot.getType() == SnapshotDescription.Type.SKIPFLUSH) { + snapshotSkipFlush = true; + } this.regions = regions; this.taskManager = taskManager; } @@ -78,10 +83,25 @@ public class FlushSnapshotSubprocedure extends Subprocedure { LOG.debug("Starting region operation on " + region); region.startRegionOperation(); try { - LOG.debug("Flush Snapshotting region " + region.toString() + " started..."); - region.flushcache(); + if (snapshotSkipFlush) { + /* + * This is to take an online-snapshot without force a coordinated flush to prevent pause + * The snapshot type is defined inside the snapshot description. FlushSnapshotSubprocedure + * should be renamed to distributedSnapshotSubprocedure, and the flush() behavior can be + * turned on/off based on the flush type. + * To minimized the code change, class name is not changed. + */ + LOG.debug("take snapshot without flush memstore first"); + } else { + LOG.debug("Flush Snapshotting region " + region.toString() + " started..."); + region.flushcache(); + } region.addRegionToSnapshot(snapshot, monitor); - LOG.debug("... Flush Snapshotting region " + region.toString() + " completed."); + if (snapshotSkipFlush) { + LOG.debug("... SkipFlush Snapshotting region " + region.toString() + " completed."); + } else { + LOG.debug("... Flush Snapshotting region " + region.toString() + " completed."); + } } finally { LOG.debug("Closing region operation on " + region); region.closeRegionOperation(); diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/snapshot/RegionServerSnapshotManager.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/snapshot/RegionServerSnapshotManager.java index 4a4ee79931c..e78d6902fa9 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/snapshot/RegionServerSnapshotManager.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/snapshot/RegionServerSnapshotManager.java @@ -186,6 +186,19 @@ public class RegionServerSnapshotManager extends RegionServerProcedureManager { new SnapshotSubprocedurePool(rss.getServerName().toString(), conf); return new FlushSnapshotSubprocedure(member, exnDispatcher, wakeMillis, timeoutMillis, involvedRegions, snapshot, taskManager); + case SKIPFLUSH: + /* + * This is to take an online-snapshot without force a coordinated flush to prevent pause + * The snapshot type is defined inside the snapshot description. FlushSnapshotSubprocedure + * should be renamed to distributedSnapshotSubprocedure, and the flush() behavior can be + * turned on/off based on the flush type. + * To minimized the code change, class name is not changed. + */ + SnapshotSubprocedurePool taskManager2 = + new SnapshotSubprocedurePool(rss.getServerName().toString(), conf); + return new FlushSnapshotSubprocedure(member, exnDispatcher, wakeMillis, + timeoutMillis, involvedRegions, snapshot, taskManager2); + default: throw new UnsupportedOperationException("Unrecognized snapshot type:" + snapshot.getType()); } diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/snapshot/TestFlushSnapshotFromClient.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/snapshot/TestFlushSnapshotFromClient.java index f0e7985caab..fd69d626134 100644 --- a/hbase-server/src/test/java/org/apache/hadoop/hbase/snapshot/TestFlushSnapshotFromClient.java +++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/snapshot/TestFlushSnapshotFromClient.java @@ -175,6 +175,49 @@ public class TestFlushSnapshotFromClient { admin, fs); } + /** + * Test snapshotting a table that is online without flushing + * @throws Exception + */ + @Test(timeout=30000) + public void testSkipFlushTableSnapshot() throws Exception { + HBaseAdmin admin = UTIL.getHBaseAdmin(); + // make sure we don't fail on listing snapshots + SnapshotTestingUtils.assertNoSnapshots(admin); + + // put some stuff in the table + HTable table = new HTable(UTIL.getConfiguration(), TABLE_NAME); + UTIL.loadTable(table, TEST_FAM); + + LOG.debug("FS state before snapshot:"); + FSUtils.logFileSystemState(UTIL.getTestFileSystem(), + FSUtils.getRootDir(UTIL.getConfiguration()), LOG); + + // take a snapshot of the enabled table + String snapshotString = "skipFlushTableSnapshot"; + byte[] snapshot = Bytes.toBytes(snapshotString); + admin.snapshot(snapshotString, STRING_TABLE_NAME, SnapshotDescription.Type.SKIPFLUSH); + LOG.debug("Snapshot completed."); + + // make sure we have the snapshot + List snapshots = SnapshotTestingUtils.assertOneSnapshotThatMatches(admin, + snapshot, TABLE_NAME); + + // make sure its a valid snapshot + FileSystem fs = UTIL.getHBaseCluster().getMaster().getMasterFileSystem().getFileSystem(); + Path rootDir = UTIL.getHBaseCluster().getMaster().getMasterFileSystem().getRootDir(); + LOG.debug("FS state after snapshot:"); + FSUtils.logFileSystemState(UTIL.getTestFileSystem(), + FSUtils.getRootDir(UTIL.getConfiguration()), LOG); + + SnapshotTestingUtils.confirmSnapshotValid(snapshots.get(0), TABLE_NAME, TEST_FAM, rootDir, + admin, fs); + + admin.deleteSnapshot(snapshot); + snapshots = admin.listSnapshots(); + SnapshotTestingUtils.assertNoSnapshots(admin); + } + /** * Test simple flush snapshotting a table that is online diff --git a/hbase-shell/src/main/ruby/hbase.rb b/hbase-shell/src/main/ruby/hbase.rb index 3c09c4dee6d..fcd11fcb495 100644 --- a/hbase-shell/src/main/ruby/hbase.rb +++ b/hbase-shell/src/main/ruby/hbase.rb @@ -61,6 +61,7 @@ module HBaseConstants ATTRIBUTES="ATTRIBUTES" VISIBILITY="VISIBILITY" AUTHORIZATIONS = "AUTHORIZATIONS" + SKIP_FLUSH = 'SKIP_FLUSH' # Load constants from hbase java API def self.promote_constants(constants) diff --git a/hbase-shell/src/main/ruby/hbase/admin.rb b/hbase-shell/src/main/ruby/hbase/admin.rb index 3d975016099..43ccad37adc 100644 --- a/hbase-shell/src/main/ruby/hbase/admin.rb +++ b/hbase-shell/src/main/ruby/hbase/admin.rb @@ -22,6 +22,7 @@ java_import java.util.Arrays java_import org.apache.hadoop.hbase.util.Pair java_import org.apache.hadoop.hbase.util.RegionSplitter java_import org.apache.hadoop.hbase.util.Bytes +java_import org.apache.hadoop.hbase.protobuf.generated.HBaseProtos::SnapshotDescription # Wrapper for org.apache.hadoop.hbase.client.HBaseAdmin @@ -718,8 +719,18 @@ module Hbase #---------------------------------------------------------------------------------------------- # Take a snapshot of specified table - def snapshot(table, snapshot_name) - @admin.snapshot(snapshot_name.to_java_bytes, table.to_java_bytes) + def snapshot(table, snapshot_name, *args) + if args.empty? + @admin.snapshot(snapshot_name.to_java_bytes, table.to_java_bytes) + else + args.each do |arg| + if arg[SKIP_FLUSH] == true + @admin.snapshot(snapshot_name.to_java_bytes, table.to_java_bytes, SnapshotDescription::Type::SKIPFLUSH) + else + @admin.snapshot(snapshot_name.to_java_bytes, table.to_java_bytes) + end + end + end end #---------------------------------------------------------------------------------------------- diff --git a/hbase-shell/src/main/ruby/shell/commands/snapshot.rb b/hbase-shell/src/main/ruby/shell/commands/snapshot.rb index 62de845fa31..15bf298e043 100644 --- a/hbase-shell/src/main/ruby/shell/commands/snapshot.rb +++ b/hbase-shell/src/main/ruby/shell/commands/snapshot.rb @@ -24,13 +24,13 @@ module Shell Take a snapshot of specified table. Examples: hbase> snapshot 'sourceTable', 'snapshotName' - hbase> snapshot 'namespace:sourceTable', 'snapshotName' + hbase> snapshot 'namespace:sourceTable', 'snapshotName', {SKIP_FLUSH => true} EOF end - def command(table, snapshot_name) + def command(table, snapshot_name, *args) format_simple_command do - admin.snapshot(table, snapshot_name) + admin.snapshot(table, snapshot_name, *args) end end end