This is an automated email from the ASF dual-hosted git repository. github-merge-queue[bot] pushed a commit to branch gh-readonly-queue/dev/pr-12188-271f73324dc4ef26f391925d6251009098717850 in repository https://gitbox.apache.org/repos/asf/seatunnel.git
commit efc81bbb10102f0f4d8c8dec5040be4564221560 Author: Goutam Adwant <[email protected]> AuthorDate: Tue Sep 8 05:28:59 2026 +0000 [Test][Zeta] Bound finished DAG store benchmark verification (#12188) Signed-off-by: goutamadwant <[email protected]> --- docs/en/engines/zeta/benchmark.md | 16 +- docs/zh/engines/zeta/benchmark.md | 14 +- .../benchmark/IMapDagStorageBenchmark.java | 6 +- .../imap/IMapDagStorageBenchmarkWorkload.java | 24 +- .../imap/IMapDagStorageBenchmarkWorkloadTest.java | 262 +++++++++++++++++++++ .../imap/IMapDagStoragePersistenceTest.java | 120 ++++++++++ 6 files changed, 432 insertions(+), 10 deletions(-) diff --git a/docs/en/engines/zeta/benchmark.md b/docs/en/engines/zeta/benchmark.md index 27ff7193bc..ed760947cf 100644 --- a/docs/en/engines/zeta/benchmark.md +++ b/docs/en/engines/zeta/benchmark.md @@ -249,7 +249,7 @@ java -jar seatunnel-benchmarks/target/benchmarks.jar \ ### Run the IMap DAG Storage Benchmarks ```bash -java -jar seatunnel-benchmarks/target/benchmarks.jar IMapDagStorageBenchmark +java -jar seatunnel-benchmarks/target/benchmarks.jar IMapDagStorageBenchmark -foe true ``` `finishedJobDagStore` writes a fixed batch of 100 unique production `JobDAGInfo` values through @@ -257,6 +257,20 @@ the finished-job DAG IMap and its file-backed MapStore. `finishedJobDagLoad` evi reloads it through MapStore. `pipelineCount=1|10|100` controls the exact number of code-built source-to-sink pipelines in each DAG, and `storedDagCount=0|100` controls retained storage pressure. +Store teardown checks the first, middle, and last cached values and deletes the batch outside +timing. After the final measurement in each fork, those samples are evicted and reloaded from +MapStore before deletion. Intermediate iterations do not replay the WAL for verification: +full-WAL scans allocate more as writes and deletion records accumulate, affecting later samples +even though teardown is not timed. WAL history still grows through the unchanged writes and +deletes; this is not a storage compaction or steady-state benchmark. + +Only the final batch has a persistence read-back in each benchmark fork. Dedicated tests cover +complete batch round trips. Local-file read-back establishes visible persisted contents, not +crash durability. Results from the former per-iteration reload fixture are not directly comparable +to this corrected baseline and must not be reported as a production speedup. +Use `-foe true` so a verification failure rejects the run. Allocation/GC profiler results may +include untimed teardown, including the terminal reload; they are not write-only measurements. + ```bash java -jar seatunnel-benchmarks/target/benchmarks.jar \ 'IMapDagStorageBenchmark.finishedJobDagLoad$' \ diff --git a/docs/zh/engines/zeta/benchmark.md b/docs/zh/engines/zeta/benchmark.md index 9338be814e..754e20f96e 100644 --- a/docs/zh/engines/zeta/benchmark.md +++ b/docs/zh/engines/zeta/benchmark.md @@ -237,7 +237,7 @@ java -jar seatunnel-benchmarks/target/benchmarks.jar \ ### 运行 IMap DAG Storage Benchmark ```bash -java -jar seatunnel-benchmarks/target/benchmarks.jar IMapDagStorageBenchmark +java -jar seatunnel-benchmarks/target/benchmarks.jar IMapDagStorageBenchmark -foe true ``` `finishedJobDagStore` 通过已完成作业 DAG IMap 及其文件型 MapStore 固定写入 100 个唯一的 @@ -245,6 +245,18 @@ java -jar seatunnel-benchmarks/target/benchmarks.jar IMapDagStorageBenchmark `pipelineCount=1|10|100` 控制代码构建的 Source-to-Sink Pipeline 数量, `storedDagCount=0|100` 控制已有存储压力。 +Store 的 TearDown 在计时之外检查缓存中的首个、中间和最后一个值,并删除本批数据。 +每个 Fork 的最后一次测量结束后,会先逐出这些样本,再通过 MapStore 重新加载并校验, +然后删除数据。中间迭代不再为校验重放 WAL:随着写入和删除记录累积,全量 WAL 扫描的 +分配量会增长,即使 TearDown 不计时,也会影响后续样本。原有写入和删除操作保持不变, +WAL 历史仍会增长;这不是存储压缩或稳态 Benchmark。 + +每个 Benchmark Fork 仅对最后一批数据进行持久化回读,独立测试覆盖完整批次的往返校验。 +本地文件回读只能证明持久化内容可见,不能证明崩溃后的持久性。旧版每次迭代都回读的 +结果不能与修正后的基线直接比较,也不能将差异描述为生产性能提升。 +使用 `-foe true` 确保校验失败时拒绝本次运行。分配量和 GC Profiler 的结果可能包含不计时的 +TearDown,包括最后的回读,因此不能将它们视为仅包含写入的指标。 + ```bash java -jar seatunnel-benchmarks/target/benchmarks.jar \ 'IMapDagStorageBenchmark.finishedJobDagLoad$' \ diff --git a/seatunnel-benchmarks/src/main/java/org/apache/seatunnel/benchmark/IMapDagStorageBenchmark.java b/seatunnel-benchmarks/src/main/java/org/apache/seatunnel/benchmark/IMapDagStorageBenchmark.java index cf93d88b13..161cad7ddf 100644 --- a/seatunnel-benchmarks/src/main/java/org/apache/seatunnel/benchmark/IMapDagStorageBenchmark.java +++ b/seatunnel-benchmarks/src/main/java/org/apache/seatunnel/benchmark/IMapDagStorageBenchmark.java @@ -68,8 +68,10 @@ public class IMapDagStorageBenchmark extends BenchmarkBase { * <p>The workload builds a real {@code JobDAGInfo} containing 1, 10, or 100 source-to-sink * pipelines before measurement and starts with 0 or 100 retained DAGs. The timed phase writes * that payload under 100 unique job IDs using the production history TTL, exercising IMap - * serialization, FileMapStore, and WAL append. Fixture construction, durable sample reloads, - * and deletion of the measured phase are not timed. + * serialization, FileMapStore, and WAL append. Fixture construction, sample checks, and + * deletion of the measured phase are not timed. Intermediate iterations check cached values; + * only the final measurement reloads samples from MapStore, avoiding full-WAL verification + * scans between measurements. This read-back checks persisted contents, not crash durability. * * <p>{@link Mode#SingleShotTime} fixes each growth phase at 100 DAGs. {@link * OperationsPerInvocation} normalizes the phase duration to one persisted DAG and prevents a diff --git a/seatunnel-benchmarks/src/main/java/org/apache/seatunnel/benchmark/storage/imap/IMapDagStorageBenchmarkWorkload.java b/seatunnel-benchmarks/src/main/java/org/apache/seatunnel/benchmark/storage/imap/IMapDagStorageBenchmarkWorkload.java index d57823579e..f94b109523 100644 --- a/seatunnel-benchmarks/src/main/java/org/apache/seatunnel/benchmark/storage/imap/IMapDagStorageBenchmarkWorkload.java +++ b/seatunnel-benchmarks/src/main/java/org/apache/seatunnel/benchmark/storage/imap/IMapDagStorageBenchmarkWorkload.java @@ -28,6 +28,8 @@ import org.openjdk.jmh.annotations.Scope; import org.openjdk.jmh.annotations.Setup; import org.openjdk.jmh.annotations.State; import org.openjdk.jmh.annotations.TearDown; +import org.openjdk.jmh.infra.IterationParams; +import org.openjdk.jmh.runner.IterationType; import com.hazelcast.map.IMap; @@ -60,10 +62,13 @@ public class IMapDagStorageBenchmarkWorkload { private long[] storeKeys; private int storedDagCountInIteration; private boolean loadExecuted; + private int measurementIteration; + private boolean lastMeasurementIteration; /** Constructs an exact number of production source-to-sink DAG pipelines. */ @Setup(Level.Trial) public void setUp(SeaTunnelStorageEnvironmentContext environment) { + measurementIteration = 0; finishedJobDagMap = environment .getServer() @@ -85,7 +90,10 @@ public class IMapDagStorageBenchmarkWorkload { /** Builds a fresh, fixed-size key set before each measured DAG-store phase. */ @Setup(Level.Iteration) - public void prepareStoreIteration() { + public void prepareStoreIteration(IterationParams iterationParams) { + lastMeasurementIteration = + iterationParams.getType() == IterationType.MEASUREMENT + && ++measurementIteration == iterationParams.getCount(); storeKeys = new long[STORE_OPERATIONS_PER_INVOCATION]; for (int index = 0; index < STORE_OPERATIONS_PER_INVOCATION; index++) { storeKeys[index] = Long.MAX_VALUE - sequence.incrementAndGet(); @@ -111,7 +119,7 @@ public class IMapDagStorageBenchmarkWorkload { } } - /** Validates durable samples and removes the fixed store batch outside measured time. */ + /** Checks sampled values and removes the fixed store batch outside measured time. */ @TearDown(Level.Iteration) public void cleanStoreIteration() { if (storedDagCountInIteration == 0) { @@ -157,15 +165,19 @@ public class IMapDagStorageBenchmarkWorkload { sampledKeys.add(storeKeys[0]); sampledKeys.add(storeKeys[STORE_OPERATIONS_PER_INVOCATION / 2]); sampledKeys.add(storeKeys[STORE_OPERATIONS_PER_INVOCATION - 1]); - for (long sampledKey : sampledKeys) { - finishedJobDagMap.evict(sampledKey); + // MapStore reloads replay the full, growing WAL. Only do this after the last + // measurement so verification allocations cannot interfere with a later sample. + if (lastMeasurementIteration) { + for (long sampledKey : sampledKeys) { + finishedJobDagMap.evict(sampledKey); + } + finishedJobDagMap.loadAll(sampledKeys, true); } - finishedJobDagMap.loadAll(sampledKeys, true); for (long sampledKey : sampledKeys) { JobDAGInfo stored = finishedJobDagMap.get(sampledKey); if (!finishedJobDag.equals(stored)) { throw new IllegalStateException( - "The JobDAGInfo append was not durably persisted for key " + sampledKey); + "The stored JobDAGInfo did not match for key " + sampledKey); } } } diff --git a/seatunnel-benchmarks/src/test/java/org/apache/seatunnel/benchmark/storage/imap/IMapDagStorageBenchmarkWorkloadTest.java b/seatunnel-benchmarks/src/test/java/org/apache/seatunnel/benchmark/storage/imap/IMapDagStorageBenchmarkWorkloadTest.java new file mode 100644 index 0000000000..dacd20dc29 --- /dev/null +++ b/seatunnel-benchmarks/src/test/java/org/apache/seatunnel/benchmark/storage/imap/IMapDagStorageBenchmarkWorkloadTest.java @@ -0,0 +1,262 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.seatunnel.benchmark.storage.imap; + +import org.apache.seatunnel.benchmark.dag.JobDagFixtureFactory; +import org.apache.seatunnel.benchmark.storage.SeaTunnelStorageEnvironmentContext; +import org.apache.seatunnel.engine.common.Constant; +import org.apache.seatunnel.engine.core.job.JobDAGInfo; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; +import org.junit.jupiter.params.provider.ValueSource; +import org.openjdk.jmh.infra.IterationParams; +import org.openjdk.jmh.runner.IterationType; +import org.openjdk.jmh.runner.options.TimeValue; + +import com.hazelcast.map.IMap; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.TimeUnit; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.anySet; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.RETURNS_DEEP_STUBS; +import static org.mockito.Mockito.clearInvocations; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +class IMapDagStorageBenchmarkWorkloadTest { + + private final Map<Long, JobDAGInfo> cached = new HashMap<>(); + private final Map<Long, JobDAGInfo> persisted = new HashMap<>(); + private final List<Long> writtenKeys = new ArrayList<>(); + private IMap<Long, JobDAGInfo> map; + private IMapDagStorageBenchmarkWorkload workload; + + @BeforeEach + @SuppressWarnings("unchecked") + void setUp() { + map = mock(IMap.class); + SeaTunnelStorageEnvironmentContext environment = + mock(SeaTunnelStorageEnvironmentContext.class, RETURNS_DEEP_STUBS); + when(environment + .getServer() + .getNodeEngine() + .getHazelcastInstance() + .<Long, JobDAGInfo>getMap(Constant.IMAP_FINISHED_JOB_VERTEX_INFO)) + .thenReturn(map); + when(environment.storageConfig().getEngineConfig().getHistoryJobExpireMinutes()) + .thenReturn(60); + doAnswer( + invocation -> { + Long key = invocation.getArgument(0); + JobDAGInfo value = invocation.getArgument(1); + persisted.put(key, value); + return cached.put(key, value); + }) + .when(map) + .put(anyLong(), any(JobDAGInfo.class)); + doAnswer( + invocation -> { + Long key = invocation.getArgument(0); + JobDAGInfo value = invocation.getArgument(1); + writtenKeys.add(key); + persisted.put(key, value); + return cached.put(key, value); + }) + .when(map) + .put(anyLong(), any(JobDAGInfo.class), eq(60L), eq(TimeUnit.MINUTES)); + when(map.get(anyLong())).thenAnswer(invocation -> cached.get(invocation.getArgument(0))); + when(map.evict(anyLong())) + .thenAnswer(invocation -> cached.remove(invocation.getArgument(0)) != null); + doAnswer( + invocation -> { + Iterable<Long> keys = invocation.getArgument(0); + for (Long key : keys) { + if (persisted.containsKey(key)) { + cached.put(key, persisted.get(key)); + } + } + return null; + }) + .when(map) + .loadAll(anySet(), eq(true)); + doAnswer( + invocation -> { + cached.remove(invocation.getArgument(0)); + persisted.remove(invocation.getArgument(0)); + return null; + }) + .when(map) + .delete(anyLong()); + + workload = new IMapDagStorageBenchmarkWorkload(); + workload.pipelineCount = 1; + workload.storedDagCount = 100; + workload.setUp(environment); + clearInvocations(map); + } + + @ParameterizedTest + @ValueSource(ints = {1, 20}) + void boundsIntermediateVerificationWithoutReloadingHistory(int batches) { + Set<Long> retainedKeys = new HashSet<>(persisted.keySet()); + for (int batch = 0; batch < batches; batch++) { + storeBatch(IterationType.MEASUREMENT, batches + 1); + workload.cleanStoreIteration(); + assertEquals(retainedKeys, persisted.keySet()); + } + assertEquals(batches * 100, new HashSet<>(writtenKeys).size()); + verify(map, times(batches * 3)).get(anyLong()); + verify(map, times(batches * 100)).delete(anyLong()); + verify(map, never()).evict(anyLong()); + verify(map, never()).loadAll(anySet(), anyBoolean()); + verify(map, times(batches * 100)) + .put(anyLong(), any(JobDAGInfo.class), eq(60L), eq(TimeUnit.MINUTES)); + } + + @ParameterizedTest + @CsvSource({"0,1", "3,1", "3,5"}) + void reloadsOnlyAfterTheFinalMeasurement(int warmups, int measurements) { + for (int index = 0; index < warmups; index++) { + storeBatch(IterationType.WARMUP, warmups); + workload.cleanStoreIteration(); + } + for (int index = 0; index < measurements - 1; index++) { + storeBatch(IterationType.MEASUREMENT, measurements); + workload.cleanStoreIteration(); + } + verify(map, never()).evict(anyLong()); + verify(map, never()).loadAll(anySet(), anyBoolean()); + + long lastKey = storeBatch(IterationType.MEASUREMENT, measurements); + workload.cleanStoreIteration(); + Set<Long> samples = new HashSet<>(); + samples.add(lastKey + 99); + samples.add(lastKey + 49); + samples.add(lastKey); + verify(map).loadAll(samples, true); + for (Long key : samples) { + verify(map).evict(key); + } + assertEquals(101, persisted.size()); + } + + @ParameterizedTest + @CsvSource({"0,false", "50,false", "99,false", "0,true", "50,true", "99,true"}) + void rejectsInvalidCachedSamplesAndStillDeletesTheBatch(int sampleIndex, boolean missing) { + storeBatch(IterationType.MEASUREMENT, 2); + Long key = writtenKeys.get(sampleIndex); + if (missing) { + cached.remove(key); + } else { + cached.put(key, JobDagFixtureFactory.create(2)); + } + IllegalStateException failure = + assertThrows(IllegalStateException.class, workload::cleanStoreIteration); + assertTrue(failure.getMessage().contains("did not match for key " + key)); + assertEquals(101, persisted.size()); + verify(map, times(100)).delete(anyLong()); + verify(map, never()).loadAll(anySet(), anyBoolean()); + } + + @Test + void finalReadBackDetectsMissingPersistenceEvenWhenTheCachedValueIsCorrect() { + storeBatch(IterationType.MEASUREMENT, 1); + Long key = writtenKeys.get(0); + persisted.remove(key); + assertTrue(cached.containsKey(key)); + + IllegalStateException failure = + assertThrows(IllegalStateException.class, workload::cleanStoreIteration); + assertTrue(failure.getMessage().contains("did not match for key " + key)); + verify(map, times(100)).delete(anyLong()); + assertEquals(101, persisted.size()); + } + + @Test + void cleansAcknowledgedWritesAfterAnIncompleteBatch() { + doAnswer( + invocation -> { + if (writtenKeys.size() == 6) { + throw new IllegalStateException("write failed"); + } + Long key = invocation.getArgument(0); + JobDAGInfo value = invocation.getArgument(1); + writtenKeys.add(key); + persisted.put(key, value); + return cached.put(key, value); + }) + .when(map) + .put(anyLong(), any(JobDAGInfo.class), eq(60L), eq(TimeUnit.MINUTES)); + workload.prepareStoreIteration(iteration(IterationType.MEASUREMENT, 1)); + assertEquals( + "write failed", + assertThrows(IllegalStateException.class, workload::storeFinishedJobDagBatch) + .getMessage()); + assertTrue( + assertThrows(IllegalStateException.class, workload::cleanStoreIteration) + .getMessage() + .contains("expected entry count")); + verify(map, times(6)).delete(anyLong()); + assertEquals(101, persisted.size()); + verify(map, never()).loadAll(anySet(), anyBoolean()); + } + + @Test + void loadInvocationsStillReloadAndValidateWithoutDeletingData() { + workload.prepareStoreIteration(iteration(IterationType.MEASUREMENT, 1)); + for (int index = 0; index < 2; index++) { + workload.prepareInvocation(); + workload.loadFinishedJobDag(); + workload.cleanInvocation(); + } + workload.cleanStoreIteration(); + verify(map, times(2)).loadAll(anySet(), eq(true)); + verify(map, times(4)).evict(anyLong()); + verify(map, times(2)).get(anyLong()); + verify(map, never()).delete(anyLong()); + } + + private long storeBatch(IterationType type, int count) { + workload.prepareStoreIteration(iteration(type, count)); + return workload.storeFinishedJobDagBatch(); + } + + private static IterationParams iteration(IterationType type, int count) { + return new IterationParams(type, count, TimeValue.seconds(1), 1); + } +} diff --git a/seatunnel-benchmarks/src/test/java/org/apache/seatunnel/benchmark/storage/imap/IMapDagStoragePersistenceTest.java b/seatunnel-benchmarks/src/test/java/org/apache/seatunnel/benchmark/storage/imap/IMapDagStoragePersistenceTest.java new file mode 100644 index 0000000000..c937718694 --- /dev/null +++ b/seatunnel-benchmarks/src/test/java/org/apache/seatunnel/benchmark/storage/imap/IMapDagStoragePersistenceTest.java @@ -0,0 +1,120 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.seatunnel.benchmark.storage.imap; + +import org.apache.seatunnel.benchmark.dag.JobDagFixtureFactory; +import org.apache.seatunnel.benchmark.storage.SeaTunnelStorageEnvironmentContext; +import org.apache.seatunnel.engine.common.Constant; +import org.apache.seatunnel.engine.core.job.JobDAGInfo; +import org.apache.seatunnel.engine.imap.storage.file.common.WALReader; +import org.apache.seatunnel.engine.imap.storage.file.config.FileConfiguration; +import org.apache.seatunnel.engine.serializer.protobuf.ProtoStuffSerializer; + +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.fs.FileSystem; +import org.apache.hadoop.fs.Path; + +import org.junit.jupiter.api.Timeout; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; +import org.openjdk.jmh.infra.IterationParams; +import org.openjdk.jmh.runner.IterationType; +import org.openjdk.jmh.runner.options.TimeValue; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.TimeUnit; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** Exercises complete DAG batches through the benchmark's actual file-backed MapStore. */ +class IMapDagStoragePersistenceTest { + + @ParameterizedTest + @CsvSource({"1,0", "1,100", "10,0", "10,100", "100,0", "100,100"}) + @Timeout(value = 3, unit = TimeUnit.MINUTES) + void persistsCompleteBatchesAndDeletionRecords(int pipelines, int retainedDags) + throws Exception { + SeaTunnelStorageEnvironmentContext environment = new SeaTunnelStorageEnvironmentContext(); + try { + environment.setUp(); + IMapDagStorageBenchmarkWorkload workload = new IMapDagStorageBenchmarkWorkload(); + workload.pipelineCount = pipelines; + workload.storedDagCount = retainedDags; + workload.setUp(environment); + JobDAGInfo expected = JobDagFixtureFactory.create(pipelines); + String clusterName = + environment + .getServer() + .getNodeEngine() + .getHazelcastInstance() + .getConfig() + .getClusterName(); + Path walRoot = + new Path( + environment + .imapDirectory() + .resolve(clusterName) + .resolve(Constant.IMAP_FINISHED_JOB_VERTEX_INFO) + .toUri()); + try (FileSystem fileSystem = FileSystem.newInstanceLocal(new Configuration())) { + WALReader reader = + new WALReader( + fileSystem, FileConfiguration.HDFS, new ProtoStuffSerializer()); + Set<Object> retainedKeys = + new HashSet<>(reader.loadAllData(walRoot, Collections.emptySet()).keySet()); + assertEquals(retainedDags + 1, retainedKeys.size()); + for (int batch = 0; batch < 2; batch++) { + workload.prepareStoreIteration( + new IterationParams( + IterationType.MEASUREMENT, 2, TimeValue.seconds(1), 1)); + workload.prepareInvocation(); + long lastKey = workload.storeFinishedJobDagBatch(); + workload.cleanInvocation(); + + // Read the WAL independently of IMap's cache, once for the entire batch. + Map<Object, Object> persisted = + reader.loadAllData(walRoot, Collections.emptySet()); + assertEquals(retainedDags + 101, persisted.size()); + for (long key = lastKey; key < lastKey + 100; key++) { + assertEquals(expected, persisted.get(key), "Missing or invalid DAG " + key); + } + workload.cleanStoreIteration(); + Map<Object, Object> afterDeletion = + reader.loadAllData(walRoot, Collections.emptySet()); + assertEquals(retainedKeys, afterDeletion.keySet()); + assertTrue(afterDeletion.values().stream().allMatch(expected::equals)); + for (long key = lastKey; key < lastKey + 100; key++) { + assertFalse(afterDeletion.containsKey(key)); + } + } + for (int invocation = 0; invocation < 2; invocation++) { + workload.prepareInvocation(); + workload.loadFinishedJobDag(); + workload.cleanInvocation(); + } + } + } finally { + environment.tearDown(); + } + } +}
