Conversation
Instead of returning all the values and execute the aggregation (like avg()), compute avg directly on the block (in this case sum + count)
…ed functions) Also added tags in sealed blocks for fast skip the entire block if dosnt contain the requested tag
Also added snappy-java dependency
🧪 CI InsightsHere's what we observed from your CI run for 02beced. 🟢 All jobs passed!But CI Insights is watching 👀 |
Code Review: Timeseries ModuleThis is an impressive and substantial addition. The architecture is well thought out — columnar storage, crash-safe compaction, Gorilla XOR + delta-of-delta codecs, SIMD acceleration with scalar fallback, push-down aggregation, PromQL support, and continuous aggregates. The 26 test classes provide solid coverage. The following are issues worth addressing before merge. Bugs / Correctness1. Both 2.
3. The Gorilla paper's "same leading/trailing zeros as previous" optimization is correct only when the block length is exactly the same. The canonical implementation tracks 4. Database db = dbRef.get();
if (db == null) { task.cancel(false); return; }
// ... use db later ...Between the null check and subsequent use, the referent could theoretically be GC'd (though in practice the 5. The method finds the WHERE clause by scanning for parentheses and quoted strings, then string-splices in Performance6. When the sealed store is opened cold, 7. Round-robin shard routing distributes time-ordered data across all shards
8. shardExecutor.awaitTermination(30, TimeUnit.SECONDS);On a clean shutdown (no pending tasks), this waits up to 30 seconds unnecessarily if Security9.
10. // Maps '.', '-', ':' to '_'Two distinct ArcadeDB type names that differ only in those characters (e.g., Code Quality / Minor11. if (sampleCount <= maxSamplesAfterDownsample * 0.9)
continue; // already downsampledThe 90% threshold is undocumented. Extract it as a named constant ( 12. If the iterator holds open a file channel (e.g., via 13. Verify that 14. The O(1) clear relies on the fact that the header's active-page counter is the sole truth about live data. This invariant must survive crash recovery. Please add a comment (or an assertion) documenting this, and verify that the mutable-bucket crash-recovery path in Positive highlights
|
Coverage summary from CodacySee diff coverage on Codacy
Coverage variation details
Coverage variation is the difference between the coverage for the head and common ancestor commits of the pull request branch: Diff coverage details
Diff coverage is the percentage of lines that are covered by tests out of the coverable lines that the pull request added or modified: See your quality gate settings Change summary preferences |
Code Review: Timeseries ModuleGreat addition — the module is well-structured with a clear separation between storage (sealed store, buckets), codec, query execution, and schema layers. The test coverage is broad. The notes below are intended to help harden the implementation before merging. HIGH severity1.
final int sampleCountInPage = dataPage.readShort(DATA_SAMPLE_COUNT_OFFSET);
2.
this.indexFile = new RandomAccessFile(f, "rw");
this.indexChannel = indexFile.getChannel();
loadDirectory(); // or writeEmptyHeader() — both can throwIf 3.
Measurement names, tag keys, and tag values are accumulated character-by-character with no upper bound. A crafted line with a multi-megabyte field name causes unbounded heap growth. Add a 4.
if (sealedFormatVersion != TimeSeriesSealedStore.CURRENT_VERSION)
throw new IllegalStateException("Unsupported sealed store format version ...");
5.
if (patternCache.size() >= MAX_PATTERN_CACHE)
patternCache.clear();
return patternCache.computeIfAbsent(regex, r -> Pattern.compile(r));
6.
private String labelKey(final Map<String, String> labels) {
final List<String> sorted = new ArrayList<>(labels.keySet());
Collections.sort(sorted);
final StringBuilder sb = new StringBuilder("{");
...
}This allocates an MEDIUM severity7. final long value = Long.parseLong(interval.substring(0, unitStart));
return switch (unit) {
case "w" -> value * 7 * 86_400_000L;
...
};A very large 8. The per-component overflow guard ( 9.
private final List<long[]> samples = new ArrayList<>();A one-year window at 1-second granularity accumulates ~31 M rows per group in the aggregation phase. This is unnecessary: rate only requires 10. private static boolean isInArray(final int value, final int[] array) {
for (final int v : array) if (v == value) return true;
return false;
}Called for every (row × column) during a projected scan. Pre-sort 11.
12. final int k = agg.param() != null ? (int) ((NumberLiteral) agg.param()).value() : 1;If 13.
else
value = 0.0;For 14.
15. There is no 16.
LOW severity
Test coverage gaps
Overall this is a substantial and well-thought-out feature. The codec implementations (Gorilla XOR, delta-of-delta, Simple8b) are correctly structured, the SQL integration is clean, and the crash-recovery design is sound. Addressing the HIGH items — especially the |
Issue #3514 Unable to reproduce, it might be already fixed
…m the flush queue
|
test |
Code Review — Timeseries Model (PR #3511)This is an impressive feature addition — a full time-series engine with columnar compressed storage, PromQL support, continuous aggregates, SIMD acceleration, and Grafana/Prometheus integration. The test coverage is extensive (29+ test classes) and the architecture is well-thought-out. Previous review rounds have already caught and confirmed fixes for many issues (string overflow guard, metrics race condition, crash recovery, recursion depth limit, Critical — Still Open1. Non-atomic file swap in All four file-rewrite paths ( if (!oldFile.delete() || !tmpFile.renameTo(oldFile))
throw new IOException("...");A crash or Files.move(tmpFile.toPath(), oldFile.toPath(), ATOMIC_MOVE, REPLACE_EXISTING);This is the last remaining critical item per the 5th review pass. 2. The default -> new byte[0];This is invoked from High — New Observations3.
Fix: validate in 4. for (long t = startMs; t <= endMs; t += stepMs)If if (stepMs <= 0)
throw new IllegalArgumentException("stepMs must be positive");5. Transaction not rolled back on failure in try {
database.begin();
type.initEngine();
database.commit();
} catch (final Exception e) {
throw new SchemaException("...", e);
}If Medium6.
7. Flat-mode pre-allocation OOM risk in // pre-allocates double[maxBuckets][requestCount]A query like 8. In return query + " WHERE `" + tsColumn + "` >= " + watermark;This produces invalid SQL like 9. Retention policy skips the mutable bucket
10.
Minor / ArcadeDB Style11. final Integer[] indices = new Integer[timestamps.length];Allocates up to N 12. if (tmpFile.exists())
tmpFile.delete();If deletion fails (e.g., locked on Windows), the store silently continues. Log a warning or throw to prevent operating in a potentially inconsistent state. 13. Silent sample loss is particularly dangerous for time-series data. The async task wraps failures in an exception but the caller has no way to detect dropped writes. Consider exposing an error counter on the engine or providing a callback mechanism. 14. The incubating module warning and the requirement for Strengths
Overall: the non-atomic file swap (#1), the silent data drop in |
|
Addendum — Additional Findings from Engine Analysis A few more issues found on closer inspection of the core engine files: HighFile handles closed before the atomic move completes (TimeSeriesSealedStore) In both TimeSeriesShard constructor: resource leak if sealedStore construction fails this.mutableBucket = new TimeSeriesBucket(...); // succeeds, file opened
this.sealedStore = new TimeSeriesSealedStore(...); // throws -> mutableBucket never closedIf MediumUnsynchronized access to blockDirectory in getter methods (TimeSeriesSealedStore) Some getter methods access the block directory without holding Read lock held during transaction rollback in appendSamples() (TimeSeriesShard) The shard read lock is acquired outside the transaction boundary and released in a finally block that spans any rollback. If the rollback is slow (I/O contention), concurrent readers are blocked for longer than necessary. Consider releasing the lock before the rollback, or at minimum document the intended ordering. These supplement the previous review comment. The file-handle ordering bug is worth fixing alongside the atomic-move change already flagged. |
|
Addendum 2 — Schema and SQL Integration Findings A few more issues from the schema and SQL executor layer: HighNPE in FetchFromTimeSeriesStep and AggregateFromTimeSeriesStep Both execution steps call final TimeSeriesEngine engine = tsType.getEngine(); // can be null
resultIterator = engine.iterateQuery(...); // NPEFix: check for null and throw a descriptive Watermark advancement not atomic with data commit (ContinuousAggregateRefresher) The continuous aggregate refresher commits the new aggregated rows in a database transaction, then calls if (ca.getWatermarkTs() > watermark) {
final LocalSchema schema = (LocalSchema) database.getSchema();
schema.saveConfiguration(); // called AFTER transaction commit
}If Crash recovery gap for continuous aggregates (LocalSchema) On startup, if a continuous aggregate status is Medium
The character-by-character parser skips subquery depth but does not handle
The pattern These are independent of the items in the previous two review comments. |
PR Review: Timeseries ModelThis is an impressive and well-architected addition. The two-layer design (mutable ACID bucket + immutable columnar sealed store), crash-safe compaction protocol, and push-down aggregation all show careful engineering. Below is a detailed review. Bugs / Correctness Issues1.
} else if (dod >= -255 && dod <= 255) {
writer.writeBits(0b110, 3);
writer.writeBits(zigZagEncode(dod), 9);The Javadoc says the range is 2. Potential OOM from malformed / corrupted block data
if (totalCount < 0)
throw new IOException("Simple8bCodec: negative count..." );
final long[] result = new long[totalCount];A corrupted or maliciously crafted block with 3.
for (int i = 0; i < tsCount; i++) {
final long ts = timestamps[i];
if (ts < fromTs || ts > toTs) continue;
Performance / Memory4.
5.
6.
Concurrency / Locking7. Maintenance scheduler thundering herd
8. All maintenance threads share the same name
Format Versioning / Upgrade Path9. Hard
Security10. PromQL ReDoS guard: good start, but incomplete
Minor / Nits
What's Well Done
|
Code Review: Timeseries Model (#3511)This is a substantial and well-designed contribution. The architecture is sound: LSM-tree-style lock-free compaction, columnar sealed storage with block-level stats for aggregation push-down, Gorilla/Delta-of-Delta/Simple-8b codecs, and InfluxDB line protocol support. The test coverage is extensive. Below are the issues I found, from most to least critical. Concurrency: Temp-file path collision (TimeSeriesShard + TimeSeriesSealedStore)Phase 3 of
The maintenance scheduler runs compact→retention→downsampling sequentially within a single task, so within one scheduled tick this cannot race. But a user-triggered Suggested fix: either extend Correctness:
|
Coverage summary from CodacySee diff coverage on Codacy
Coverage variation details
Coverage variation is the difference between the coverage for the head and common ancestor commits of the pull request branch: Diff coverage details
Diff coverage is the percentage of lines that are covered by tests out of the coverable lines that the pull request added or modified: See your quality gate settings Change summary preferences |
Code Review — PR #3511: Timeseries modelThis is an impressive and substantial addition. The architecture is well thought out: LSM-style lock-free compaction, Gorilla/delta-of-delta codecs, SIMD-accelerated aggregation, block-level fast-path stats, PromQL integration, and crash-safe recovery. The inline documentation is excellent and the test coverage is broad. Below are issues worth addressing before merge. Bug: Double rollback may silently discard a caller's outer transactionFile: } catch (final ConcurrentModificationException cme) {
if (database.isTransactionActive())
database.rollback();
// Also roll back the enclosing TX ...
if (database.isTransactionActive())
database.rollback(); // ← rolls back caller's outer TX
throw cme;
}The second Potential metadata inconsistency after
|
Code Review: TimeSeries ModuleThis is a substantial and well-architected contribution. The LSM-style sharded storage, multi-tier downsampling, crash-safe compaction, Gorilla/Delta-of-Delta codecs, and SIMD vectorization are all thoughtful choices. The test coverage breadth is impressive. Below are issues worth addressing before merging. Critical / High Priority1. SIMD module runtime crash risk
Recommendation: Wrap the SIMD class instantiation in 2. SQL injection in The 3. In Recommendation: Use the AST node directly (e.g. 4. Tag filter extraction is incorrect for
5. If a tag value is legitimately null or missing in a sample, any call to Medium Priority6.
7. Shallow clone in The constructor clones 8. No explicit transaction in The execute method calls 9. The maintenance scheduler submits tasks to a thread pool. If Low Priority / Style10. Multiple test classes override 11. Missing test for
12. In 13. When time-range extraction fails or push-down is not eligible, the planner falls back to a full scan with no log entry. For a performance-critical feature this makes it hard to diagnose why a query is slow. Consider a 14. The incubator module flag added to the Maven compiler does not automatically propagate to Positive Observations
Overall this is high-quality work. The critical issues above (particularly the SIMD crash risk, SQL injection surface, and tag-filter OR semantics) should be addressed before merge. |
Code Review — TimeSeries ModuleThis is a substantial and well-designed feature. The architecture is solid and the code demonstrates careful thinking about performance, crash safety, and concurrency. Below are observations ranging from bugs to minor nitpicks. Potential Bugs1. The class Javadoc documents the 9-bit ZigZag bucket as // DeltaOfDeltaCodec.java line 73
} else if (dod >= -255 && dod <= 255) {
writer.writeBits(0b110, 3);
writer.writeBits(zigZagEncode(dod), 9);This is a 1-value discrepancy. The Gorilla paper uses asymmetric ranges, but the documented format says 2.
In practice this causes only suboptimal flat-array sizing in Performance Concerns3. For range queries,
A single pass over 4. For each block, the method allocates 5–8 separate small Design / API Issues5. The method is named 6. The Javadoc on 7. The query stored in a continuous aggregate is user-provided and then modified by string manipulation to inject a watermark filter. The WITH cte AS (SELECT ... FROM src WHERE ts > 0) SELECT ... FROM cte GROUP BY ...would have Minor Notes8. Compaction errors silently swallowed in The maintenance task catches 9.
10. This constant limits TAG column values to 256 UTF-8 bytes. This constraint is not surfaced at type creation time (the What's Done Well
|
Code Review — TimeSeries ModuleThis is a large, ambitious feature and the overall quality is high. The compression codec choices are well-founded (Gorilla/DoD/Simple8b/Dictionary), the 5-phase lock-free compaction with crash recovery is carefully designed, and the test coverage is excellent (20+ test classes covering crash recovery, concurrent compaction, downsampling, format versioning, gap analysis, and more). Below are observations ranging from bugs to minor nits. 🔴 Bugs1. WHERE clause operator-precedence bug in When the source query already has a WHERE clause, the watermark filter is injected as: WHERE `ts` >= 1234 AND <existing condition>If -- Original: WHERE a = 1 OR b = 2
-- Result: WHERE ts >= 1234 AND a = 1 OR b = 2
-- Equivalent: WHERE (ts >= 1234 AND a = 1) OR b = 2Rows matching only return before + " `" + tsColumn + "` >= " + watermark + " AND (" + after.stripLeading() + ")";
2. Unlike return Long.MIN_VALUE; // silent fallbackA WHERE condition like 🟡 Design / Architecture3. The same timestamp-conversion logic exists in:
These should be consolidated into a single utility method (e.g., 4. The two implementations are byte-for-byte identical. 5. The sealed store intentionally uses 🟡 Correctness / Validation6. The compact constructor validates 7. private static final Pattern SAFE_COLUMN_NAME = Pattern.compile("[A-Za-z0-9_]+");This permits names like 🟡 Code Quality8. This single file contains file I/O, block directory management, aggregation logic, decompression, block-level stats, CRC validation, downsampling, compaction temp-file writing, and retention. Consider extracting at minimum 9. With many TimeSeries types or high downsampling workloads, 4 threads may be insufficient or wasteful. This should be driven by a 10.
🟢 Positive Observations
|
New Timeseries module, finally!