Visitar URL original
DROP TYPE on a TIMESERIES type whose engine failed to start reports success and deletes none of the engine's files, and a type later created under that name silently serves the dropped type's samples · Issue #10866 · ArcadeData/arcadedb · GitHub
Skip to content

DROP TYPE on a TIMESERIES type whose engine failed to start reports success and deletes none of the engine's files, and a type later created under that name silently serves the dropped type's samples #10866

Closed

Description

@ruispereira

SEVERITY: HIGH

Summary

A TIMESERIES type registered with no engine (issue #6356, a sealed store that failed to open at load)
answers DROP TYPE with dropped:true, leaves the type out of schema.json, and deletes nothing: both
shard buckets, both sealed files and the tag dictionary stay on disk at full size, and all three
components come back registered in the FileManager at every subsequent open. CHECK DATABASE reports
zero warnings and an empty unreferencedFiles on that database. The name is then either unusable
(CREATE TIMESERIES TYPE of the same name throws) or, once the one damaged file is repaired or
removed, a brand new empty type comes up serving the dropped type's samples: 3000 of 3000 after a
repair, 1500 of 3000 after the damaged file is deleted. The same sequence on a healthy type deletes
all five files and recreates an empty type.

The rename path refuses to create exactly this state (checkEngineFilesCanMove). The drop path creates
it.

Where (HEAD 0d9d8ce)

  • engine/src/main/java/com/arcadedb/schema/LocalTimeSeriesType.java:258-267 drop(), if (engine != null)
    with no else
  • engine/src/main/java/com/arcadedb/schema/LocalTimeSeriesType.java:320-335 collectOwnedComponentFiles,
    which HAS the no-engine arm
  • engine/src/main/java/com/arcadedb/schema/LocalTimeSeriesType.java:269-301 and :374-409 renameFiles /
    renameFilesWithoutEngine, which also have it
  • engine/src/main/java/com/arcadedb/schema/LocalDocumentType.java:754-759 the javadoc stating the rule
  • engine/src/main/java/com/arcadedb/schema/LocalSchema.java:3242-3243 dropTypeInternal, the only TS step
  • engine/src/main/java/com/arcadedb/schema/LocalSchema.java:3309-3310 the drop side consuming the list
  • engine/src/main/java/com/arcadedb/schema/LocalSchema.java:3800-3817 openTimeSeriesEngine, which calls
    markEngineUnavailable
  • engine/src/main/java/com/arcadedb/engine/UnreferencedFiles.java:68-71 and :240-256, why CHECK DATABASE
    is silent about the leftovers

Repro

Probe: create the type with SHARDS 2, append 3000 rows, compactAll() so a real .ts.sealed exists per
shard, close, flip byte 0 of the shard-0 sealed file (the recipe the existing test
Issue6340TimeSeriesCheckDatabaseTest#aTimeSeriesTypeWhoseSealedStoreFailsToLoadStaysInTheSchemaAndIsReported
already uses), reopen, DROP TYPE, then list the directory, the FileManager and schema.json. The control
is the identical sequence with the byte flip omitted.

The engine == null state is reachable: it is the supported #6356 state, and the open prints the
SEVERE line that registers it.

[state] existsType(Cpu) = true
[state] isEngineAvailable() = false
[state] engineUnavailableReason = Invalid sealed store magic in '.../Cpu_shard_0.ts.sealed': ab534958

CONTROL, healthy type, same drop:

[setup] files on disk before reopen:
Cpu_shard_0.2.65536.v2.tstb (131072 bytes)
Cpu_shard_0.ts.sealed (5305 bytes)
Cpu_shard_1.3.65536.v2.tstb (131072 bytes)
Cpu_shard_1.ts.sealed (5579 bytes)
Cpu_tags.1.65536.v0.tstd (131072 bytes)
[state] isEngineAvailable() = true
[drop] row: {"operation":"drop type","typeName":"Cpu","dropped":true}
[after drop] files on disk:
[after drop] FileManager components mentioning Cpu:

[after drop] schema.getFileByName(...):
Cpu_shard_0 -> null
Cpu_shard_1 -> null
Cpu_tags -> null
[recreate] succeeded
[recreate] first rows =

DAMAGED, engine == null, same drop:

[drop] row: {"operation":"drop type","typeName":"Cpu","dropped":true}
[drop] existsType(Cpu) after = false
[after drop] files on disk:
Cpu_shard_0.2.65536.v2.tstb (131072 bytes)
Cpu_shard_0.ts.sealed (5305 bytes)
Cpu_shard_1.3.65536.v2.tstb (131072 bytes)
Cpu_shard_1.ts.sealed (5579 bytes)
Cpu_tags.1.65536.v0.tstd (131072 bytes)
[after drop] FileManager components mentioning Cpu:
id=1 component=Cpu_tags file=Cpu_tags.1.65536.v0.tstd
id=2 component=Cpu_shard_0 file=Cpu_shard_0.2.65536.v2.tstb
id=3 component=Cpu_shard_1 file=Cpu_shard_1.3.65536.v2.tstb
[after drop] schema.getFileByName(...):
Cpu_shard_0 -> TimeSeriesBucket fileId=2
Cpu_shard_1 -> TimeSeriesBucket fileId=3
Cpu_tags -> TimeSeriesTagDictionary fileId=1
[after drop] CHECK DATABASE:
totalTimeSeriesTypes = 0
corruptedTimeSeries = []
totalWarnings = 0
unreferencedFiles = []
warnings = []
autoFix = 0

The three components are NOT written back to schema.json, and they come back registered anyway at the
next open, from the directory scan:

[after drop] schema.json lines mentioning Cpu:
<no 'Cpu' in schema.json>
[reopen] existsType(Cpu) = false
[reopen] FileManager components mentioning Cpu:
id=1 component=Cpu_tags file=Cpu_tags.1.65536.v0.tstd
id=2 component=Cpu_shard_0 file=Cpu_shard_0.2.65536.v2.tstb
id=3 component=Cpu_shard_1 file=Cpu_shard_1.3.65536.v2.tstb

The whole of schema.json after the drop, verbatim:

{"schemaVersion":15,"dbmsVersion":"26.11.1-SNAPSHOT","dbmsBuild":"0d9d8ce189...","settings":{...},
"types":{},"triggers":{},"materializedViews":{},"continuousAggregates":{},"functions":{}}

Recreating the name straight away fails, because the one damaged file is still there:

[recreate] CREATE TIMESERIES TYPE Cpu ... (same name, same shape)
[recreate] THREW com.arcadedb.exception.SchemaException: Failed to initialize TimeSeries engine for type 'Cpu'

The adoption, measured

Two repairs an operator would plausibly make after the drop, before recreating the name. Same probe,
the fix applied between the DROP and the CREATE.

Repair the flipped byte (the sealed store is valid again):

[fix] flipped byte 0 of Cpu_shard_0.ts.sealed back: the sealed store is valid again
[reopen] existsType(Cpu) = false
[recreate] succeeded, isEngineAvailable() = true
[recreate] SELECT count(*) FROM Cpu = 3000
[recreate] SELECT FROM Cpu LIMIT 3 = {"ts":"2023-11-14 22:13:20","hostname":"host_0","usage":0.0} {"ts":"2023-11-14 22:13:21","hostname":"host_1","usage":1.0} {"ts":"2023-11-14 22:13:22","hostname":"host_2","usage":2.0}
[recreate] CHECK DATABASE:
totalTimeSeriesTypes = 1
corruptedTimeSeries = []
totalWarnings = 0
unreferencedFiles = []

Delete the corrupt sealed file instead (the more likely cleanup):

[fix] deleting the corrupt Cpu_shard_0.ts.sealed: true
[recreate] succeeded, isEngineAvailable() = true
[recreate] SELECT count(*) FROM Cpu = 1500
[recreate] SELECT FROM Cpu LIMIT 3 = {"ts":"2023-11-14 22:38:20","hostname":"host_2","usage":1500.0} ...

That second one is the worst shape the probe found: a type the operator created as new comes up
holding exactly half of a dropped type's samples, the half that was on the undamaged shard, with
CHECK DATABASE reporting a healthy database. The control, dropped and recreated the same way, answers
0 rows, which is what DropTimeSeriesTypeTest#dropAndRecreateTimeSeriesTypeDataCleanup asserts.

Causality pinned

Applied the fix the reading proposes to LocalTimeSeriesType.drop() in wt-ref (an else arm that resolves
the components through engineComponentNames(name), deletes each resolved PaginatedComponent the way
TimeSeriesEngine.drop() does, and deletes the per-shard sealed files by name), recompiled, re-ran both
probes unchanged. The damaged case then behaves exactly like the control:

[state] isEngineAvailable() = false
[drop] row: {"operation":"drop type","typeName":"Cpu","dropped":true}
[after drop] files on disk:
[after drop] FileManager components mentioning Cpu:

[after drop] schema.getFileByName(...):
Cpu_shard_0 -> null
Cpu_shard_1 -> null
Cpu_tags -> null
[recreate] succeeded
[recreate] isEngineAvailable() = true
[recreate] first rows =

and the adoption is gone in both recovery variants:

=================== post-drop recovery: repair ===================
[recreate] SELECT count() FROM Cpu = 0
=================== post-drop recovery: remove ===================
[fix] deleting the corrupt Cpu_shard_0.ts.sealed: false
[recreate] SELECT count(
) FROM Cpu = 0

The sealed file delete answering false in the second run is the drop having already deleted it.

With that arm in place the existing suite around the drop, the rename and the #6356 state is still
green, so the arm does not disturb the behaviour those tests pin:

mvn -pl engine -Dtest='DropTimeSeriesTypeTest,Issue6340TimeSeriesCheckDatabaseTest,Issue10749RenameRollbackTest,CreateTimeSeriesTypeStatementTest' test
[INFO] Tests run: 18, Failures: 0, Errors: 0, Skipped: 0 -- in com.arcadedb.engine.timeseries.Issue6340TimeSeriesCheckDatabaseTest
[INFO] Tests run: 35, Failures: 0, Errors: 0, Skipped: 0
[INFO] BUILD SUCCESS

Note that those 35 green tests are also the measure of the gap: none of them covers the drop in the
engine-less state, which is why the defect is at HEAD. The patch was reverted afterwards; it is a
causality probe, not a proposed patch text.

Refutation attempted

  • Is the state reachable at all? Yes, and it is not a synthetic one: the identical byte flip is the
    setup of four existing tests in Issue6340TimeSeriesCheckDatabaseTest, which assert the type must stay
    registered with isEngineAvailable() false. Reproduced it and printed the state before going further.
  • Does some other step of the drop delete the files? No. Measured, not read: after DROP TYPE the five
    files are byte-for-byte the size they were, and the three components answer getFileByName.
  • Does a reopen clean them up? No. Reopened and they are registered again, from the directory scan and
    not from schema.json, which has no trace of the type.
  • Does CHECK DATABASE see it? No. Zero warnings, empty unreferencedFiles, on the database with five
    orphaned files. The reading's explanation holds: UnreferencedFiles reports a file with NO schema
    component (these have one) or a component that is a LocalBucket (TimeSeriesBucket and
    TimeSeriesTagDictionary both extend PaginatedComponent, checked at their class declarations), and its
    own javadoc at :68-71 names "the time-series internals" among what it claims without checking.
  • Is the adoption only possible because the file was damaged? No, the opposite: the damage is what makes
    CREATE fail loudly. Repair or remove the one damaged file and the adoption happens silently.
  • Is the leftover data unreachable in practice? No. It is served by a plain SELECT on the new type.
  • Does the healthy drop leave anything behind too? No. Control directory listing after the drop is
    empty of Cpu files, FileManager has none, and the recreated type is empty.

Suggested fix and the guarantee

Give drop() the same no-engine arm its two siblings have. The guarantee to ask for: one list, honoured
by the rename and by the drop, in both engine states, pinned by a test that drives both engine states
through both operations rather than one state through both. The probe patch above shows the arm is a
dozen lines and that it closes both the leftover files and the adoption.

A second guarantee worth asking for separately, because it would have caught this on its own: the drop
probe at LocalSchema.java:3379-3433 runs over the ids collectOwnedComponentFiles returns and calls them
"the files this statement is going to delete". Nothing checks afterwards that they were. An assertion
that every probed id is gone from the FileManager once the mutation has run would have failed here.

Scope / confidence

Executed: both probes, on a clean build of HEAD in wt-ref, before and after the causality patch. Every
line quoted above is real output. Not executed: anything under HA (the leftover components would also
be on followers, which I did not test), and the server/HTTP surface; the probe is embedded only.

Dedup

alltitles.txt: #10750, #10421, #10412, #10368, #10317, #10096, #9978 are the DROP TYPE entries, none
about TIMESERIES engine files. #10749 is the closed rename issue whose fix introduced
collectOwnedComponentFiles; it is the rename moving no files, not the drop deleting none. #10796 and
#10751 are rename-adjacent. No "6356" title, no title about leftover shard or sealed files. No near
match.

Activity

  1. added
    engineCore engine: document/schema API
    severity:majorA feature is broken or returns wrong results, with no practical workaround
    storageStorage/paging/WAL engine layer
    on Oct 28, 2026
  2. added this to the 26.11.1 milestone on Oct 28, 2026
  3. added
    in progressThe team is actively working on this issue
    on Oct 28, 2026
  4. self-assigned this
    on Oct 28, 2026
  5. added 2 commits that reference this issue on Oct 28, 2026
  6. lvca commented on Oct 28, 2026

    @lvca
    Member

    Fixed in #10901 (release 26.11.1).

    DROP TYPE on a TIMESERIES type whose storage could not be opened (for example a damaged .ts.sealed file) now deletes all of the type's files: shard buckets, sealed files and tag dictionary. A type created later with the same name starts empty, instead of failing or showing the dropped type's samples. If a drop ever leaves a file behind, it is now logged.


    ❤️ Thanks for helping make ArcadeDB better! ArcadeDB is free and open source, sustained by its community. If it's useful to you or your company, please consider becoming a sponsor to keep development going.

  7. removed
    in progressThe team is actively working on this issue
    on Oct 28, 2026
  8. ruispereira commented on Oct 29, 2026

    @ruispereira
    ContributorAuthor

    Re-validated at main e353330. COMPLETE, and the engine-less drop now deletes all five files.

    Two things to pass on.

    One residual on the logging claim. LocalSchema.reportFilesTheDropLeft is fed collectOwnedComponentFiles, which only ever holds component file ids, because LocalTimeSeriesType.java:374-377 adds an id only when schema.getFileByName(componentName) instanceof PaginatedComponent. A sealed file is held by no component. So "if a drop ever leaves a file behind it is now logged" covers the buckets and the tag dictionary at SEVERE, and the sealed files only at WARNING from inside dropFilesWithoutEngine's catch. Asserting after the mutation that neither the probed ids nor TimeSeriesSealedStore.sealedFileNameFor(name, i) for i < shardCount still exists would make the claim true for both halves.

    And the thing worth more than the residual: this fix and #10867 both guard the DROP, which is the producer. Nothing guards the CREATE, which is the consumer, and the state both issues were about is still reachable by any other route that puts a file at the name. We reproduced it and filed it as #10925. Our own part in that gap: both of our reports ended at the same bad state and both suggested-fix sections addressed the drop. Neither of us asked why CREATE adopts a file it did not write.

  9. added a commit that references this issue on Nov 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

bugengineCore engine: document/schema APIseverity:majorA feature is broken or returns wrong results, with no practical workaroundstorageStorage/paging/WAL engine layertimeseriesTime series module

Type

No type

Projects

No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions