@cryptotaxi247 / netdata-1 / commits / f1c26d0e2

DBENGINE: support ZSTD compression (#17244)

* extract dbengine compression to separate files * added ZSTD support in dbengine * automatically select best compression * handle decompression errors * eliminate fatals from compression algorithms; fallback to uncompressed pages if compression fails or generates bigger data * have the unit test generate many data files

Costa Tsaousis committed Mar 25, 2024 at 12:30 UTC f1c26d0e2bf2f09480b48a5e9f9dc7abea88a5da
7 files changed +223 -41
CMakeLists.txt
+2
@@ -1054,6 +1054,8 @@ if(ENABLE_DBENGINE)
1054 src/database/engine/pdc.h
1055 src/database/engine/dbengine-unittest.c
1056 src/database/engine/dbengine-stresstest.c
1057 + src/database/engine/dbengine-compression.c
1058 + src/database/engine/dbengine-compression.h
1059 )
1060 endif()
1061
src/database/engine/dbengine-compression.c new
+163
@@ -0,0 +1,163 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +#include "rrdengine.h"
4 +#include "dbengine-compression.h"
5 +
6 +#ifdef ENABLE_LZ4
7 +#include <lz4.h>
8 +#endif
9 +
10 +#ifdef ENABLE_ZSTD
11 +#include <zstd.h>
12 +#define DBENGINE_ZSTD_DEFAULT_COMPRESSION_LEVEL 3
13 +#endif
14 +
15 +uint8_t dbengine_default_compression(void) {
16 +
17 +#ifdef ENABLE_ZSTD
18 + return RRDENG_COMPRESSION_ZSTD;
19 +#endif
20 +
21 +#ifdef ENABLE_LZ4
22 + return RRDENG_COMPRESSION_LZ4;
23 +#endif
24 +
25 + return RRDENG_COMPRESSION_NONE;
26 +}
27 +
28 +bool dbengine_valid_compression_algorithm(uint8_t algorithm) {
29 + switch(algorithm) {
30 + case RRDENG_COMPRESSION_NONE:
31 +
32 +#ifdef ENABLE_LZ4
33 + case RRDENG_COMPRESSION_LZ4:
34 +#endif
35 +
36 +#ifdef ENABLE_ZSTD
37 + case RRDENG_COMPRESSION_ZSTD:
38 +#endif
39 +
40 + return true;
41 +
42 + default:
43 + return false;
44 + }
45 +}
46 +
47 +size_t dbengine_max_compressed_size(size_t uncompressed_size, uint8_t algorithm) {
48 + switch(algorithm) {
49 +#ifdef ENABLE_LZ4
50 + case RRDENG_COMPRESSION_LZ4:
51 + fatal_assert(uncompressed_size < LZ4_MAX_INPUT_SIZE);
52 + return LZ4_compressBound((int)uncompressed_size);
53 +#endif
54 +
55 +#ifdef ENABLE_ZSTD
56 + case RRDENG_COMPRESSION_ZSTD:
57 + return ZSTD_compressBound(uncompressed_size);
58 +#endif
59 +
60 + case RRDENG_COMPRESSION_NONE:
61 + return uncompressed_size;
62 +
63 + default:
64 + fatal("DBENGINE: unknown compression algorithm %u", algorithm);
65 + }
66 +}
67 +
68 +size_t dbengine_compress(void *payload, size_t uncompressed_size, uint8_t algorithm) {
69 + // the result should be stored in the payload
70 + // the caller must have called dbengine_max_compressed_size() to make sure the
71 + // payload is big enough to fit the max size needed.
72 +
73 + switch(algorithm) {
74 +#ifdef ENABLE_LZ4
75 + case RRDENG_COMPRESSION_LZ4: {
76 + size_t max_compressed_size = dbengine_max_compressed_size(uncompressed_size, algorithm);
77 + struct extent_buffer *eb = extent_buffer_get(max_compressed_size);
78 + void *compressed_buf = eb->data;
79 +
80 + size_t compressed_size =
81 + LZ4_compress_default(payload, compressed_buf, (int)uncompressed_size, (int)max_compressed_size);
82 +
83 + if(compressed_size > 0 && compressed_size < uncompressed_size)
84 + memcpy(payload, compressed_buf, compressed_size);
85 + else
86 + compressed_size = 0;
87 +
88 + extent_buffer_release(eb);
89 + return compressed_size;
90 + }
91 +#endif
92 +
93 +#ifdef ENABLE_ZSTD
94 + case RRDENG_COMPRESSION_ZSTD: {
95 + size_t max_compressed_size = dbengine_max_compressed_size(uncompressed_size, algorithm);
96 + struct extent_buffer *eb = extent_buffer_get(max_compressed_size);
97 + void *compressed_buf = eb->data;
98 +
99 + size_t compressed_size = ZSTD_compress(compressed_buf, max_compressed_size, payload, uncompressed_size,
100 + DBENGINE_ZSTD_DEFAULT_COMPRESSION_LEVEL);
101 +
102 + if (ZSTD_isError(compressed_size)) {
103 + internal_fatal(true, "DBENGINE: ZSTD compression error %s", ZSTD_getErrorName(compressed_size));
104 + compressed_size = 0;
105 + }
106 +
107 + if(compressed_size > 0 && compressed_size < uncompressed_size)
108 + memcpy(payload, compressed_buf, compressed_size);
109 + else
110 + compressed_size = 0;
111 +
112 + extent_buffer_release(eb);
113 + return compressed_size;
114 + }
115 +#endif
116 +
117 + case RRDENG_COMPRESSION_NONE:
118 + return 0;
119 +
120 + default:
121 + fatal("DBENGINE: unknown compression algorithm %u", algorithm);
122 + }
123 +}
124 +
125 +size_t dbengine_decompress(void *dst, void *src, size_t dst_size, size_t src_size, uint8_t algorithm) {
126 + switch(algorithm) {
127 +
128 +#ifdef ENABLE_LZ4
129 + case RRDENG_COMPRESSION_LZ4: {
130 + int rc = LZ4_decompress_safe(src, dst, (int)src_size, (int)dst_size);
131 + if(rc < 0) {
132 + nd_log(NDLS_DAEMON, NDLP_ERR, "DBENGINE: ZSTD decompression error %d", rc);
133 + rc = 0;
134 + }
135 +
136 + return rc;
137 + }
138 +#endif
139 +
140 +#ifdef ENABLE_ZSTD
141 + case RRDENG_COMPRESSION_ZSTD: {
142 + size_t decompressed_size = ZSTD_decompress(dst, dst_size, src, src_size);
143 +
144 + if (ZSTD_isError(decompressed_size)) {
145 + nd_log(NDLS_DAEMON, NDLP_ERR, "DBENGINE: ZSTD decompression error %s",
146 + ZSTD_getErrorName(decompressed_size));
147 +
148 + decompressed_size = 0;
149 + }
150 +
151 + return decompressed_size;
152 + }
153 +#endif
154 +
155 + case RRDENG_COMPRESSION_NONE:
156 + internal_fatal(true, "DBENGINE: %s() should not be called for uncompressed pages", __FUNCTION__ );
157 + return 0;
158 +
159 + default:
160 + internal_fatal(true, "DBENGINE: unknown compression algorithm %u", algorithm);
161 + return 0;
162 + }
163 +}
src/database/engine/dbengine-compression.h new
+15
@@ -0,0 +1,15 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +#ifndef NETDATA_DBENGINE_COMPRESSION_H
4 +#define NETDATA_DBENGINE_COMPRESSION_H
5 +
6 +uint8_t dbengine_default_compression(void);
7 +
8 +bool dbengine_valid_compression_algorithm(uint8_t algorithm);
9 +
10 +size_t dbengine_max_compressed_size(size_t uncompressed_size, uint8_t algorithm);
11 +size_t dbengine_compress(void *payload, size_t uncompressed_size, uint8_t algorithm);
12 +
13 +size_t dbengine_decompress(void *dst, void *src, size_t dst_size, size_t src_size, uint8_t algorithm);
14 +
15 +#endif //NETDATA_DBENGINE_COMPRESSION_H
src/database/engine/pdc.c
+12 -8
@@ -1,6 +1,7 @@
1 // SPDX-License-Identifier: GPL-3.0-or-later
2 #define NETDATA_RRD_INTERNALS
3 #include "pdc.h"
4 +#include "dbengine-compression.h"
5
6 struct extent_page_details_list {
7 uv_file file;
@@ -940,7 +941,6 @@ static bool epdl_populate_pages_from_extent_data(
941 PDC_PAGE_STATUS tags,
942 bool cached_extent)
943 {
943 - int ret;
944 unsigned i, count;
945 void *uncompressed_buf = NULL;
946 uint32_t payload_length, payload_offset, trailer_offset, uncompressed_payload_length = 0;
@@ -975,7 +975,7 @@ static bool epdl_populate_pages_from_extent_data(
975 if( !can_use_data ||
976 count < 1 ||
977 count > MAX_PAGES_PER_EXTENT ||
978 - (header->compression_algorithm != RRDENG_COMPRESSION_NONE && header->compression_algorithm != RRDENG_COMPRESSION_LZ4) ||
978 + !dbengine_valid_compression_algorithm(header->compression_algorithm) ||
979 (payload_length != trailer_offset - payload_offset) ||
980 (data_length != payload_offset + payload_length + sizeof(*trailer))
981 ) {
@@ -985,8 +985,7 @@ static bool epdl_populate_pages_from_extent_data(
985
986 crc = crc32(0L, Z_NULL, 0);
987 crc = crc32(crc, data, epdl->extent_size - sizeof(*trailer));
988 - ret = crc32cmp(trailer->checksum, crc);
989 - if (unlikely(ret)) {
988 + if (unlikely(crc32cmp(trailer->checksum, crc))) {
989 ctx_io_error(ctx);
990 have_read_error = true;
991 epdl_extent_loading_error_log(ctx, epdl, NULL, "CRC32 checksum FAILED");
@@ -1018,11 +1017,16 @@ static bool epdl_populate_pages_from_extent_data(
1017 eb = extent_buffer_get(uncompressed_payload_length);
1018 uncompressed_buf = eb->data;
1019
1021 - ret = LZ4_decompress_safe(data + payload_offset, uncompressed_buf,
1022 - (int) payload_length, (int) uncompressed_payload_length);
1020 + size_t bytes = dbengine_decompress(uncompressed_buf, data + payload_offset,
1021 + uncompressed_payload_length, payload_length,
1022 + header->compression_algorithm);
1023
1024 - __atomic_add_fetch(&ctx->stats.before_decompress_bytes, payload_length, __ATOMIC_RELAXED);
1025 - __atomic_add_fetch(&ctx->stats.after_decompress_bytes, ret, __ATOMIC_RELAXED);
1024 + if(!bytes)
1025 + have_read_error = true;
1026 + else {
1027 + __atomic_add_fetch(&ctx->stats.before_decompress_bytes, payload_length, __ATOMIC_RELAXED);
1028 + __atomic_add_fetch(&ctx->stats.after_decompress_bytes, bytes, __ATOMIC_RELAXED);
1029 + }
1030 }
1031 }
1032
src/database/engine/rrddiskprotocol.h
+1
@@ -21,6 +21,7 @@
21
22 #define RRDENG_COMPRESSION_NONE (0)
23 #define RRDENG_COMPRESSION_LZ4 (1)
24 +#define RRDENG_COMPRESSION_ZSTD (2)
25
26 #define RRDENG_DF_SB_PADDING_SZ (RRDENG_BLOCK_SIZE - (RRDENG_MAGIC_SZ + RRDENG_VER_SZ + sizeof(uint8_t)))
27
src/database/engine/rrdengine.c
+28 -32
@@ -3,6 +3,7 @@
3
4 #include "rrdengine.h"
5 #include "pdc.h"
6 +#include "dbengine-compression.h"
7
8 rrdeng_stats_t global_io_errors = 0;
9 rrdeng_stats_t global_fs_errors = 0;
@@ -772,13 +773,10 @@ static struct rrdengine_datafile *get_datafile_to_write_extent(struct rrdengine_
773 */
774 static struct extent_io_descriptor *datafile_extent_build(struct rrdengine_instance *ctx, struct page_descr_with_data *base, struct completion *completion) {
775 int ret;
775 - int compressed_size, max_compressed_size = 0;
776 unsigned i, count, size_bytes, pos, real_io_size;
777 - uint32_t uncompressed_payload_length, payload_offset;
777 + uint32_t uncompressed_payload_length, max_compressed_size, payload_offset;
778 struct page_descr_with_data *descr, *eligible_pages[MAX_PAGES_PER_EXTENT];
779 struct extent_io_descriptor *xt_io_descr;
780 - struct extent_buffer *eb = NULL;
781 - void *compressed_buf = NULL;
780 Word_t Index;
781 uint8_t compression_algorithm = ctx->config.global_compress_alg;
782 struct rrdengine_datafile *datafile;
@@ -807,20 +805,8 @@ static struct extent_io_descriptor *datafile_extent_build(struct rrdengine_insta
805 xt_io_descr = extent_io_descriptor_get();
806 xt_io_descr->ctx = ctx;
807 payload_offset = sizeof(*header) + count * sizeof(header->descr[0]);
810 - switch (compression_algorithm) {
811 - case RRDENG_COMPRESSION_NONE:
812 - size_bytes = payload_offset + uncompressed_payload_length + sizeof(*trailer);
813 - break;
814 -
815 - default: /* Compress */
816 - fatal_assert(uncompressed_payload_length < LZ4_MAX_INPUT_SIZE);
817 - max_compressed_size = LZ4_compressBound(uncompressed_payload_length);
818 - eb = extent_buffer_get(max_compressed_size);
819 - compressed_buf = eb->data;
820 - size_bytes = payload_offset + MAX(uncompressed_payload_length, (unsigned)max_compressed_size) + sizeof(*trailer);
821 - break;
822 - }
823 -
808 + max_compressed_size = dbengine_max_compressed_size(uncompressed_payload_length, compression_algorithm);
809 + size_bytes = payload_offset + MAX(uncompressed_payload_length, max_compressed_size) + sizeof(*trailer);
810 ret = posix_memalign((void *)&xt_io_descr->buf, RRDFILE_ALIGNMENT, ALIGN_BYTES_CEILING(size_bytes));
811 if (unlikely(ret)) {
812 fatal("DBENGINE: posix_memalign:%s", strerror(ret));
@@ -832,7 +818,6 @@ static struct extent_io_descriptor *datafile_extent_build(struct rrdengine_insta
818
819 pos = 0;
820 header = xt_io_descr->buf;
835 - header->compression_algorithm = compression_algorithm;
821 header->number_of_pages = count;
822 pos += sizeof(*header);
823
@@ -858,29 +843,40 @@ static struct extent_io_descriptor *datafile_extent_build(struct rrdengine_insta
843
844 pos += sizeof(header->descr[i]);
845 }
846 +
847 + // build the extent payload
848 for (i = 0 ; i < count ; ++i) {
849 descr = xt_io_descr->descr_array[i];
850 pgd_copy_to_extent(descr->pgd, xt_io_descr->buf + pos, descr->page_length);
851 pos += descr->page_length;
852 }
853
867 - if(likely(compression_algorithm == RRDENG_COMPRESSION_LZ4)) {
868 - compressed_size = LZ4_compress_default(
869 - xt_io_descr->buf + payload_offset,
870 - compressed_buf,
871 - (int)uncompressed_payload_length,
872 - max_compressed_size);
854 + // compress the payload
855 + size_t compressed_size =
856 + (int)dbengine_compress(xt_io_descr->buf + payload_offset,
857 + uncompressed_payload_length,
858 + compression_algorithm);
859
874 - __atomic_add_fetch(&ctx->stats.before_compress_bytes, uncompressed_payload_length, __ATOMIC_RELAXED);
875 - __atomic_add_fetch(&ctx->stats.after_compress_bytes, compressed_size, __ATOMIC_RELAXED);
860 + internal_fatal(compressed_size > max_compressed_size, "DBENGINE: compression returned more data than the max allowed");
861 + internal_fatal(compressed_size > uncompressed_payload_length, "DBENGINE: compression returned more data than the uncompressed extent");
862
877 - (void) memcpy(xt_io_descr->buf + payload_offset, compressed_buf, compressed_size);
878 - extent_buffer_release(eb);
879 - size_bytes = payload_offset + compressed_size + sizeof(*trailer);
863 + if(compressed_size) {
864 + header->compression_algorithm = compression_algorithm;
865 header->payload_length = compressed_size;
866 }
882 - else { // RRD_NO_COMPRESSION
883 - header->payload_length = uncompressed_payload_length;
867 + else {
868 + // compression failed, or generated bigger pages
869 + // so it didn't touch our uncompressed buffer
870 + header->compression_algorithm = RRDENG_COMPRESSION_NONE;
871 + header->payload_length = compressed_size = uncompressed_payload_length;
872 + }
873 +
874 + // set the correct size
875 + size_bytes = payload_offset + compressed_size + sizeof(*trailer);
876 +
877 + if(compression_algorithm != RRDENG_COMPRESSION_NONE) {
878 + __atomic_add_fetch(&ctx->stats.before_compress_bytes, uncompressed_payload_length, __ATOMIC_RELAXED);
879 + __atomic_add_fetch(&ctx->stats.after_compress_bytes, compressed_size, __ATOMIC_RELAXED);
880 }
881
882 real_io_size = ALIGN_BYTES_CEILING(size_bytes);
src/database/engine/rrdengineapi.c
+2 -1
@@ -2,6 +2,7 @@
2
3 #include "database/engine/rrddiskprotocol.h"
4 #include "rrdengine.h"
5 +#include "dbengine-compression.h"
6
7 /* Default global database instance */
8 struct rrdengine_instance multidb_ctx_storage_tier0;
@@ -1170,7 +1171,7 @@ int rrdeng_init(struct rrdengine_instance **ctxp, const char *dbfiles_path,
1171
1172 ctx->config.tier = (int)tier;
1173 ctx->config.page_type = tier_page_type[tier];
1173 - ctx->config.global_compress_alg = RRDENG_COMPRESSION_LZ4;
1174 + ctx->config.global_compress_alg = dbengine_default_compression();
1175 if (disk_space_mb < RRDENG_MIN_DISK_SPACE_MB)
1176 disk_space_mb = RRDENG_MIN_DISK_SPACE_MB;
1177 ctx->config.max_disk_space = disk_space_mb * 1048576LLU;