17
17
*/
18
18
package org .apache .hadoop .hbase .io .hfile ;
19
19
20
+ import static org .junit .Assert .assertEquals ;
20
21
import static org .junit .Assert .assertFalse ;
21
22
import static org .junit .Assert .assertTrue ;
22
23
23
24
import java .io .IOException ;
25
+ import java .util .ArrayList ;
26
+ import java .util .List ;
24
27
import java .util .Random ;
25
28
import org .apache .hadoop .conf .Configuration ;
26
29
import org .apache .hadoop .fs .FSDataInputStream ;
30
33
import org .apache .hadoop .hbase .HBaseClassTestRule ;
31
34
import org .apache .hadoop .hbase .HBaseConfiguration ;
32
35
import org .apache .hadoop .hbase .HBaseTestingUtil ;
36
+ import org .apache .hadoop .hbase .HConstants ;
33
37
import org .apache .hadoop .hbase .KeyValue ;
34
38
import org .apache .hadoop .hbase .fs .HFileSystem ;
35
39
import org .apache .hadoop .hbase .io .ByteBuffAllocator ;
36
40
import org .apache .hadoop .hbase .io .FSDataInputStreamWrapper ;
37
41
import org .apache .hadoop .hbase .io .compress .Compression ;
42
+ import org .apache .hadoop .hbase .nio .ByteBuff ;
38
43
import org .apache .hadoop .hbase .testclassification .IOTests ;
39
44
import org .apache .hadoop .hbase .testclassification .MediumTests ;
40
45
import org .apache .hadoop .hbase .util .Bytes ;
@@ -71,7 +76,62 @@ public void setUp() throws Exception {
71
76
Configuration conf = HBaseConfiguration .create (TEST_UTIL .getConfiguration ());
72
77
conf .setInt (ByteBuffAllocator .MIN_ALLOCATE_SIZE_KEY , MIN_ALLOCATION_SIZE );
73
78
allocator = ByteBuffAllocator .create (conf , true );
79
+ }
74
80
81
+ /**
82
+ * It's important that if you read and unpack the same HFileBlock twice, it results in an
83
+ * identical buffer each time. Otherwise we end up with validation failures in block cache, since
84
+ * contents may not match if the same block is cached twice. See
85
+ * https://issues.apache.org/jira/browse/HBASE-27053
86
+ */
87
+ @ Test
88
+ public void itUnpacksIdenticallyEachTime () throws IOException {
89
+ Path path = new Path (TEST_UTIL .getDataTestDir (), name .getMethodName ());
90
+ int totalSize = createTestBlock (path );
91
+
92
+ // Allocate a bunch of random buffers, so we can be sure that unpack will only have "dirty"
93
+ // buffers to choose from when allocating itself.
94
+ Random random = new Random ();
95
+ byte [] temp = new byte [HConstants .DEFAULT_BLOCKSIZE ];
96
+ List <ByteBuff > buffs = new ArrayList <>();
97
+ for (int i = 0 ; i < 10 ; i ++) {
98
+ ByteBuff buff = allocator .allocate (HConstants .DEFAULT_BLOCKSIZE );
99
+ random .nextBytes (temp );
100
+ buff .put (temp );
101
+ buffs .add (buff );
102
+ }
103
+
104
+ buffs .forEach (ByteBuff ::release );
105
+
106
+ // read the same block twice. we should expect the underlying buffer below to
107
+ // be identical each time
108
+ HFileBlockWrapper blockOne = readBlock (path , totalSize );
109
+ HFileBlockWrapper blockTwo = readBlock (path , totalSize );
110
+
111
+ // first check size fields
112
+ assertEquals (blockOne .original .getOnDiskSizeWithHeader (),
113
+ blockTwo .original .getOnDiskSizeWithHeader ());
114
+ assertEquals (blockOne .original .getUncompressedSizeWithoutHeader (),
115
+ blockTwo .original .getUncompressedSizeWithoutHeader ());
116
+
117
+ // next check packed buffers
118
+ assertBuffersEqual (blockOne .original .getBufferWithoutHeader (),
119
+ blockTwo .original .getBufferWithoutHeader (),
120
+ blockOne .original .getOnDiskDataSizeWithHeader () - blockOne .original .headerSize ());
121
+
122
+ // now check unpacked buffers. prior to HBASE-27053, this would fail because
123
+ // the unpacked buffer would include extra space for checksums at the end that was not written.
124
+ // so the checksum space would be filled with random junk when re-using pooled buffers.
125
+ assertBuffersEqual (blockOne .unpacked .getBufferWithoutHeader (),
126
+ blockTwo .unpacked .getBufferWithoutHeader (),
127
+ blockOne .original .getUncompressedSizeWithoutHeader ());
128
+ }
129
+
130
+ private void assertBuffersEqual (ByteBuff bufferOne , ByteBuff bufferTwo , int expectedSize ) {
131
+ assertEquals (expectedSize , bufferOne .limit ());
132
+ assertEquals (expectedSize , bufferTwo .limit ());
133
+ assertEquals (0 ,
134
+ ByteBuff .compareTo (bufferOne , 0 , bufferOne .limit (), bufferTwo , 0 , bufferTwo .limit ()));
75
135
}
76
136
77
137
/**
@@ -83,53 +143,75 @@ public void setUp() throws Exception {
83
143
*/
84
144
@ Test
85
145
public void itUsesSharedMemoryIfUnpackedBlockExceedsMinAllocationSize () throws IOException {
86
- Configuration conf = TEST_UTIL .getConfiguration ();
87
- HFileContext meta =
88
- new HFileContextBuilder ().withCompression (Compression .Algorithm .GZ ).withIncludesMvcc (false )
89
- .withIncludesTags (false ).withBytesPerCheckSum (HFile .DEFAULT_BYTES_PER_CHECKSUM ).build ();
90
-
91
146
Path path = new Path (TEST_UTIL .getDataTestDir (), name .getMethodName ());
92
- int totalSize ;
93
- try (FSDataOutputStream os = fs .create (path )) {
94
- HFileBlock .Writer hbw = new HFileBlock .Writer (conf , NoOpDataBlockEncoder .INSTANCE , meta );
95
- hbw .startWriting (BlockType .DATA );
96
- writeTestKeyValues (hbw , MIN_ALLOCATION_SIZE - 1 );
97
- hbw .writeHeaderAndData (os );
98
- totalSize = hbw .getOnDiskSizeWithHeader ();
99
- assertTrue (
100
- "expected generated block size " + totalSize + " to be less than " + MIN_ALLOCATION_SIZE ,
101
- totalSize < MIN_ALLOCATION_SIZE );
147
+ int totalSize = createTestBlock (path );
148
+ HFileBlockWrapper blockFromHFile = readBlock (path , totalSize );
149
+
150
+ assertFalse ("expected hfile block to NOT be unpacked" , blockFromHFile .original .isUnpacked ());
151
+ assertFalse ("expected hfile block to NOT use shared memory" ,
152
+ blockFromHFile .original .isSharedMem ());
153
+
154
+ assertTrue (
155
+ "expected generated block size " + blockFromHFile .original .getOnDiskSizeWithHeader ()
156
+ + " to be less than " + MIN_ALLOCATION_SIZE ,
157
+ blockFromHFile .original .getOnDiskSizeWithHeader () < MIN_ALLOCATION_SIZE );
158
+ assertTrue (
159
+ "expected generated block uncompressed size "
160
+ + blockFromHFile .original .getUncompressedSizeWithoutHeader () + " to be more than "
161
+ + MIN_ALLOCATION_SIZE ,
162
+ blockFromHFile .original .getUncompressedSizeWithoutHeader () > MIN_ALLOCATION_SIZE );
163
+
164
+ assertTrue ("expected unpacked block to be unpacked" , blockFromHFile .unpacked .isUnpacked ());
165
+ assertTrue ("expected unpacked block to use shared memory" ,
166
+ blockFromHFile .unpacked .isSharedMem ());
167
+ }
168
+
169
+ private final static class HFileBlockWrapper {
170
+ private final HFileBlock original ;
171
+ private final HFileBlock unpacked ;
172
+
173
+ private HFileBlockWrapper (HFileBlock original , HFileBlock unpacked ) {
174
+ this .original = original ;
175
+ this .unpacked = unpacked ;
102
176
}
177
+ }
103
178
179
+ private HFileBlockWrapper readBlock (Path path , int totalSize ) throws IOException {
104
180
try (FSDataInputStream is = fs .open (path )) {
105
- meta =
181
+ HFileContext meta =
106
182
new HFileContextBuilder ().withHBaseCheckSum (true ).withCompression (Compression .Algorithm .GZ )
107
183
.withIncludesMvcc (false ).withIncludesTags (false ).build ();
108
184
ReaderContext context =
109
185
new ReaderContextBuilder ().withInputStreamWrapper (new FSDataInputStreamWrapper (is ))
110
186
.withFileSize (totalSize ).withFilePath (path ).withFileSystem (fs ).build ();
111
- HFileBlock .FSReaderImpl hbr = new HFileBlock .FSReaderImpl (context , meta , allocator , conf );
112
- hbr .setDataBlockEncoder (NoOpDataBlockEncoder .INSTANCE , conf );
187
+ HFileBlock .FSReaderImpl hbr =
188
+ new HFileBlock .FSReaderImpl (context , meta , allocator , TEST_UTIL .getConfiguration ());
189
+ hbr .setDataBlockEncoder (NoOpDataBlockEncoder .INSTANCE , TEST_UTIL .getConfiguration ());
113
190
hbr .setIncludesMemStoreTS (false );
114
191
HFileBlock blockFromHFile = hbr .readBlockData (0 , -1 , false , false , false );
115
192
blockFromHFile .sanityCheck ();
116
- assertFalse ("expected hfile block to NOT be unpacked" , blockFromHFile .isUnpacked ());
117
- assertFalse ("expected hfile block to NOT use shared memory" , blockFromHFile .isSharedMem ());
193
+ return new HFileBlockWrapper (blockFromHFile , blockFromHFile .unpack (meta , hbr ));
194
+ }
195
+ }
118
196
197
+ private int createTestBlock (Path path ) throws IOException {
198
+ HFileContext meta =
199
+ new HFileContextBuilder ().withCompression (Compression .Algorithm .GZ ).withIncludesMvcc (false )
200
+ .withIncludesTags (false ).withBytesPerCheckSum (HFile .DEFAULT_BYTES_PER_CHECKSUM ).build ();
201
+
202
+ int totalSize ;
203
+ try (FSDataOutputStream os = fs .create (path )) {
204
+ HFileBlock .Writer hbw =
205
+ new HFileBlock .Writer (TEST_UTIL .getConfiguration (), NoOpDataBlockEncoder .INSTANCE , meta );
206
+ hbw .startWriting (BlockType .DATA );
207
+ writeTestKeyValues (hbw , MIN_ALLOCATION_SIZE - 1 );
208
+ hbw .writeHeaderAndData (os );
209
+ totalSize = hbw .getOnDiskSizeWithHeader ();
119
210
assertTrue (
120
- "expected generated block size " + blockFromHFile .getOnDiskSizeWithHeader ()
121
- + " to be less than " + MIN_ALLOCATION_SIZE ,
122
- blockFromHFile .getOnDiskSizeWithHeader () < MIN_ALLOCATION_SIZE );
123
- assertTrue (
124
- "expected generated block uncompressed size "
125
- + blockFromHFile .getUncompressedSizeWithoutHeader () + " to be more than "
126
- + MIN_ALLOCATION_SIZE ,
127
- blockFromHFile .getUncompressedSizeWithoutHeader () > MIN_ALLOCATION_SIZE );
128
-
129
- HFileBlock blockUnpacked = blockFromHFile .unpack (meta , hbr );
130
- assertTrue ("expected unpacked block to be unpacked" , blockUnpacked .isUnpacked ());
131
- assertTrue ("expected unpacked block to use shared memory" , blockUnpacked .isSharedMem ());
211
+ "expected generated block size " + totalSize + " to be less than " + MIN_ALLOCATION_SIZE ,
212
+ totalSize < MIN_ALLOCATION_SIZE );
132
213
}
214
+ return totalSize ;
133
215
}
134
216
135
217
static int writeTestKeyValues (HFileBlock .Writer hbw , int desiredSize ) throws IOException {
0 commit comments