Skip to content

Commit a02a535

Browse files
committed
Compress ZooKeeper replication logs
Motivation: When a file is newly created or has a large diff, the replication log size can grow significantly. With compression, up to about 7–8 MB of data can be stored in a single znode, improving replication performance. Modifcations: - Add `zstd-jni` dependency to the server module. - Compress replication logs using zstd before storing them in ZooKeeper. - Decompress replication logs when compressed prior to replay. Result: Central Dogma server now compresses replication logs with zstd.
1 parent dd34da0 commit a02a535

3 files changed

Lines changed: 27 additions & 4 deletions

File tree

dependencies.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ thrift09 = { strictly = "0.9.3-1" }
8383
# See: https://github.com/apache/curator/blob/master/pom.xml
8484
# (Switch to the right tag to find out the right version.)
8585
zookeeper = "3.9.3"
86+
zstd = "1.5.7-4"
8687

8788
[boms]
8889
armeria = { module = "com.linecorp.armeria:armeria-bom", version.ref = "armeria" }
@@ -465,6 +466,10 @@ exclusions = [
465466
"org.apache.yetus:audience-annotations",
466467
"org.slf4j:slf4j-log4j12"]
467468

469+
[libraries.zstd]
470+
module = "com.github.luben:zstd-jni"
471+
version.ref = "zstd"
472+
468473
[plugins]
469474
download = { id = "de.undercouch.download", version.ref = "download" }
470475
jmh = { id = "me.champeau.jmh", version.ref = "jmh-gradle-plugin" }

server/build.gradle

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ dependencies {
4747
// Snappy
4848
implementation libs.snappy
4949

50+
implementation libs.zstd
51+
5052
// Logging
5153
optionalImplementation libs.logback15
5254
// For Caffiene. See https://github.com/line/centraldogma/pull/499

server/src/main/java/com/linecorp/centraldogma/server/internal/replication/ZooKeeperCommandExecutor.java

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package com.linecorp.centraldogma.server.internal.replication;
1818

19+
import static com.google.common.base.MoreObjects.firstNonNull;
1920
import static com.google.common.collect.ImmutableList.toImmutableList;
2021
import static java.util.Objects.requireNonNull;
2122

@@ -72,6 +73,7 @@
7273

7374
import com.fasterxml.jackson.annotation.JsonCreator;
7475
import com.fasterxml.jackson.annotation.JsonProperty;
76+
import com.github.luben.zstd.Zstd;
7577
import com.google.common.annotations.VisibleForTesting;
7678
import com.google.common.base.Joiner;
7779
import com.google.common.base.MoreObjects;
@@ -1029,18 +1031,21 @@ private static class LogMeta {
10291031
private final int replicaId;
10301032
private final long timestamp;
10311033
private final int size;
1034+
private final boolean compressed;
10321035
private final List<Long> blocks = new ArrayList<>();
10331036

10341037
@JsonCreator
10351038
LogMeta(@JsonProperty(value = "replicaId", required = true) int replicaId,
10361039
@JsonProperty(value = "timestamp", defaultValue = "0") Long timestamp,
1037-
@JsonProperty("size") int size) {
1040+
@JsonProperty("size") int size, @JsonProperty("compressed") Boolean compressed) {
10381041
this.replicaId = replicaId;
10391042
if (timestamp == null) {
10401043
timestamp = 0L;
10411044
}
10421045
this.timestamp = timestamp;
10431046
this.size = size;
1047+
// Defaults to null for backward compatibility.
1048+
this.compressed = firstNonNull(compressed, false);
10441049
}
10451050

10461051
@JsonProperty
@@ -1058,6 +1063,11 @@ int size() {
10581063
return size;
10591064
}
10601065

1066+
@JsonProperty("compressed")
1067+
boolean compressed() {
1068+
return compressed;
1069+
}
1070+
10611071
@JsonProperty
10621072
List<Long> blocks() {
10631073
return Collections.unmodifiableList(blocks);
@@ -1073,17 +1083,20 @@ public String toString() {
10731083
.add("replicaId", replicaId)
10741084
.add("timestamp", timestamp)
10751085
.add("size", size)
1086+
.add("compressed", compressed)
10761087
.add("blocks", blocks)
10771088
.toString();
10781089
}
10791090
}
10801091

10811092
private long storeLog(ReplicationLog<?> log) {
10821093
try {
1083-
final byte[] bytes = Jackson.writeValueAsBytes(log);
1094+
byte[] bytes = Jackson.writeValueAsBytes(log);
10841095
assert bytes.length > 0;
1096+
bytes = Zstd.compress(bytes);
10851097

1086-
final LogMeta logMeta = new LogMeta(log.replicaId(), System.currentTimeMillis(), bytes.length);
1098+
final LogMeta logMeta = new LogMeta(log.replicaId(), System.currentTimeMillis(), bytes.length,
1099+
true);
10871100

10881101
final int count = (bytes.length + MAX_BYTES - 1) / MAX_BYTES;
10891102
for (int i = 0; i < count; ++i) {
@@ -1122,7 +1135,7 @@ Optional<ReplicationLog<?>> loadLog(long revision, boolean skipIfSameReplica) {
11221135
return Optional.empty();
11231136
}
11241137

1125-
final byte[] bytes = new byte[logMeta.size()];
1138+
byte[] bytes = new byte[logMeta.size()];
11261139
int offset = 0;
11271140
for (long blockId : logMeta.blocks()) {
11281141
final String blockPath = absolutePath(LOG_BLOCK_PATH) + '/' + pathFromRevision(blockId);
@@ -1132,6 +1145,9 @@ Optional<ReplicationLog<?>> loadLog(long revision, boolean skipIfSameReplica) {
11321145
}
11331146
assert logMeta.size() == offset;
11341147

1148+
if (logMeta.compressed()) {
1149+
bytes = Zstd.decompress(bytes);
1150+
}
11351151
final ReplicationLog<?> log = Jackson.readValue(bytes, ReplicationLog.class);
11361152
return Optional.of(log);
11371153
} catch (Exception e) {

0 commit comments

Comments
 (0)