diff options
29 files changed, 905 insertions, 184 deletions
diff --git a/bench/wtperf/wtperf.c b/bench/wtperf/wtperf.c index a1dc78a5312..8c7f0053388 100644 --- a/bench/wtperf/wtperf.c +++ b/bench/wtperf/wtperf.c @@ -2078,6 +2078,11 @@ config_compress(WTPERF *wtperf) wtperf->compress_ext = ZLIB_EXT; #endif wtperf->compress_table = ZLIB_BLK; + } else if (strcmp(s, "zstd") == 0) { +#ifndef HAVE_BUILTIN_EXTENSION_ZSTD + wtperf->compress_ext = ZSTD_EXT; +#endif + wtperf->compress_table = ZSTD_BLK; } else { fprintf(stderr, "invalid compression configuration: %s\n", s); diff --git a/bench/wtperf/wtperf.h b/bench/wtperf/wtperf.h index afce017d919..81d74e134f6 100644 --- a/bench/wtperf/wtperf.h +++ b/bench/wtperf/wtperf.h @@ -54,6 +54,9 @@ typedef struct __truncate_queue_entry TRUNCATE_QUEUE_ENTRY; #define ZLIB_BLK BLKCMP_PFX "zlib" #define ZLIB_EXT \ EXT_PFX EXTPATH "zlib/.libs/libwiredtiger_zlib.so" EXT_SFX +#define ZSTD_BLK BLKCMP_PFX "zstd" +#define ZSTD_EXT \ + EXT_PFX EXTPATH "zstd/.libs/libwiredtiger_zstd.so" EXT_SFX typedef struct { int64_t threads; /* Thread count */ diff --git a/bench/wtperf/wtperf_opt.i b/bench/wtperf/wtperf_opt.i index d2d255b38c2..680eb53a90e 100644 --- a/bench/wtperf/wtperf_opt.i +++ b/bench/wtperf/wtperf_opt.i @@ -100,7 +100,7 @@ DEF_OPT_AS_BOOL(close_conn, 1, "properly close connection at end of test. " DEF_OPT_AS_BOOL(compact, 0, "post-populate compact for LSM merging activity") DEF_OPT_AS_STRING(compression, "none", "compression extension. Allowed configuration values are: " - "'none', 'lz4', 'snappy', 'zlib'") + "'none', 'lz4', 'snappy', 'zlib', 'zstd'") DEF_OPT_AS_BOOL(create, 1, "do population phase; false to use existing database") DEF_OPT_AS_UINT32(database_count, 1, diff --git a/build_posix/Make.base b/build_posix/Make.base index 4efbe3f76c3..5b945aca5e0 100644 --- a/build_posix/Make.base +++ b/build_posix/Make.base @@ -77,6 +77,9 @@ endif if HAVE_BUILTIN_EXTENSION_ZLIB libwiredtiger_la_LIBADD += ext/compressors/zlib/libwiredtiger_zlib.la endif +if HAVE_BUILTIN_EXTENSION_ZSTD +libwiredtiger_la_LIBADD += ext/compressors/zstd/libwiredtiger_zstd.la +endif libwiredtiger_static_la_LIBADD=$(libwiredtiger_la_LIBADD) libwiredtiger_static_la_SOURCES=$(libwiredtiger_la_SOURCES) diff --git a/build_posix/Make.subdirs b/build_posix/Make.subdirs index 0b5175e4196..55941837249 100644 --- a/build_posix/Make.subdirs +++ b/build_posix/Make.subdirs @@ -11,6 +11,7 @@ ext/compressors/lz4 LZ4 ext/compressors/nop ext/compressors/snappy SNAPPY ext/compressors/zlib ZLIB +ext/compressors/zstd ZSTD ext/datasources/helium HAVE_HELIUM ext/encryptors/nop ext/encryptors/rotn diff --git a/build_posix/aclocal/options.m4 b/build_posix/aclocal/options.m4 index 1f6a1690279..7043430a6d6 100644 --- a/build_posix/aclocal/options.m4 +++ b/build_posix/aclocal/options.m4 @@ -19,10 +19,12 @@ AH_TEMPLATE(HAVE_BUILTIN_EXTENSION_SNAPPY, [Snappy support automatically loaded.]) AH_TEMPLATE(HAVE_BUILTIN_EXTENSION_ZLIB, [Zlib support automatically loaded.]) +AH_TEMPLATE(HAVE_BUILTIN_EXTENSION_ZSTD, + [ZSTD support automatically loaded.]) AC_MSG_CHECKING(if --with-builtins option specified) AC_ARG_WITH(builtins, [AS_HELP_STRING([--with-builtins], - [builtin extension names (lz4, snappy, zlib).])], + [builtin extension names (lz4, snappy, zlib, zstd).])], [with_builtins=$withval], [with_builtins=]) @@ -36,6 +38,8 @@ for builtin_i in $builtin_list; do wt_cv_with_builtin_extension_snappy=yes;; zlib) AC_DEFINE(HAVE_BUILTIN_EXTENSION_ZLIB) wt_cv_with_builtin_extension_zlib=yes;; + zstd) AC_DEFINE(HAVE_BUILTIN_EXTENSION_ZSTD) + wt_cv_with_builtin_extension_zstd=yes;; *) AC_MSG_ERROR([Unknown builtin extension "$builtin_i"]);; esac done @@ -45,6 +49,8 @@ AM_CONDITIONAL([HAVE_BUILTIN_EXTENSION_SNAPPY], [test "$wt_cv_with_builtin_extension_snappy" = "yes"]) AM_CONDITIONAL([HAVE_BUILTIN_EXTENSION_ZLIB], [test "$wt_cv_with_builtin_extension_zlib" = "yes"]) +AM_CONDITIONAL([HAVE_BUILTIN_EXTENSION_ZSTD], + [test "$wt_cv_with_builtin_extension_zstd" = "yes"]) AC_MSG_RESULT($with_builtins) AH_TEMPLATE( @@ -276,4 +282,30 @@ if test "$wt_cv_enable_zlib" = "yes"; then fi AM_CONDITIONAL([ZLIB], [test "$wt_cv_enable_zlib" = "yes"]) +AC_MSG_CHECKING(if --enable-zstd option specified) +AC_ARG_ENABLE(zstd, + [AS_HELP_STRING([--enable-zstd], + [Build the zstd compressor extension.])], r=$enableval, r=no) +case "$r" in +no) if test "$wt_cv_with_builtin_extension_zstd" = "yes"; then + wt_cv_enable_zstd=yes + else + wt_cv_enable_zstd=no + fi + ;; +*) if test "$wt_cv_with_builtin_extension_zstd" = "yes"; then + AC_MSG_ERROR( + [Only one of --enable-zstd --with-builtins=zstd allowed]) + fi + wt_cv_enable_zstd=yes;; +esac +AC_MSG_RESULT($wt_cv_enable_zstd) +if test "$wt_cv_enable_zstd" = "yes"; then + AC_CHECK_HEADER(zstd.h,, + [AC_MSG_ERROR([--enable-zstd requires zstd.h])]) + AC_CHECK_LIB(zstd, ZSTD_compress,, + [AC_MSG_ERROR([--enable-zstd requires Zstd library])]) +fi +AM_CONDITIONAL([ZSTD], [test "$wt_cv_enable_zstd" = "yes"]) + ]) diff --git a/build_win/wiredtiger_config.h b/build_win/wiredtiger_config.h index 83ddc6eb194..78d2784cb70 100644 --- a/build_win/wiredtiger_config.h +++ b/build_win/wiredtiger_config.h @@ -19,6 +19,9 @@ /* Zlib support automatically loaded. */ /* #undef HAVE_BUILTIN_EXTENSION_ZLIB */ +/* ZSTD support automatically loaded. */ +/* #undef HAVE_BUILTIN_EXTENSION_ZSTD */ + /* Define to 1 if you have the `clock_gettime' function. */ /* #undef HAVE_CLOCK_GETTIME */ @@ -70,6 +73,9 @@ /* Define to 1 if you have the `z' library (-lz). */ /* #undef HAVE_LIBZ */ +/* Define to 1 if you have the `zstd' library (-lzstd). */ +/* #undef HAVE_LIBZSTD */ + /* Define to 1 if you have the <memory.h> header file. */ /* #undef HAVE_MEMORY_H */ diff --git a/dist/api_data.py b/dist/api_data.py index df3577f56b8..7affc58a217 100644 --- a/dist/api_data.py +++ b/dist/api_data.py @@ -136,8 +136,8 @@ file_config = format_meta + [ configure a compressor for file blocks. Permitted values are \c "none" or custom compression engine name created with WT_CONNECTION::add_compressor. If WiredTiger has builtin support for - \c "snappy", \c "lz4" or \c "zlib" compression, these names are also - available. See @ref compression for more information'''), + \c "lz4", \c "snappy", \c "zlib" or \c "zstd" compression, these names + are also available. See @ref compression for more information'''), Config('cache_resident', 'false', r''' do not ever evict the object's pages from cache. Not compatible with LSM tables; see @ref tuning_cache_resident for more information''', @@ -570,8 +570,9 @@ wiredtiger_open_log_configuration = [ configure a compressor for log records. Permitted values are \c "none" or custom compression engine name created with WT_CONNECTION::add_compressor. If WiredTiger has builtin support - for \c "snappy", \c "lz4" or \c "zlib" compression, these names - are also available. See @ref compression for more information'''), + for \c "lz4", \c "snappy", \c "zlib" or \c "zstd" compression, + these names are also available. See @ref compression for more + information'''), Config('file_max', '100MB', r''' the maximum size of log files''', min='100KB', max='2GB'), diff --git a/dist/s_export b/dist/s_export index dc69238b270..b8e42c970f9 100755 --- a/dist/s_export +++ b/dist/s_export @@ -26,7 +26,7 @@ check() sort | uniq -u | egrep -v \ - 'zlib_extension_init|lz4_extension_init|snappy_extension_init' > $t + 'lz4_extension_init|snappy_extension_init|zlib_extension_init|zstd_extension_init' > $t test -s $t && { echo "=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=" diff --git a/dist/s_string.ok b/dist/s_string.ok index a80af2f18e1..7cf96aec399 100644 --- a/dist/s_string.ok +++ b/dist/s_string.ok @@ -60,6 +60,7 @@ COVERITY CPUs CRC CSV +CStream CURSORs CURSTD CallsCustDate @@ -69,6 +70,7 @@ Checksum Checksums CityHash CloseHandle +Collet Comparator Config Coverity @@ -125,6 +127,7 @@ FORALL FOREACH FS FULLFSYNC +Facebook FindClose FindFirstFile Fixup @@ -398,6 +401,12 @@ WriteFile Wuninitialized Wunused XP +Yann +ZSTD +Zlib +Zlib's +Zstd +Zstd's abcdef abcdefghijklmnopqrstuvwxyz addl @@ -515,6 +524,7 @@ collatorp comparator comparep compat +compressStream concat cond conf @@ -534,6 +544,7 @@ cp cpuid crc create's +createCStream crypto cryptobad csv @@ -626,6 +637,7 @@ emp encodings encryptor encryptors +endStream endian english enqueue @@ -753,6 +765,7 @@ infeasible inflateInit infmt init +initCStream initializers initn initsize @@ -854,6 +867,7 @@ majorp malloc marshall marshalled +maxCLevel maxcpu maxdbs mbll @@ -1233,4 +1247,7 @@ zalloc zf zfree zlib +zlib's +zstd +zstd's zu diff --git a/dist/s_void b/dist/s_void index f7bfbcc7e8e..e5e9f97c0b7 100644 --- a/dist/s_void +++ b/dist/s_void @@ -96,10 +96,13 @@ func_ok() -e '/int wiredtiger_extension_init$/d' \ -e '/int wiredtiger_extension_terminate$/d' \ -e '/int wiredtiger_pack_close$/d' \ - -e '/int wt_snappy_pre_size$/d' \ - -e '/int wt_snappy_terminate$/d' \ + -e '/int snappy_pre_size$/d' \ + -e '/int snappy_terminate$/d' \ -e '/int zlib_error$/d' \ - -e '/int zlib_terminate$/d' + -e '/int zlib_terminate$/d' \ + -e '/int zstd_error$/d' \ + -e '/int zstd_pre_size$/d' \ + -e '/int zstd_terminate$/d' } # Complain about functions which return an "int" but which don't return except diff --git a/examples/c/ex_all.c b/examples/c/ex_all.c index a2042c22bbb..ea646604a76 100644 --- a/examples/c/ex_all.c +++ b/examples/c/ex_all.c @@ -611,6 +611,13 @@ session_ops(WT_SESSION *session) "block_compressor=zlib,key_format=S,value_format=S"); /*! [Create a zlib compressed table] */ ret = session->drop(session, "table:mytable", NULL); + + /*! [Create a zstd compressed table] */ + ret = session->create(session, + "table:mytable", + "block_compressor=zstd,key_format=S,value_format=S"); + /*! [Create a zstd compressed table] */ + ret = session->drop(session, "table:mytable", NULL); #endif /*! [Configure checksums to uncompressed] */ @@ -1108,6 +1115,32 @@ main(void) if (ret == 0) (void)conn->close(conn, NULL); + /*! [Configure zlib extension with compression level] */ + ret = wiredtiger_open(home, NULL, + "create," + "extensions=[/usr/local/lib/" + "libwiredtiger_zlib.so=[config=[compression_level=3]]]", &conn); + /*! [Configure zlib extension with compression level] */ + if (ret == 0) + (void)conn->close(conn, NULL); + + /*! [Configure zstd extension] */ + ret = wiredtiger_open(home, NULL, + "create," + "extensions=[/usr/local/lib/libwiredtiger_zstd.so]", &conn); + /*! [Configure zstd extension] */ + if (ret == 0) + (void)conn->close(conn, NULL); + + /*! [Configure zstd extension with compression level] */ + ret = wiredtiger_open(home, NULL, + "create," + "extensions=[/usr/local/lib/" + "libwiredtiger_zstd.so=[config=[compression_level=9]]]", &conn); + /*! [Configure zstd extension with compression level] */ + if (ret == 0) + (void)conn->close(conn, NULL); + /* * This example code gets run, and direct I/O might not be available, * causing the open to fail. The documentation requires code snippets, diff --git a/examples/java/com/wiredtiger/examples/ex_all.java b/examples/java/com/wiredtiger/examples/ex_all.java index 83a37e9a6a5..cf8491aa4f8 100644 --- a/examples/java/com/wiredtiger/examples/ex_all.java +++ b/examples/java/com/wiredtiger/examples/ex_all.java @@ -549,6 +549,12 @@ session_ops(Session session) "block_compressor=zlib,key_format=S,value_format=S"); /*! [Create a zlib compressed table] */ ret = session.drop("table:mytable", null); + + /*! [Create a zstd compressed table] */ + ret = session.create("table:mytable", + "block_compressor=zstd,key_format=S,value_format=S"); + /*! [Create a zstd compressed table] */ + ret = session.drop("table:mytable", null); } // if (false) /*! [Configure checksums to uncompressed] */ @@ -942,6 +948,29 @@ allExample() /*! [Configure zlib extension] */ conn.close(null); + /*! [Configure zlib extension with compression level] */ + conn = wiredtiger.open(home, + "create," + + "extensions=[/usr/local/lib/" + + "libwiredtiger_zlib.so=[config=[compression_level=3]]]"); + /*! [Configure zlib extension with compression level] */ + conn.close(null); + + /*! [Configure zstd extension] */ + conn = wiredtiger.open(home, + "create," + + "extensions=[/usr/local/lib/libwiredtiger_zstd.so]"); + /*! [Configure zstd extension] */ + conn.close(null); + + /*! [Configure zstd extension with compression level] */ + conn = wiredtiger.open(home, + "create," + + "extensions=[/usr/local/lib/" + + "libwiredtiger_zstd.so=[config=[compression_level=9]]]"); + /*! [Configure zstd extension with compression level] */ + conn.close(null); + /* * This example code gets run, and direct I/O might not be available, * causing the open to fail. The documentation requires code snippets, diff --git a/ext/compressors/lz4/lz4_compress.c b/ext/compressors/lz4/lz4_compress.c index 35159d0fa76..885701e564b 100644 --- a/ext/compressors/lz4/lz4_compress.c +++ b/ext/compressors/lz4/lz4_compress.c @@ -31,10 +31,20 @@ #include <stdlib.h> #include <string.h> +/* + * We need to include the configuration file to detect whether this extension + * is being built into the WiredTiger library; application-loaded compression + * functions won't need it. + */ #include <wiredtiger_config.h> + #include <wiredtiger.h> #include <wiredtiger_ext.h> +#ifdef _MSC_VER +#define inline __inline +#endif + /* Local compressor structure. */ typedef struct { WT_COMPRESSOR compressor; /* Must come first */ @@ -171,8 +181,6 @@ lz4_decompress(WT_COMPRESSOR *compressor, WT_SESSION *session, int decoded; uint8_t *dst_tmp; - (void)src_len; /* Unused parameters */ - wt_api = ((LZ4_COMPRESSOR *)compressor)->wt_api; /* @@ -183,6 +191,13 @@ lz4_decompress(WT_COMPRESSOR *compressor, WT_SESSION *session, #ifdef WORDS_BIGENDIAN lz4_prefix_swap(&prefix); #endif + if (prefix.compressed_len + sizeof(LZ4_PREFIX) > src_len) { + (void)wt_api->err_printf(wt_api, + session, + "WT_COMPRESSOR.decompress: stored size exceeds source " + "size"); + return (WT_ERROR); + } /* * Decompress, starting after the prefix bytes. Use safe decompression: @@ -267,18 +282,24 @@ lz4_compress_raw(WT_COMPRESSOR *compressor, WT_SESSION *session, size_t *result_lenp, uint32_t *result_slotsp) { LZ4_PREFIX prefix; - int lz4_len; uint32_t slot; - int sourceSize, targetDestSize; + int lz4_len, sourceSize, targetDestSize; (void)compressor; /* Unused parameters */ (void)session; (void)split_pct; (void)final; - sourceSize = (int)offsets[slots]; /* Type conversion */ - targetDestSize = - (int)((dst_len < page_max ? dst_len : page_max) - extra); + /* + * Set the source and target sizes. The target size is complicated: we + * don't want to exceed the smaller of the maximum page size or the + * destination buffer length, and in both cases we have to take into + * account the space for our overhead and the extra bytes required by + * our caller. + */ + sourceSize = (int)offsets[slots]; + targetDestSize = (int)(page_max < dst_len ? page_max : dst_len); + targetDestSize -= (int)(sizeof(LZ4_PREFIX) + extra); /* Compress, starting after the prefix bytes. */ lz4_len = LZ4_compress_destSize((const char *)src, @@ -352,7 +373,7 @@ lz4_terminate(WT_COMPRESSOR *compressor, WT_SESSION *session) * Add a LZ4 compressor. */ static int -lz_add_compressor(WT_CONNECTION *connection, int raw, const char *name) +lz_add_compressor(WT_CONNECTION *connection, bool raw, const char *name) { LZ4_COMPRESSOR *lz4_compressor; @@ -391,9 +412,9 @@ lz4_extension_init(WT_CONNECTION *connection, WT_CONFIG_ARG *config) (void)config; /* Unused parameters */ - if ((ret = lz_add_compressor(connection, 1, "lz4")) != 0) + if ((ret = lz_add_compressor(connection, true, "lz4")) != 0) return (ret); - if ((ret = lz_add_compressor(connection, 0, "lz4-noraw")) != 0) + if ((ret = lz_add_compressor(connection, false, "lz4-noraw")) != 0) return (ret); return (0); } diff --git a/ext/compressors/snappy/snappy_compress.c b/ext/compressors/snappy/snappy_compress.c index 981e334a2de..32f1ddcb9a0 100644 --- a/ext/compressors/snappy/snappy_compress.c +++ b/ext/compressors/snappy/snappy_compress.c @@ -31,10 +31,20 @@ #include <stdlib.h> #include <string.h> +/* + * We need to include the configuration file to detect whether this extension + * is being built into the WiredTiger library; application-loaded compression + * functions won't need it. + */ #include <wiredtiger_config.h> + #include <wiredtiger.h> #include <wiredtiger_ext.h> +#ifdef _MSC_VER +#define inline __inline +#endif + /* Local compressor structure. */ typedef struct { WT_COMPRESSOR compressor; /* Must come first */ @@ -42,6 +52,12 @@ typedef struct { WT_EXTENSION_API *wt_api; /* Extension API */ } SNAPPY_COMPRESSOR; +/* + * Snappy decompression requires an exact compressed byte count. WiredTiger + * doesn't track that value, store it in the destination buffer. + */ +#define SNAPPY_PREFIX sizeof(uint64_t) + #ifdef WORDS_BIGENDIAN /* * snappy_bswap64 -- @@ -64,11 +80,11 @@ snappy_bswap64(uint64_t v) #endif /* - * wt_snappy_error -- + * snappy_error -- * Output an error message, and return a standard error code. */ static int -wt_snappy_error(WT_COMPRESSOR *compressor, +snappy_error(WT_COMPRESSOR *compressor, WT_SESSION *session, const char *call, snappy_status snret) { WT_EXTENSION_API *wt_api; @@ -94,68 +110,69 @@ wt_snappy_error(WT_COMPRESSOR *compressor, } /* - * wt_snappy_compress -- + * snappy_compression -- * WiredTiger snappy compression. */ static int -wt_snappy_compress(WT_COMPRESSOR *compressor, WT_SESSION *session, +snappy_compression(WT_COMPRESSOR *compressor, WT_SESSION *session, uint8_t *src, size_t src_len, uint8_t *dst, size_t dst_len, size_t *result_lenp, int *compression_failed) { snappy_status snret; size_t snaplen; + uint64_t snaplen_u64; char *snapbuf; /* - * dst_len was computed in wt_snappy_pre_size, so we know it's big - * enough. Skip past the space we'll use to store the final count - * of compressed bytes. + * dst_len was computed in snappy_pre_size, so we know it's big enough. + * Skip past the space we'll use to store the final count of compressed + * bytes. */ - snaplen = dst_len - sizeof(size_t); - snapbuf = (char *)dst + sizeof(size_t); + snaplen = dst_len - SNAPPY_PREFIX; + snapbuf = (char *)dst + SNAPPY_PREFIX; /* snaplen is an input and an output arg. */ snret = snappy_compress((char *)src, src_len, snapbuf, &snaplen); - if (snret == SNAPPY_OK) { - if (snaplen + sizeof(size_t) < src_len) { - *result_lenp = snaplen + sizeof(size_t); - *compression_failed = 0; - - /* - * On decompression, snappy requires an exact compressed - * byte count (the current value of snaplen). WiredTiger - * does not preserve that value, so save snaplen at the - * beginning of the destination buffer. - * - * Store the value in little-endian format. - */ + if (snret == SNAPPY_OK && snaplen + SNAPPY_PREFIX < src_len) { + *result_lenp = snaplen + SNAPPY_PREFIX; + *compression_failed = 0; + + /* + * On decompression, snappy requires an exact compressed byte + * count (the current value of snaplen). WiredTiger does not + * preserve that value, so save snaplen at the beginning of + * the destination buffer. + * + * Store the value in little-endian format. + */ + snaplen_u64 = snaplen; #ifdef WORDS_BIGENDIAN - snaplen = snappy_bswap64(snaplen); + snaplen_u64 = snappy_bswap64(snaplen_u64); #endif - *(size_t *)dst = snaplen; - } else - /* The compressor failed to produce a smaller result. */ - *compression_failed = 1; + *(uint64_t *)dst = snaplen_u64; return (0); } - return (wt_snappy_error(compressor, session, "snappy_compress", snret)); + + *compression_failed = 1; + return (snret == SNAPPY_OK ? + 0 : snappy_error(compressor, session, "snappy_compress", snret)); } /* - * wt_snappy_decompress -- + * snappy_decompression -- * WiredTiger snappy decompression. */ static int -wt_snappy_decompress(WT_COMPRESSOR *compressor, WT_SESSION *session, +snappy_decompression(WT_COMPRESSOR *compressor, WT_SESSION *session, uint8_t *src, size_t src_len, uint8_t *dst, size_t dst_len, size_t *result_lenp) { WT_EXTENSION_API *wt_api; snappy_status snret; - size_t snaplen; + uint64_t snaplen; wt_api = ((SNAPPY_COMPRESSOR *)compressor)->wt_api; @@ -163,36 +180,36 @@ wt_snappy_decompress(WT_COMPRESSOR *compressor, WT_SESSION *session, * Retrieve the saved length, handling little- to big-endian conversion * as necessary. */ - snaplen = *(size_t *)src; + snaplen = *(uint64_t *)src; #ifdef WORDS_BIGENDIAN snaplen = snappy_bswap64(snaplen); #endif - if (snaplen + sizeof(size_t) > src_len) { + if (snaplen + SNAPPY_PREFIX > src_len) { (void)wt_api->err_printf(wt_api, session, - "wt_snappy_decompress: stored size exceeds buffer size"); + "WT_COMPRESSOR.decompress: stored size exceeds source " + "size"); return (WT_ERROR); } /* dst_len is an input and an output arg. */ snret = snappy_uncompress( - (char *)src + sizeof(size_t), snaplen, (char *)dst, &dst_len); + (char *)src + SNAPPY_PREFIX, + (size_t)snaplen, (char *)dst, &dst_len); if (snret == SNAPPY_OK) { *result_lenp = dst_len; return (0); } - - return ( - wt_snappy_error(compressor, session, "snappy_decompress", snret)); + return (snappy_error(compressor, session, "snappy_decompress", snret)); } /* - * wt_snappy_pre_size -- + * snappy_pre_size -- * WiredTiger snappy destination buffer sizing. */ static int -wt_snappy_pre_size(WT_COMPRESSOR *compressor, WT_SESSION *session, +snappy_pre_size(WT_COMPRESSOR *compressor, WT_SESSION *session, uint8_t *src, size_t src_len, size_t *result_lenp) { @@ -203,19 +220,19 @@ wt_snappy_pre_size(WT_COMPRESSOR *compressor, WT_SESSION *session, /* * Snappy requires the dest buffer be somewhat larger than the source. * Fortunately, this is fast to compute, and will give us a dest buffer - * in wt_snappy_compress that we can compress to directly. We add space + * in snappy_compress that we can compress to directly. We add space * in the dest buffer to store the accurate compressed size. */ - *result_lenp = snappy_max_compressed_length(src_len) + sizeof(size_t); + *result_lenp = snappy_max_compressed_length(src_len) + SNAPPY_PREFIX; return (0); } /* - * wt_snappy_terminate -- + * snappy_terminate -- * WiredTiger snappy compression termination. */ static int -wt_snappy_terminate(WT_COMPRESSOR *compressor, WT_SESSION *session) +snappy_terminate(WT_COMPRESSOR *compressor, WT_SESSION *session) { (void)session; /* Unused parameters */ @@ -227,9 +244,9 @@ int snappy_extension_init(WT_CONNECTION *, WT_CONFIG_ARG *); /* * snappy_extension_init -- - * WiredTiger snappy compression extension - called directly when - * Snappy support is built in, or via wiredtiger_extension_init when - * snappy support is included via extension loading. + * WiredTiger snappy compression extension - called directly when snappy + * support is built in, or via wiredtiger_extension_init when snappy support + * is included via extension loading. */ int snappy_extension_init(WT_CONNECTION *connection, WT_CONFIG_ARG *config) @@ -241,11 +258,11 @@ snappy_extension_init(WT_CONNECTION *connection, WT_CONFIG_ARG *config) if ((snappy_compressor = calloc(1, sizeof(SNAPPY_COMPRESSOR))) == NULL) return (errno); - snappy_compressor->compressor.compress = wt_snappy_compress; + snappy_compressor->compressor.compress = snappy_compression; snappy_compressor->compressor.compress_raw = NULL; - snappy_compressor->compressor.decompress = wt_snappy_decompress; - snappy_compressor->compressor.pre_size = wt_snappy_pre_size; - snappy_compressor->compressor.terminate = wt_snappy_terminate; + snappy_compressor->compressor.decompress = snappy_decompression; + snappy_compressor->compressor.pre_size = snappy_pre_size; + snappy_compressor->compressor.terminate = snappy_terminate; snappy_compressor->wt_api = connection->get_extension_api(connection); diff --git a/ext/compressors/zlib/zlib_compress.c b/ext/compressors/zlib/zlib_compress.c index 484df0a6785..ef20503df0a 100644 --- a/ext/compressors/zlib/zlib_compress.c +++ b/ext/compressors/zlib/zlib_compress.c @@ -32,16 +32,18 @@ #include <stdlib.h> #include <string.h> -#include <wiredtiger.h> -#include <wiredtiger_ext.h> - /* * We need to include the configuration file to detect whether this extension - * is being built into the WiredTiger library. + * is being built into the WiredTiger library; application-loaded compression + * functions won't need it. */ -#include "wiredtiger_config.h" +#include <wiredtiger_config.h> + +#include <wiredtiger.h> +#include <wiredtiger_ext.h> + #ifdef _MSC_VER -#define inline __inline +#define inline __inline #endif /* Local compressor structure. */ @@ -234,121 +236,163 @@ zlib_compress_raw(WT_COMPRESSOR *compressor, WT_SESSION *session, { ZLIB_COMPRESSOR *zlib_compressor; ZLIB_OPAQUE opaque; - z_stream *best_zs, last_zs, zs; - uint32_t curr_slot, last_slot; - int ret; + z_stream *best_zs, *last_zs, _last_zs, *zs, _zs; + uint32_t curr_slot, last_slot, zlib_reserved; + bool increase_reserve; + int ret, tret; - curr_slot = last_slot = 0; - (void)split_pct; - (void)dst_len; + (void)split_pct; /* Unused parameters */ (void)final; zlib_compressor = (ZLIB_COMPRESSOR *)compressor; - memset(&zs, 0, sizeof(zs)); - zs.zalloc = zalloc; - zs.zfree = zfree; - opaque.compressor = compressor; - opaque.session = session; - zs.opaque = &opaque; - - if ((ret = deflateInit(&zs, zlib_compressor->zlib_level)) != Z_OK) - return (zlib_error(compressor, session, "deflateInit", ret)); - - zs.next_in = src; - zs.next_out = dst; /* * Experimentally derived, reserve this many bytes for zlib to finish * up a buffer. If this isn't sufficient, we don't fail but we will be * inefficient. */ #define WT_ZLIB_RESERVED 24 - zs.avail_out = (uint32_t)(page_max - (extra + WT_ZLIB_RESERVED)); +#define WT_ZLIB_RESERVED_MAX 48 + zlib_reserved = WT_ZLIB_RESERVED; + + if (0) { +retry: /* If we reached our maximum reserve, quit. */ + if (zlib_reserved == WT_ZLIB_RESERVED_MAX) + return (0); + zlib_reserved = WT_ZLIB_RESERVED_MAX; + } + + best_zs = last_zs = NULL; + last_slot = 0; + increase_reserve = false; + ret = 0; - /* Save the stream state in case the chosen data doesn't fit. */ - if ((ret = deflateCopy(&last_zs, &zs)) != Z_OK) - return (zlib_error(compressor, session, "deflateCopy", ret)); + zs = &_zs; + memset(zs, 0, sizeof(*zs)); + zs->zalloc = zalloc; + zs->zfree = zfree; + opaque.compressor = compressor; + opaque.session = session; + zs->opaque = &opaque; + + if ((ret = deflateInit(zs, zlib_compressor->zlib_level)) != Z_OK) + return (zlib_error(compressor, session, "deflateInit", ret)); + + zs->next_in = src; + zs->next_out = dst; + + /* + * Set the target size. The target size is complicated: we don't want + * to exceed the smaller of the maximum page size or the destination + * buffer length, and in both cases we have to take into account the + * space required by zlib to finish up the buffer and the extra bytes + * required by our caller. + */ + zs->avail_out = (uint32_t)(page_max < dst_len ? page_max : dst_len); + zs->avail_out -= (uint32_t)(zlib_reserved + extra); /* * Strategy: take the available output size and compress that much * input. Continue until there is no input small enough or the * compression fails to fit. */ - for (best_zs = NULL;;) { + for (;;) { /* Find the next slot we will try to compress up to. */ - if ((curr_slot = zlib_find_slot( - zs.total_in + zs.avail_out, offsets, slots)) > last_slot) { - zs.avail_in = offsets[curr_slot] - offsets[last_slot]; - while (zs.avail_in > 0 && zs.avail_out > 0) - if ((ret = deflate(&zs, Z_SYNC_FLUSH)) != Z_OK) - return (zlib_error(compressor, - session, "deflate", ret)); + curr_slot = zlib_find_slot( + zs->total_in + zs->avail_out, offsets, slots); + if (curr_slot > last_slot) { + zs->avail_in = offsets[curr_slot] - offsets[last_slot]; + while (zs->avail_in > 0 && zs->avail_out > 0) + if ((ret = deflate(zs, Z_SYNC_FLUSH)) != Z_OK) { + ret = zlib_error(compressor, + session, "deflate", ret); + goto err; + } } /* * We didn't do a deflate, or it didn't work: use the last saved - * position. + * position (if any). */ - if (curr_slot <= last_slot || zs.avail_in > 0) { - if ((ret = deflateEnd(&zs)) != Z_OK && - ret != Z_DATA_ERROR) - return (zlib_error( - compressor, session, "deflateEnd", ret)); - - best_zs = &last_zs; + if (curr_slot <= last_slot || zs->avail_in > 0) { + best_zs = last_zs; break; } - /* The last deflation succeeded, discard the saved one. */ - if ((ret = deflateEnd(&last_zs)) != Z_OK && ret != Z_DATA_ERROR) - return (zlib_error( - compressor, session, "deflateEnd", ret)); - /* * If there's more compression to do, save a snapshot and keep * going, otherwise, use the current compression. */ last_slot = curr_slot; - if (zs.avail_out > 0) { - if ((ret = deflateCopy(&last_zs, &zs)) != Z_OK) - return (zlib_error( - compressor, session, "deflateCopy", ret)); + if (zs->avail_out > 0) { + /* Discard any previously saved snapshot. */ + if (last_zs != NULL) { + ret = deflateEnd(last_zs); + last_zs = NULL; + if (ret != Z_OK && ret != Z_DATA_ERROR) { + ret = zlib_error(compressor, + session, "deflateEnd", ret); + goto err; + } + } + last_zs = &_last_zs; + if ((ret = deflateCopy(last_zs, zs)) != Z_OK) { + last_zs = NULL; + ret = zlib_error( + compressor, session, "deflateCopy", ret); + goto err; + } continue; } - best_zs = &zs; + best_zs = zs; break; } - best_zs->avail_out += WT_ZLIB_RESERVED; - ret = deflate(best_zs, Z_FINISH); + if (last_slot > 0 && best_zs != NULL) { + /* Add the reserved bytes and try to finish the compression. */ + best_zs->avail_out += zlib_reserved; + ret = deflate(best_zs, Z_FINISH); - /* - * If the end marker didn't fit, report that we got no work done, - * WiredTiger will compress the (possibly large) page image using - * ordinary compression instead. - */ - if (ret == Z_OK || ret == Z_BUF_ERROR) - last_slot = 0; - else if (ret != Z_STREAM_END) - return ( - zlib_error(compressor, session, "deflate end block", ret)); + /* + * If the end marker didn't fit with the default value, try + * again with a maximum value; if that doesn't work, report we + * got no work done, WiredTiger will compress the (possibly + * large) page image using ordinary compression instead. + */ + if (ret == Z_OK || ret == Z_BUF_ERROR) { + last_slot = 0; + increase_reserve = true; + } else if (ret != Z_STREAM_END) { + ret = zlib_error( + compressor, session, "deflate end block", ret); + goto err; + } + ret = 0; + } - if ((ret = deflateEnd(best_zs)) != Z_OK && ret != Z_DATA_ERROR) - return (zlib_error(compressor, session, "deflateEnd", ret)); +err: if (zs != NULL && + (tret = deflateEnd(zs)) != Z_OK && tret != Z_DATA_ERROR) + ret = zlib_error(compressor, session, "deflateEnd", tret); + if (last_zs != NULL && + (tret = deflateEnd(last_zs)) != Z_OK && tret != Z_DATA_ERROR) + ret = zlib_error(compressor, session, "deflateEnd", tret); - if (last_slot > 0) { + if (ret == 0 && last_slot > 0) { *result_slotsp = last_slot; *result_lenp = (size_t)best_zs->total_out; } else { - /* We didn't manage to compress anything: don't retry. */ + /* We didn't manage to compress anything. */ *result_slotsp = 0; *result_lenp = 1; + + if (increase_reserve) + goto retry; } #if 0 /* Decompress the result and confirm it matches the original source. */ - if (last_slot > 0) { + if (ret == 0 && last_slot > 0) { void *decomp; size_t result_len; @@ -363,19 +407,20 @@ zlib_compress_raw(WT_COMPRESSOR *compressor, WT_SESSION *session, "deflate compare with original source", Z_DATA_ERROR); zfree(&opaque, decomp); - if (ret != 0) - return (ret); } #endif #if 0 - fprintf(stderr, - "zlib_compress_raw (%s): page_max %" PRIuMAX ", slots %" PRIu32 - ", take %" PRIu32 ": %" PRIu32 " -> %" PRIuMAX "\n", - final ? "final" : "not final", (uintmax_t)page_max, - slots, last_slot, offsets[last_slot], (uintmax_t)*result_lenp); + if (ret == 0 && last_slot > 0) + fprintf(stderr, + "zlib_compress_raw (%s): page_max %" PRIuMAX ", slots %" + PRIu32 ", take %" PRIu32 ": %" PRIu32 " -> %" PRIuMAX "\n", + final ? "final" : "not final", (uintmax_t)page_max, + slots, last_slot, offsets[last_slot], + (uintmax_t)*result_lenp); #endif - return (0); + + return (ret); } /* @@ -396,7 +441,8 @@ zlib_terminate(WT_COMPRESSOR *compressor, WT_SESSION *session) * Add a zlib compressor. */ static int -zlib_add_compressor(WT_CONNECTION *connection, int raw, const char *name) +zlib_add_compressor( + WT_CONNECTION *connection, bool raw, const char *name, int zlib_level) { ZLIB_COMPRESSOR *zlib_compressor; @@ -415,17 +461,80 @@ zlib_add_compressor(WT_CONNECTION *connection, int raw, const char *name) zlib_compressor->compressor.terminate = zlib_terminate; zlib_compressor->wt_api = connection->get_extension_api(connection); - - /* - * Between 0-10: level: see zlib manual. - */ - zlib_compressor->zlib_level = Z_DEFAULT_COMPRESSION; + zlib_compressor->zlib_level = zlib_level; /* Load the compressor. */ return (connection->add_compressor( connection, name, (WT_COMPRESSOR *)zlib_compressor, NULL)); } +/* + * zlib_init_config -- + * Handle zlib configuration. + */ +static int +zlib_init_config( + WT_CONNECTION *connection, WT_CONFIG_ARG *config, int *zlib_levelp) +{ + WT_CONFIG_ITEM k, v; + WT_CONFIG_PARSER *config_parser; + WT_EXTENSION_API *wtext; + int ret, zlib_level; + + /* If configured as a built-in, there's no configuration argument. */ + if (config == NULL) + return (0); + + /* + * Zlib compression engine allows applications to specify a compression + * level; review the configuration. + */ + wtext = connection->get_extension_api(connection); + if ((ret = wtext->config_get(wtext, NULL, config, "config", &v)) != 0) { + (void)wtext->err_printf(wtext, NULL, + "WT_EXTENSION_API.config_get: zlib configure: %s", + wtext->strerror(wtext, NULL, ret)); + return (ret); + } + if ((ret = wtext->config_parser_open( + wtext, NULL, v.str, v.len, &config_parser)) != 0) { + (void)wtext->err_printf(wtext, NULL, + "WT_EXTENSION_API.config_parser_open: zlib configure: %s", + wtext->strerror(wtext, NULL, ret)); + return (ret); + } + while ((ret = config_parser->next(config_parser, &k, &v)) == 0) + if (strlen("compression_level") == k.len && + strncmp("compression_level", k.str, k.len) == 0) { + /* + * Between 0-9: level: see zlib manual. + */ + zlib_level = (int)v.val; + if (zlib_level < 0 || zlib_level > 9) { + (void)wtext->err_printf(wtext, NULL, + "WT_CONFIG_PARSER.next: zlib configure: " + "unsupported compression level %d", + zlib_level); + return (EINVAL); + } + *zlib_levelp = zlib_level; + continue; + } + if (ret != WT_NOTFOUND) { + (void)wtext->err_printf(wtext, NULL, + "WT_CONFIG_PARSER.next: zlib configure: %s", + wtext->strerror(wtext, NULL, ret)); + return (ret); + } + if ((ret = config_parser->close(config_parser)) != 0) { + (void)wtext->err_printf(wtext, NULL, + "WT_CONFIG_PARSER.close: zlib configure: %s", + wtext->strerror(wtext, NULL, ret)); + return (ret); + } + return (0); +} + int zlib_extension_init(WT_CONNECTION *, WT_CONFIG_ARG *); /* @@ -437,13 +546,17 @@ int zlib_extension_init(WT_CONNECTION *, WT_CONFIG_ARG *); int zlib_extension_init(WT_CONNECTION *connection, WT_CONFIG_ARG *config) { - int ret; + int ret, zlib_level; - (void)config; /* Unused parameters */ + zlib_level = Z_DEFAULT_COMPRESSION; /* Default */ + if ((ret = zlib_init_config(connection, config, &zlib_level)) != 0) + return (ret); - if ((ret = zlib_add_compressor(connection, 1, "zlib")) != 0) + if ((ret = zlib_add_compressor( + connection, true, "zlib", zlib_level)) != 0) return (ret); - if ((ret = zlib_add_compressor(connection, 0, "zlib-noraw")) != 0) + if ((ret = zlib_add_compressor( + connection, false, "zlib-noraw", zlib_level)) != 0) return (ret); return (0); } diff --git a/ext/compressors/zstd/Makefile.am b/ext/compressors/zstd/Makefile.am new file mode 100644 index 00000000000..9f0997011e9 --- /dev/null +++ b/ext/compressors/zstd/Makefile.am @@ -0,0 +1,11 @@ +AM_CPPFLAGS = -I$(top_builddir) -I$(top_srcdir)/src/include + +if HAVE_BUILTIN_EXTENSION_ZSTD +noinst_LTLIBRARIES = libwiredtiger_zstd.la +else +lib_LTLIBRARIES = libwiredtiger_zstd.la +libwiredtiger_zstd_la_LDFLAGS = -avoid-version -module +endif + +libwiredtiger_zstd_la_SOURCES = zstd_compress.c +libwiredtiger_zstd_la_LIBADD = -lzstd diff --git a/ext/compressors/zstd/zstd_compress.c b/ext/compressors/zstd/zstd_compress.c new file mode 100644 index 00000000000..3d0447248b6 --- /dev/null +++ b/ext/compressors/zstd/zstd_compress.c @@ -0,0 +1,358 @@ +/*- + * Public Domain 2014-2016 MongoDB, Inc. + * Public Domain 2008-2014 WiredTiger, Inc. + * + * This is free and unencumbered software released into the public domain. + * + * Anyone is free to copy, modify, publish, use, compile, sell, or + * distribute this software, either in source code form or as a compiled + * binary, for any purpose, commercial or non-commercial, and by any + * means. + * + * In jurisdictions that recognize copyright laws, the author or authors + * of this software dedicate any and all copyright interest in the + * software to the public domain. We make this dedication for the benefit + * of the public at large and to the detriment of our heirs and + * successors. We intend this dedication to be an overt act of + * relinquishment in perpetuity of all present and future rights to this + * software under copyright law. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +#include <zstd.h> +#include <errno.h> +#include <stdlib.h> +#include <string.h> + +/* + * We need to include the configuration file to detect whether this extension + * is being built into the WiredTiger library; application-loaded compression + * functions won't need it. + */ +#include <wiredtiger_config.h> + +#include <wiredtiger.h> +#include <wiredtiger_ext.h> + +#ifdef _MSC_VER +#define inline __inline +#endif + +/* Local compressor structure. */ +typedef struct { + WT_COMPRESSOR compressor; /* Must come first */ + + WT_EXTENSION_API *wt_api; /* Extension API */ + + int compression_level; /* compression level */ +} ZSTD_COMPRESSOR; + +/* + * Zstd decompression requires an exact compressed byte count. WiredTiger + * doesn't track that value, store it in the destination buffer. + */ +#define ZSTD_PREFIX sizeof(uint64_t) + +#ifdef WORDS_BIGENDIAN +/* + * zstd_bswap64 -- + * 64-bit unsigned little-endian to/from big-endian value. + */ +static inline uint64_t +zstd_bswap64(uint64_t v) +{ + return ( + ((v << 56) & 0xff00000000000000UL) | + ((v << 40) & 0x00ff000000000000UL) | + ((v << 24) & 0x0000ff0000000000UL) | + ((v << 8) & 0x000000ff00000000UL) | + ((v >> 8) & 0x00000000ff000000UL) | + ((v >> 24) & 0x0000000000ff0000UL) | + ((v >> 40) & 0x000000000000ff00UL) | + ((v >> 56) & 0x00000000000000ffUL) + ); +} +#endif + +/* + * zstd_error -- + * Output an error message, and return a standard error code. + */ +static int +zstd_error(WT_COMPRESSOR *compressor, + WT_SESSION *session, const char *call, size_t error) +{ + WT_EXTENSION_API *wt_api; + + wt_api = ((ZSTD_COMPRESSOR *)compressor)->wt_api; + + (void)wt_api->err_printf(wt_api, session, + "zstd error: %s: %s", call, ZSTD_getErrorName(error)); + return (WT_ERROR); +} + +/* + * zstd_compress -- + * WiredTiger Zstd compression. + */ +static int +zstd_compress(WT_COMPRESSOR *compressor, WT_SESSION *session, + uint8_t *src, size_t src_len, + uint8_t *dst, size_t dst_len, + size_t *result_lenp, int *compression_failed) +{ + ZSTD_COMPRESSOR *zcompressor; + size_t zstd_ret; + uint64_t zstd_len; + + zcompressor = (ZSTD_COMPRESSOR *)compressor; + + /* Compress, starting past the prefix bytes. */ + zstd_ret = ZSTD_compress( + dst + ZSTD_PREFIX, dst_len - ZSTD_PREFIX, + src, src_len, zcompressor->compression_level); + + /* + * If compression succeeded and the compressed length is smaller than + * the original size, return success. + */ + if (!ZSTD_isError(zstd_ret) && zstd_ret + ZSTD_PREFIX < src_len) { + *result_lenp = zstd_ret + ZSTD_PREFIX; + *compression_failed = 0; + + /* + * On decompression, Zstd requires an exact compressed byte + * count (the current value of zstd_ret). WiredTiger does not + * preserve that value, so save zstd_ret at the beginning of + * the destination buffer. + * + * Store the value in little-endian format. + */ + zstd_len = zstd_ret; +#ifdef WORDS_BIGENDIAN + zstd_len = zstd_bswap64(zstd_len); +#endif + *(uint64_t *)dst = zstd_len; + return (0); + } + + *compression_failed = 1; + return (ZSTD_isError(zstd_ret) ? + zstd_error(compressor, session, "ZSTD_compress", zstd_ret) : 0); +} + +/* + * zstd_decompress -- + * WiredTiger Zstd decompression. + */ +static int +zstd_decompress(WT_COMPRESSOR *compressor, WT_SESSION *session, + uint8_t *src, size_t src_len, + uint8_t *dst, size_t dst_len, + size_t *result_lenp) +{ + WT_EXTENSION_API *wt_api; + size_t zstd_ret; + uint64_t zstd_len; + + wt_api = ((ZSTD_COMPRESSOR *)compressor)->wt_api; + + /* + * Retrieve the saved length, handling little- to big-endian conversion + * as necessary. + */ + zstd_len = *(uint64_t *)src; +#ifdef WORDS_BIGENDIAN + zstd_len = zstd_bswap64(zstd_len); +#endif + if (zstd_len + ZSTD_PREFIX > src_len) { + (void)wt_api->err_printf(wt_api, + session, + "WT_COMPRESSOR.decompress: stored size exceeds source " + "size"); + return (WT_ERROR); + } + + zstd_ret = + ZSTD_decompress(dst, dst_len, src + ZSTD_PREFIX, (size_t)zstd_len); + + if (!ZSTD_isError(zstd_ret)) { + *result_lenp = zstd_ret; + return (0); + } + return (zstd_error(compressor, session, "ZSTD_decompress", zstd_ret)); +} + +/* + * zstd_pre_size -- + * WiredTiger Zstd destination buffer sizing for compression. + */ +static int +zstd_pre_size(WT_COMPRESSOR *compressor, WT_SESSION *session, + uint8_t *src, size_t src_len, size_t *result_lenp) +{ + (void)compressor; /* Unused parameters */ + (void)session; + (void)src; + + /* + * Zstd compression runs faster if the destination buffer is sized at + * the upper-bound of the buffer size needed by the compression. Use + * the library calculation of that overhead (plus our overhead). + */ + *result_lenp = ZSTD_compressBound(src_len) + ZSTD_PREFIX; + return (0); +} + +/* + * zstd_terminate -- + * WiredTiger Zstd compression termination. + */ +static int +zstd_terminate(WT_COMPRESSOR *compressor, WT_SESSION *session) +{ + (void)session; /* Unused parameters */ + + free(compressor); + return (0); +} + +/* + * zstd_init_config -- + * Handle zstd configuration. + */ +static int +zstd_init_config( + WT_CONNECTION *connection, WT_CONFIG_ARG *config, int *compression_levelp) +{ + WT_CONFIG_ITEM k, v; + WT_CONFIG_PARSER *config_parser; + WT_EXTENSION_API *wtext; + int ret; + + /* If configured as a built-in, there's no configuration argument. */ + if (config == NULL) + return (0); + + /* + * Zstd compression engine allows applications to specify a compression + * level; review the configuration. + */ + wtext = connection->get_extension_api(connection); + if ((ret = wtext->config_get(wtext, NULL, config, "config", &v)) != 0) { + (void)wtext->err_printf(wtext, NULL, + "WT_EXTENSION_API.config_get: zstd configure: %s", + wtext->strerror(wtext, NULL, ret)); + return (ret); + } + if ((ret = wtext->config_parser_open( + wtext, NULL, v.str, v.len, &config_parser)) != 0) { + (void)wtext->err_printf(wtext, NULL, + "WT_EXTENSION_API.config_parser_open: zstd configure: %s", + wtext->strerror(wtext, NULL, ret)); + return (ret); + } + while ((ret = config_parser->next(config_parser, &k, &v)) == 0) + if (strlen("compression_level") == k.len && + strncmp("compression_level", k.str, k.len) == 0) { + *compression_levelp = (int)v.val; + continue; + } + if (ret != WT_NOTFOUND) { + (void)wtext->err_printf(wtext, NULL, + "WT_CONFIG_PARSER.next: zstd configure: %s", + wtext->strerror(wtext, NULL, ret)); + return (ret); + } + if ((ret = config_parser->close(config_parser)) != 0) { + (void)wtext->err_printf(wtext, NULL, + "WT_CONFIG_PARSER.close: zstd configure: %s", + wtext->strerror(wtext, NULL, ret)); + return (ret); + } + return (0); +} + +int zstd_extension_init(WT_CONNECTION *, WT_CONFIG_ARG *); + +/* + * zstd_extension_init -- + * WiredTiger Zstd compression extension - called directly when Zstd + * support is built in, or via wiredtiger_extension_init when Zstd support + * is included via extension loading. + */ +int +zstd_extension_init(WT_CONNECTION *connection, WT_CONFIG_ARG *config) +{ + ZSTD_COMPRESSOR *zstd_compressor; + int compression_level, ret; + + /* + * Zstd's sweet-spot is better compression than zlib at significantly + * faster compression/decompression speeds. LZ4 and snappy are faster + * than zstd, but have worse compression ratios. Applications wanting + * faster compression/decompression with worse compression will select + * LZ4 or snappy, so we configure zstd for better compression. + * + * From the zstd github site, default measurements of the compression + * engines we support, listing compression ratios with compression and + * decompression speeds: + * + * Name Ratio C.speed D.speed + * MB/s MB/s + * zstd 2.877 330 940 + * zlib 2.730 95 360 + * LZ4 2.101 620 3100 + * snappy 2.091 480 1600 + * + * Set the zstd compression level to 3: according to the zstd web site, + * that reduces zstd's compression speed to around 200 MB/s, increasing + * the compression ratio to 3.100 (close to zlib's best compression + * ratio). In other words, position zstd as a zlib replacement, having + * similar compression at much higher compression/decompression speeds. + */ + compression_level = 3; + if ((ret = + zstd_init_config(connection, config, &compression_level)) != 0) + return (ret); + + if ((zstd_compressor = calloc(1, sizeof(ZSTD_COMPRESSOR))) == NULL) + return (errno); + + zstd_compressor->compressor.compress = zstd_compress; + zstd_compressor->compressor.compress_raw = NULL; + zstd_compressor->compressor.decompress = zstd_decompress; + zstd_compressor->compressor.pre_size = zstd_pre_size; + zstd_compressor->compressor.terminate = zstd_terminate; + + zstd_compressor->wt_api = connection->get_extension_api(connection); + + zstd_compressor->compression_level = compression_level; + + /* Load the compressor */ + return (connection->add_compressor( + connection, "zstd", (WT_COMPRESSOR *)zstd_compressor, NULL)); +} + +/* + * We have to remove this symbol when building as a builtin extension otherwise + * it will conflict with other builtin libraries. + */ +#ifndef HAVE_BUILTIN_EXTENSION_ZSTD +/* + * wiredtiger_extension_init -- + * WiredTiger Zstd compression extension. + */ +int +wiredtiger_extension_init(WT_CONNECTION *connection, WT_CONFIG_ARG *config) +{ + return (zstd_extension_init(connection, config)); +} +#endif diff --git a/src/conn/conn_api.c b/src/conn/conn_api.c index 8a87aa9c7da..04c29e957a3 100644 --- a/src/conn/conn_api.c +++ b/src/conn/conn_api.c @@ -789,14 +789,17 @@ __conn_get_extension_api(WT_CONNECTION *wt_conn) return (&conn->extension_api); } +#ifdef HAVE_BUILTIN_EXTENSION_LZ4 + extern int lz4_extension_init(WT_CONNECTION *, WT_CONFIG_ARG *); +#endif #ifdef HAVE_BUILTIN_EXTENSION_SNAPPY extern int snappy_extension_init(WT_CONNECTION *, WT_CONFIG_ARG *); #endif #ifdef HAVE_BUILTIN_EXTENSION_ZLIB extern int zlib_extension_init(WT_CONNECTION *, WT_CONFIG_ARG *); #endif -#ifdef HAVE_BUILTIN_EXTENSION_LZ4 - extern int lz4_extension_init(WT_CONNECTION *, WT_CONFIG_ARG *); +#ifdef HAVE_BUILTIN_EXTENSION_ZSTD + extern int zstd_extension_init(WT_CONNECTION *, WT_CONFIG_ARG *); #endif /* @@ -808,14 +811,17 @@ __conn_load_default_extensions(WT_CONNECTION_IMPL *conn) { WT_UNUSED(conn); +#ifdef HAVE_BUILTIN_EXTENSION_LZ4 + WT_RET(lz4_extension_init(&conn->iface, NULL)); +#endif #ifdef HAVE_BUILTIN_EXTENSION_SNAPPY WT_RET(snappy_extension_init(&conn->iface, NULL)); #endif #ifdef HAVE_BUILTIN_EXTENSION_ZLIB WT_RET(zlib_extension_init(&conn->iface, NULL)); #endif -#ifdef HAVE_BUILTIN_EXTENSION_LZ4 - WT_RET(lz4_extension_init(&conn->iface, NULL)); +#ifdef HAVE_BUILTIN_EXTENSION_ZSTD + WT_RET(zstd_extension_init(&conn->iface, NULL)); #endif return (0); } diff --git a/src/docs/build-posix.dox b/src/docs/build-posix.dox index 4889bf931c9..3e7f8f37acd 100644 --- a/src/docs/build-posix.dox +++ b/src/docs/build-posix.dox @@ -150,10 +150,14 @@ Configure WiredTiger to support the \c verbose configuration string to Configure WiredTiger for <a href="http://www.zlib.net/">zlib</a> compression; see @ref compression for more information. +@par \c --enable-zstd +Configure WiredTiger for <a href="https://github.com/facebook/zstd">Zstd</a> +compression; see @ref compression for more information. + @par <code>--with-builtins</code> Configure WiredTiger to include support for extensions in the main library. This avoids requiring additional libraries for supported extensions. Currently -supported options are \c lz4, \c snappy and \c zlib. +supported options are \c lz4, \c snappy, \c zlib and \c zstd. @par <code>--with-python-prefix</code> Configure WiredTiger to install Python libraries to a non-standard Python diff --git a/src/docs/compression.dox b/src/docs/compression.dox index 0be96835760..74bed5c6f68 100644 --- a/src/docs/compression.dox +++ b/src/docs/compression.dox @@ -1,7 +1,7 @@ /*! @m_page{{c,java},compression,Compressors} This section explains how to configure WiredTiger's builtin support for -the lz4, snappy and zlib compression engines. +the lz4, snappy, zlib and zstd compression engines. @section compression_lz4 Using LZ4 compression @@ -85,11 +85,53 @@ an extension. For example, with the WiredTiger library installed in @snippet ex_all.c Configure zlib extension +The default compression level for the zlib compression is +\c Z_DEFAULT_COMPRESSION (see the zlib documentation for further +information); compression can be configured to other levels using the +additional configuration argument \c compression_level. + +@snippet ex_all.c Configure zlib extension with compression level + Finally, when creating the WiredTiger object, set \c block_compressor to \c zlib: @snippet ex_all.c Create a zlib compressed table +@section compression_zstd Using Zstd compression + +To use the builtin support for Facebook's +<a href="https://github.com/facebook/zstd">Zstd</a> +compression, first check that Zstd is installed in include and library +directories searched by the compiler. Once Zstd is installed, you can +enable Zstd using the \c --enable-zstd option to configure. + +If Zstd is installed in a location not normally searched by the +compiler toolchain, you'll need to modify the \c CPPFLAGS and \c LDFLAGS +to indicate these locations. For example, with the Zstd includes and +libraries installed in \c /usr/local/include and \c /usr/local/lib, you +would run configure with the following additional arguments: + +@code +--enable-zstd CPPFLAGS="-I/usr/local/include" LDFLAGS="-L/usr/local/include" +@endcode + +When opening the WiredTiger database, load the Zstd shared library as +an extension. For example, with the WiredTiger library installed in +\c /usr/local/lib, you would use the following extension: + +@snippet ex_all.c Configure zstd extension + +The default compression level for the zstd compression is 3; compression +can be configured to other levels using the additional configuration +argument \c compression_level. + +@snippet ex_all.c Configure zstd extension with compression level + +Finally, when creating the WiredTiger object, set \c block_compressor +to \c zstd: + +@snippet ex_all.c Create a zstd compressed table + @section compression_upgrading Upgrading compression engines WiredTiger does not store information with file blocks to identify the diff --git a/src/docs/spell.ok b/src/docs/spell.ok index a2ef7658ec6..4b1337f84b8 100644 --- a/src/docs/spell.ok +++ b/src/docs/spell.ok @@ -95,6 +95,7 @@ WiredTigerStat WiredTigerTestCase Yann Za +Zstd aR abstime ack'ed @@ -507,3 +508,4 @@ xa yieldcpu zlib zseries +zstd diff --git a/src/docs/wtperf.dox b/src/docs/wtperf.dox index 085e1addf61..83aadf8a776 100644 --- a/src/docs/wtperf.dox +++ b/src/docs/wtperf.dox @@ -160,7 +160,7 @@ properly close connection at end of test. Setting to false does not sync data t @par compact (boolean, default=false) post-populate compact for LSM merging activity @par compression (string, default="none") -compression extension. Allowed configuration values are: 'none', 'lz4', 'snappy', 'zlib' +compression extension. Allowed configuration values are: 'none', 'lz4', 'snappy', 'zlib', 'zstd' @par create (boolean, default=true) do population phase; false to use existing database @par database_count (unsigned int, default=1) diff --git a/src/include/wiredtiger.in b/src/include/wiredtiger.in index 7bbdf85d954..b6185b4ead6 100644 --- a/src/include/wiredtiger.in +++ b/src/include/wiredtiger.in @@ -1005,9 +1005,9 @@ struct __wt_session { * @config{block_compressor, configure a compressor for file blocks. * Permitted values are \c "none" or custom compression engine name * created with WT_CONNECTION::add_compressor. If WiredTiger has - * builtin support for \c "snappy"\, \c "lz4" or \c "zlib" compression\, - * these names are also available. See @ref compression for more - * information., a string; default \c none.} + * builtin support for \c "lz4"\, \c "snappy"\, \c "zlib" or \c "zstd" + * compression\, these names are also available. See @ref compression + * for more information., a string; default \c none.} * @config{cache_resident, do not ever evict the object's pages from * cache. Not compatible with LSM tables; see @ref * tuning_cache_resident for more information., a boolean flag; default @@ -2338,11 +2338,11 @@ struct __wt_connection { * @config{ compressor, configure a compressor for log * records. Permitted values are \c "none" or custom compression engine name * created with WT_CONNECTION::add_compressor. If WiredTiger has builtin - * support for \c "snappy"\, \c "lz4" or \c "zlib" compression\, these names are - * also available. See @ref compression for more information., a string; - * default \c none.} - * @config{ enabled, enable logging - * subsystem., a boolean flag; default \c false.} + * support for \c "lz4"\, \c "snappy"\, \c "zlib" or \c "zstd" compression\, + * these names are also available. See @ref compression for more information., + * a string; default \c none.} + * @config{ enabled, enable + * logging subsystem., a boolean flag; default \c false.} * @config{ file_max, the maximum size of log files., an * integer between 100KB and 2GB; default \c 100MB.} * @config{ path, the name of a directory into which log diff --git a/test/format/config.c b/test/format/config.c index 542adf33da2..95b05c3c833 100644 --- a/test/format/config.c +++ b/test/format/config.c @@ -270,28 +270,33 @@ config_compression(const char *conf_name) */ switch (mmrand(NULL, 1, 20)) { #ifdef HAVE_BUILTIN_EXTENSION_LZ4 - case 1: case 2: case 3: case 4: /* 20% lz4 */ + case 1: case 2: /* 10% lz4 */ cstr = "lz4"; break; - case 5: /* 5% lz4-no-raw */ + case 3: /* 5% lz4-no-raw */ cstr = "lz4-noraw"; break; #endif #ifdef HAVE_BUILTIN_EXTENSION_SNAPPY - case 6: case 7: case 8: case 9: /* 30% snappy */ - case 10: case 11: + case 4: case 5: case 6: case 7: /* 30% snappy */ + case 8: case 9: cstr = "snappy"; break; #endif #ifdef HAVE_BUILTIN_EXTENSION_ZLIB - case 12: case 13: case 14: case 15: /* 20% zlib */ + case 10: case 11: case 12: case 13: /* 20% zlib */ cstr = "zlib"; break; - case 16: /* 5% zlib-no-raw */ + case 14: /* 5% zlib-no-raw */ cstr = "zlib-noraw"; break; #endif - case 17: case 18: case 19: case 20: /* 20% no compression */ +#ifdef HAVE_BUILTIN_EXTENSION_ZSTD + case 15: case 16 case 17: /* 15% zstd */ + cstr = "zstd"; + break; +#endif + case 18: case 19: case 20: /* 15% no compression */ default: break; } @@ -748,6 +753,8 @@ config_map_compression(const char *s, u_int *vp) *vp = COMPRESS_ZLIB; else if (strcmp(s, "zlib-noraw") == 0) *vp = COMPRESS_ZLIB_NO_RAW; + else if (strcmp(s, "zstd") == 0) + *vp = COMPRESS_ZSTD; else testutil_die(EINVAL, "illegal compression configuration: %s", s); diff --git a/test/format/config.h b/test/format/config.h index 725bc7c5d97..9bfba3cd0df 100644 --- a/test/format/config.h +++ b/test/format/config.h @@ -58,7 +58,7 @@ typedef struct { } CONFIG; #define COMPRESSION_LIST \ - "(none | lz4 | lz4-noraw | snappy | zlib | zlib-noraw)" + "(none | lz4 | lz4-noraw | snappy | zlib | zlib-noraw | zstd)" static CONFIG c[] = { { "abort", diff --git a/test/format/format.h b/test/format/format.h index 363dcf9eea8..820bc020c9b 100644 --- a/test/format/format.h +++ b/test/format/format.h @@ -48,6 +48,8 @@ EXTPATH "compressors/snappy/.libs/libwiredtiger_snappy.so" #define ZLIB_PATH \ EXTPATH "compressors/zlib/.libs/libwiredtiger_zlib.so" +#define ZSTD_PATH \ + EXTPATH "compressors/zstd/.libs/libwiredtiger_zstd.so" #define REVERSE_PATH \ EXTPATH "collators/reverse/.libs/libwiredtiger_reverse_collator.so" @@ -219,6 +221,7 @@ typedef struct { #define COMPRESS_SNAPPY 5 #define COMPRESS_ZLIB 6 #define COMPRESS_ZLIB_NO_RAW 7 +#define COMPRESS_ZSTD 8 u_int c_compression_flag; /* Compression flag value */ u_int c_logging_compression_flag; /* Log compression flag value */ diff --git a/test/format/wts.c b/test/format/wts.c index 1600786855a..23fdbce156c 100644 --- a/test/format/wts.c +++ b/test/format/wts.c @@ -50,6 +50,8 @@ compressor(uint32_t compress_flag) return ("zlib"); case COMPRESS_ZLIB_NO_RAW: return ("zlib-noraw"); + case COMPRESS_ZSTD: + return ("zstd"); default: break; } @@ -210,13 +212,14 @@ wts_open(const char *home, bool set_api, WT_CONNECTION **connp) /* Extensions. */ p += snprintf(p, REMAIN(p, end), ",extensions=[" - "\"%s\", \"%s\", \"%s\", \"%s\", \"%s\", \"%s\", \"%s\"],", + "\"%s\", \"%s\", \"%s\", \"%s\", \"%s\", \"%s\", \"%s\", \"%s\"],", g.c_reverse ? REVERSE_PATH : "", access(LZ4_PATH, R_OK) == 0 ? LZ4_PATH : "", access(LZO_PATH, R_OK) == 0 ? LZO_PATH : "", access(ROTN_PATH, R_OK) == 0 ? ROTN_PATH : "", access(SNAPPY_PATH, R_OK) == 0 ? SNAPPY_PATH : "", access(ZLIB_PATH, R_OK) == 0 ? ZLIB_PATH : "", + access(ZSTD_PATH, R_OK) == 0 ? ZSTD_PATH : "", DATASOURCE("kvsbdb") ? KVS_BDB_PATH : ""); /* diff --git a/test/suite/test_encrypt01.py b/test/suite/test_encrypt01.py index d314cbeadfd..746c9d13e96 100644 --- a/test/suite/test_encrypt01.py +++ b/test/suite/test_encrypt01.py @@ -57,6 +57,7 @@ class test_encrypt01(wttest.WiredTigerTestCase): ('lz4', dict(log_compress='lz4', block_compress='lz4')), ('snappy', dict(log_compress='snappy', block_compress='snappy')), ('zlib', dict(log_compress='zlib', block_compress='zlib')), + ('zstd', dict(log_compress='zstd', block_compress='zstd')), ('none-snappy', dict(log_compress=None, block_compress='snappy')), ('snappy-lz4', dict(log_compress='snappy', block_compress='lz4')), ] |