Author: stack
Date: Wed Aug 10 07:07:56 2011
New Revision: 1156049
URL: http://svn.apache.org/viewvc?rev=1156049&view=rev
Log:
HBASE-4185 Add doc for new hfilev2 format
Added:
hbase/trunk/src/site/resources/images/hfile.png (with props)
hbase/trunk/src/site/resources/images/hfilev2.png (with props)
Modified:
hbase/trunk/CHANGES.txt
hbase/trunk/pom.xml
hbase/trunk/src/docbkx/book.xml
hbase/trunk/src/docbkx/developer.xml
Modified: hbase/trunk/CHANGES.txt
URL:
http://svn.apache.org/viewvc/hbase/trunk/CHANGES.txt?rev=1156049&r1=1156048&r2=1156049&view=diff
==============================================================================
--- hbase/trunk/CHANGES.txt (original)
+++ hbase/trunk/CHANGES.txt Wed Aug 10 07:07:56 2011
@@ -386,6 +386,7 @@ Release 0.91.0 - Unreleased
o.a.h.h.regionserver.wal.WALActionsListener
HBASE-4039 Users should be able to choose custom TableInputFormats without
modifying TableMapReduceUtil.initTableMapperJob() (Brock Noland)
+ HBASE-4185 Add doc for new hfilev2 format
NEW FEATURES
HBASE-2001 Coprocessors: Colocate user code with regions (Mingjie Lai via
Modified: hbase/trunk/pom.xml
URL:
http://svn.apache.org/viewvc/hbase/trunk/pom.xml?rev=1156049&r1=1156048&r2=1156049&view=diff
==============================================================================
--- hbase/trunk/pom.xml (original)
+++ hbase/trunk/pom.xml Wed Aug 10 07:07:56 2011
@@ -332,6 +332,7 @@
<sectionLabelIncludesComponentLabel>true</sectionLabelIncludesComponentLabel>
<targetDirectory>${basedir}/target/site/book/</targetDirectory>
<htmlStylesheet>../css/freebsd_docbook.css</htmlStylesheet>
+ <imgSrcPath>../images/</imgSrcPath>
</configuration>
<goals>
<goal>generate-html</goal>
@@ -348,6 +349,7 @@
<sectionLabelIncludesComponentLabel>true</sectionLabelIncludesComponentLabel>
<targetDirectory>${basedir}/target/site/</targetDirectory>
<htmlStylesheet>css/freebsd_docbook.css</htmlStylesheet>
+ <imgSrcPath>images/</imgSrcPath>
</configuration>
<goals>
<goal>generate-html</goal>
Modified: hbase/trunk/src/docbkx/book.xml
URL:
http://svn.apache.org/viewvc/hbase/trunk/src/docbkx/book.xml?rev=1156049&r1=1156048&r2=1156049&view=diff
==============================================================================
--- hbase/trunk/src/docbkx/book.xml (original)
+++ hbase/trunk/src/docbkx/book.xml Wed Aug 10 07:07:56 2011
@@ -28,9 +28,15 @@
xmlns:html="http://www.w3.org/1999/xhtml"
xmlns:db="http://docbook.org/ns/docbook">
<info>
- <title>The Apache <link xlink:href="http://www.hbase.org">HBase</link>
+ <title>The Apache <link xlink:href="http://www.hbase.org">
+ <inlinemediaobject>
+ <imageobject>
+ <imagedata align="middle" valign="middle"
fileref="hbase_small.gif" />
+ </imageobject>
+ </inlinemediaobject>
+ </link>
Book</title>
- <copyright><year>2010</year><holder>Apache Software
Foundation</holder></copyright>
+ <copyright><year>2011</year><holder>Apache Software
Foundation</holder></copyright>
<abstract>
<para>This is the official book of
<link xlink:href="http://www.hbase.org">Apache HBase</link>,
@@ -1745,6 +1751,477 @@ When I build, why do I always get <code>
</appendix>
+<appendix xml:id="hfilev2">
+ <title>HFile format version 2</title>
+
+ <appendixinfo>
+ <personname>Mikhail Bautin</personname>
+ <authorgroup>
+ <author><personname>Mikhail Bautin</personname></author>
+ <author><personname>Liyin Tang</personname></author>
+ <author><personname>Kannan Muthukarrupan</personname></author>
+ </authorgroup>
+ </appendixinfo>
+
+ <section><title>Motivation </title>
+ <para>We found it necessary to revise the HFile format after encountering
high memory usage and slow startup times caused by large Bloom filters and
block indexes in the region server. Bloom filters can get as large as 100 MB
per HFile, which adds up to 2 GB when aggregated over 20 regions. Block indexes
can grow as large as 6 GB in aggregate size over the same set of regions. A
region is not considered opened until all of its block index data is loaded.
Large Bloom filters produce a different performance problem: the first get
request that requires a Bloom filter lookup will incur the latency of loading
the entire Bloom filter bit array.</para>
+ <para>To speed up region server startup we break Bloom filters and block
indexes into multiple blocks and write those blocks out as they fill up, which
also reduces the HFile writerâs memory footprint. In the Bloom filter case,
âfilling up a blockâ means accumulating enough keys to efficiently utilize
a fixed-size bit array, and in the block index case we accumulate an âindex
blockâ of the desired size. Bloom filter blocks and index blocks (we call
these âinline blocksâ) become interspersed with data blocks, and as a side
effect we can no longer rely on the difference between block offsets to
determine data block length, as it was done in version 1.</para>
+ <para>HFile is a low-level file format by design, and it should not deal
with application-specific details such as Bloom filters, which are handled at
StoreFile level. Therefore, we call Bloom filter blocks in an HFile "inline"
blocks. We also supply HFile with an interface to write those inline blocks.
</para>
+ <para>Another format modification aimed at reducing the region server
startup time is to use a contiguous âload-on-openâ section that has to be
loaded in memory at the time an HFile is being opened. Currently, as an HFile
opens, there are separate seek operations to read the trailer, data/meta
indexes, and file info. To read the Bloom filter, there are two more seek
operations for its âdataâ and âmetaâ portions. In version 2, we seek
once to read the trailer and seek again to read everything else we need to open
the file from a contiguous block.</para></section>
+ <section><title>HFile format version 1 overview </title><para>As we will be
discussing the changes we are making to the HFile format, it is useful to give
a short overview of the previous (HFile version 1) format. An HFile in the
existing format is structured as follows:
+ <inlinemediaobject>
+ <imageobject>
+ <imagedata align="middle" valign="middle"
fileref="hfile.png" />
+ </imageobject>
+ <textobject>
+ <phrase>HFile Version 1</phrase>
+ </textobject>
+ <caption>
+ <para>HFile Version 1
+ </para>
+ </caption>
+ </inlinemediaobject>
+ <footnote><para>Image courtesy of Lars George, <link
xlink:href="http://www.larsgeorge.com/2009/10/hbase-architecture-101-storage.html">hbase-architecture-101-storage.html</link>.</para></footnote>
+ </para>
+ <section><title> Block index format in version 1 </title>
+ <para>The block index in version 1 is very straightforward. For each entry,
it contains: </para>
+ <orderedlist>
+ <listitem>
+ <para>Offset (long)</para>
+ </listitem>
+ <listitem>
+ <para>Uncompressed size (int)</para>
+ </listitem>
+ <listitem>
+ <para>Key (a serialized byte array written using
Bytes.writeByteArray) </para>
+ <orderedlist>
+ <listitem>
+ <para>Key length as a variable-length integer (VInt)
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ Key bytes
+ </para>
+ </listitem>
+ </orderedlist>
+ </listitem>
+ </orderedlist>
+ <para>The number of entries in the block index is stored in the fixed file
trailer, and has to be passed in to the method that reads the block index. One
of the limitations of the block index in version 1 is that it does not provide
the compressed size of a block, which turns out to be necessary for
decompression. Therefore, the HFile reader has to infer this compressed size
from the offset difference between blocks. We fix this limitation in version 2,
where we store on-disk block size instead of uncompressed size, and get
uncompressed size from the block
header.</para></section></section><section><title>
+ HBase file format with inline blocks (version 2)
+ </title>
+ <section><title> Overview</title>
+ <para>The version of HBase introducing the above features reads both
version 1 and 2 HFiles, but only writes version 2 HFiles. A version 2 HFile is
structured as follows:
+ <inlinemediaobject>
+ <imageobject>
+ <imagedata align="middle" valign="middle"
fileref="hfilev2.png" />
+ </imageobject>
+ <textobject>
+ <phrase>HFile Version 2</phrase>
+ </textobject>
+ <caption>
+ <para>HFile Version 2
+ </para>
+ </caption>
+ </inlinemediaobject>
+
+ </para>
+ </section>
+ <section><title>Unified version 2 block format</title>
+ <para>In the version 2 every block in the data section contains the
following fields: </para>
+ <orderedlist>
+ <listitem>
+ <para>8 bytes: Block type, a sequence of bytes equivalent to version
1's "magic records". Supported block types are: </para>
+ <orderedlist>
+ <listitem>
+ <para>DATA â data blocks
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ LEAF_INDEX â leaf-level index blocks in a
multi-level-block-index
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ BLOOM_CHUNK â Bloom filter chunks
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ META â meta blocks (not used for Bloom filters in
version 2 anymore)
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ INTERMEDIATE_INDEX â intermediate-level index blocks in
a multi-level blockindex
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ ROOT_INDEX â root>level index blocks in a multi>level
block index
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ FILE_INFO â the âfile infoâ block, a small
key>value map of metadata
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ BLOOM_META â a Bloom filter metadata block in the
load>on>open section
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ TRAILER â a fixed>size file trailer. As opposed to the
above, this is not an
+ HFile v2 block but a fixed>size (for each HFile version)
data structure
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ INDEX_V1 â this block type is only used for legacy
HFile v1 block
+ </para>
+ </listitem>
+ </orderedlist>
+ </listitem>
+ <listitem>
+ <para>Compressed size of the block's data, not including the header
(int).
+ </para>
+ <para>
+Can be used for skipping the current data block when scanning HFile data.
+ </para>
+ </listitem>
+ <listitem>
+ <para>Uncompressed size of the block's data, not including the header
(int)</para>
+ <para>
+ This is equal to the compressed size if the compression algorithm is NON
+ </para>
+ </listitem>
+ <listitem>
+ <para>File offset of the previous block of the same type (long)</para>
+ <para>
+ Can be used for seeking to the previous data/index block
+ </para>
+ </listitem>
+ <listitem>
+ <para>Compressed data (or uncompressed data if the compression
algorithm is NONE).</para>
+ </listitem>
+ </orderedlist>
+ <para>The above format of blocks is used in the following HFile
sections:</para>
+ <orderedlist>
+ <listitem>
+ <para>Scanned block section. The section is named so because it
contains all data blocks that need to be read when an HFile is scanned
sequentially. Â Also contains leaf block index and Bloom chunk blocks. </para>
+ </listitem>
+ <listitem>
+ <para>Non-scanned block section. This section still contains
unified-format v2 blocks but it does not have to be read when doing a
sequential scan. This section contains âmetaâ blocks and intermediate-level
index blocks.
+ </para>
+ </listitem>
+ </orderedlist>
+ <para>We are supporting âmetaâ blocks in version 2 the same way they
were supported in version 1, even though we do not store Bloom filter data in
these blocks anymore. </para></section>
+
+<section><title> Block index in version 2</title>
+ <para>There are three types of block indexes in HFile version 2, stored in
two different formats (root and non-root): </para>
+ <orderedlist>
+ <listitem>
+ <para>Data index â version 2 multi-level block index, consisting
of:</para>
+ <orderedlist>
+ <listitem>
+ <para>
+ Version 2 root index, stored in the data block index section of the file
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+Optionally, version 2 intermediate levels, stored in the non%root format in
the data index section of the file. Intermediate levels can only be present
if leaf level blocks are present
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+Optionally, version 2 leaf levels, stored in the non%root format inline with
data blocks
+ </para>
+ </listitem>
+ </orderedlist>
+ </listitem>
+ <listitem>
+ <para>Meta index â version 2 root index format only, stored in the
meta index section of the file</para>
+ </listitem>
+ <listitem>
+ <para>Bloom index â version 2 root index format only, stored in the
âload-on-openâ section as part of Bloom filter metadata.</para>
+ </listitem>
+ </orderedlist></section>
+<section><title>
+ Root block index format in version 2</title>
+ <para>This format applies to:</para>
+ <orderedlist>
+ <listitem>
+ <para>Root level of the version 2 data index</para>
+ </listitem>
+ <listitem>
+ <para>Entire meta and Bloom indexes in version 2, which are always
single-level. </para>
+ </listitem>
+ </orderedlist>
+ <para>A version 2 root index block is a sequence of entries of the
following format, similar to entries of a version 1 block index, but storing
on-disk size instead of uncompressed size. </para>
+ <orderedlist>
+ <listitem>
+ <para>Offset (long) </para>
+ <para>
+This offset may point to a data block or to a deeper>level index block.
+ </para>
+ </listitem>
+ <listitem>
+ <para>On-disk size (int) </para>
+ </listitem>
+ <listitem>
+ <para>Key (a serialized byte array stored using Bytes.writeByteArray)
</para>
+ <orderedlist>
+ <listitem>
+ <para>Key (VInt)
+ </para>
+ </listitem>
+ <listitem>
+ <para>Key bytes
+ </para>
+ </listitem>
+ </orderedlist>
+ </listitem>
+ </orderedlist>
+ <para>A single-level version 2 block index consists of just a single root
index block. To read a root index block of version 2, one needs to know the
number of entries. For the data index and the meta index the number of entries
is stored in the trailer, and for the Bloom index it is stored in the compound
Bloom filter metadata.</para>
+
+ <para>For a multi-level block index we also store the following fields in
the root index block in the load-on-open section of the HFile, in addition to
the data structure described above:</para>
+ <orderedlist>
+ <listitem>
+ <para>Middle leaf index block offset</para>
+ </listitem>
+ <listitem>
+ <para>Middle leaf block on-disk size (meaning the leaf index block
containing the reference to the âmiddleâ data block of the file) </para>
+ </listitem>
+ <listitem>
+ <para>The index of the mid-key (defined below) in the middle
leaf-level block.</para>
+ </listitem>
+ </orderedlist>
+ <para/>
+ <para>These additional fields are used to efficiently retrieve the mid-key
of the HFile used in HFile splits, which we define as the first key of the
block with a zero-based index of (n â 1) / 2, if the total number of blocks
in the HFile is n. This definition is consistent with how the mid-key was
determined in HFile version 1, and is reasonable in general, because blocks are
likely to be the same size on average, but we donât have any estimates on
individual key/value pair sizes. </para>
+ <para/>
+ <para>When writing a version 2 HFile, the total number of data blocks
pointed to by every leaf-level index block is kept track of. When we finish
writing and the total number of leaf-level blocks is determined, it is clear
which leaf-level block contains the mid-key, and the fields listed above are
computed. Â When reading the HFile and the mid-key is requested, we retrieve
the middle leaf index block (potentially from the block cache) and get the
mid-key value from the appropriate position inside that leaf
block.</para></section>
+<section><title>
+ Non-root block index format in version 2</title>
+ <para>This format applies to intermediate-level and leaf index blocks of a
version 2 multi-level data block index. Every non-root index block is
structured as follows. </para>
+ <orderedlist>
+ <listitem>
+ <para>numEntries: the number of entries (int). </para>
+ </listitem>
+ <listitem>
+ <para>entryOffsets: the âsecondary indexâ of offsets of entries
in the block, to facilitate a quick binary search on the key (numEntries + 1
int values). The last value is the total length of all entries in this index
block. For example, in a non-root index block with entry sizes 60, 80, 50 the
âsecondary indexâ will contain the following int array: {0, 60, 140,
190}.</para>
+ </listitem>
+ <listitem>
+ <para>Entries. Each entry contains: </para>
+ <orderedlist>
+ <listitem>
+ <para>
+Offset of the block referenced by this entry in the file (long)
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+On>disk size of the referenced block (int)
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+Key. The length can be calculated from entryOffsets.
+ </para>
+ </listitem>
+ </orderedlist>
+
+ </listitem>
+ </orderedlist></section><section><title>
+ Bloom filters in version 2</title>
+ <para>In contrast with version 1, in a version 2 HFile Bloom filter
metadata is stored in the load-on-open section of the HFile for quick startup.
</para>
+ <orderedlist>
+ <listitem>
+ <para>A compound Bloom filter. </para>
+ <orderedlist>
+ <listitem>
+ <para>
+ Bloom filter version = 3 (int). There used to be a DynamicByteBloomFilter
class that had the Bloom filter version number 2
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+The total byte size of all compound Bloom filter chunks (long)
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ Number of hash functions (int
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+Type of hash functions (int)
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+The total key count inserted into the Bloom filter (long)
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+The maximum total number of keys in the Bloom filter (long)
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+The number of chunks (int)
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+Comparator class used for Bloom filter keys, a UTF>8 encoded string stored
using Bytes.writeByteArray
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ Bloom block index in the version 2 root block index format
+ </para>
+ </listitem>
+ </orderedlist>
+ </listitem>
+ </orderedlist></section><section><title>File Info format in versions 1 and
2</title>
+ <para>The file info block is a serialized <ulink
url="http://hbase.apache.org/apidocs/org/apache/hadoop/hbase/io/HbaseMapWritable.html">HbaseMapWritable</ulink>
(essentially a map from byte arrays to byte arrays) with the following keys,
among others. StoreFile-level logic adds more keys to this.</para>
+ <informaltable frame="all">
+ <tgroup cols="2"><tbody><row>
+ <entry>
+ <para>hfile.LASTKEY </para>
+ </entry>
+ <entry>
+ <para>The last key of the file (byte array) </para>
+ </entry>
+ </row>
+ <row>
+ <entry>
+ <para>hfile.AVG_KEY_LEN </para>
+ </entry>
+ <entry>
+ <para>The average key length in the file (int) </para>
+ </entry>
+ </row>
+ <row>
+ <entry>
+ <para>hfile.AVG_VALUE_LEN </para>
+ </entry>
+ <entry>
+ <para>The average value length in the file (int) </para>
+ </entry>
+ </row></tbody></tgroup>
+ </informaltable>
+ <para>File info format did not change in version 2. However, we moved the
file info to the final section of the file, which can be loaded as one block at
the time the HFile is being opened. Also, we do not store comparator in the
version 2 file info anymore. Instead, we store it in the fixed file trailer.
This is because we need to know the comparator at the time of parsing the
load-on-open section of the HFile.</para></section><section><title>
+ Fixed file trailer format differences between versions 1 and 2</title>
+ <para>The following table shows common and different fields between fixed
file trailers in versions 1 and 2. Note that the size of the trailer is
different depending on the version, so it is âfixedâ only within one
version. However, the version is always stored as the last four-byte integer in
the file. </para>
+ <para/>
+ <informaltable frame="all">
+ <tgroup cols="2">
+<colspec colname='c1'/>
+<colspec colname='c2'/>
+<tbody>
+ <row>
+ <entry>
+ <para>Version 1 </para>
+ </entry>
+ <entry>
+ <para>Version 2 </para>
+ </entry>
+ </row>
+ <row>
+ <entry align="center" namest="c1" nameend="c2">
+ <para>File info offset (long) </para>
+ </entry>
+ </row>
+ <row>
+ <entry>
+ <para>Data index offset (long) </para>
+ </entry>
+ <entry>
+ <para>loadOnOpenOffset (long)</para>
+ <para><emphasis>The offset of the section that we need toload
when opening the file.</emphasis></para>
+ </entry>
+ </row>
+ <row>
+ <entry align="center" namest="c1" nameend="c2">
+ <para>Number of data index entries (int) </para>
+ </entry>
+ </row>
+ <row>
+ <entry>
+ <para>metaIndexOffset (long)</para>
+ <para>This field is not being used by the version 1 reader, so
we removed it from version 2.</para>
+ </entry>
+ <entry>
+ <para>uncompressedDataIndexSize (long)</para>
+ <para>The total uncompressed size of the whole data block
index, including root-level, intermediate-level, and leaf-level blocks.</para>
+ </entry>
+ </row>
+ <row>
+ <entry namest="c1" nameend="c2" align="center">
+ <para>Number of meta index entries (int) </para>
+ </entry>
+ </row>
+ <row>
+ <entry namest="c1" nameend="c2" align="center">
+ <para>Total uncompressed bytes (long) </para>
+ </entry>
+ </row>
+ <row>
+ <entry>
+ <para>numEntries (int) </para>
+ </entry>
+ <entry>
+ <para>numEntries (long) </para>
+ </entry>
+ </row>
+ <row>
+ <entry namest="c1" nameend="c2" align="center">
+ <para>Compression codec: 0 = LZO, 1 = GZ, 2 = NONE (int) </para>
+ </entry>
+ </row>
+ <row>
+ <entry>
+ <para></para>
+ </entry>
+ <entry>
+ <para>The number of levels in the data block index (int) </para>
+ </entry>
+ </row>
+ <row>
+ <entry>
+ <para></para>
+ </entry>
+ <entry>
+ <para>firstDataBlockOffset (long)</para>
+ <para>The offset of the first first data block. Used when
scanning. </para>
+ </entry>
+ </row>
+ <row>
+ <entry>
+ <para></para>
+ </entry>
+ <entry>
+ <para>lastDataBlockEnd (long)</para>
+ <para>The offset of the first byte after the last key/value
data block. We don't need to go beyond this offset when scanning. </para>
+ </entry>
+ </row>
+ <row>
+ <entry>
+ <para>Version: 1 (int) </para>
+ </entry>
+ <entry>
+ <para>Version: 2 (int) </para>
+ </entry>
+ </row></tbody></tgroup>
+ </informaltable>
+ <para/></section></section></appendix>
<index xml:id="book_index">
Modified: hbase/trunk/src/docbkx/developer.xml
URL:
http://svn.apache.org/viewvc/hbase/trunk/src/docbkx/developer.xml?rev=1156049&r1=1156048&r2=1156049&view=diff
==============================================================================
--- hbase/trunk/src/docbkx/developer.xml (original)
+++ hbase/trunk/src/docbkx/developer.xml Wed Aug 10 07:07:56 2011
@@ -112,7 +112,7 @@ mvn test -Dtest=TestXYZ
<link
xlink:href="http://blog.sematext.com/2010/08/30/hbase-case-study-using-hbasetestingutility-for-local-testing-development/">HBase
Case-Study: Using HBaseTestingUtility for Local Testing and Development</link>
(2010).
</para>
<section xml:id="mockito">
- <title>Mocito</title>
+ <title>Mockito</title>
<para>Sometimes you don't need a full running server
unit testing. For example, some methods can make do with a
a <classname>org.apache.hadoop.hbase.Server</classname> instance
Added: hbase/trunk/src/site/resources/images/hfile.png
URL:
http://svn.apache.org/viewvc/hbase/trunk/src/site/resources/images/hfile.png?rev=1156049&view=auto
==============================================================================
Binary file - no diff available.
Propchange: hbase/trunk/src/site/resources/images/hfile.png
------------------------------------------------------------------------------
svn:mime-type = application/octet-stream
Added: hbase/trunk/src/site/resources/images/hfilev2.png
URL:
http://svn.apache.org/viewvc/hbase/trunk/src/site/resources/images/hfilev2.png?rev=1156049&view=auto
==============================================================================
Binary file - no diff available.
Propchange: hbase/trunk/src/site/resources/images/hfilev2.png
------------------------------------------------------------------------------
svn:mime-type = application/octet-stream