Skip to content

fstree: support online depth reshaping - #4149

Open
End-rey wants to merge 1 commit into
masterfrom
fstree-online-reshape
Open

fstree: support online depth reshaping#4149
End-rey wants to merge 1 commit into
masterfrom
fstree-online-reshape

Conversation

@End-rey

@End-rey End-rey commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

Closes #3774.

@codecov

codecov Bot commented Aug 26, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 78.21782% with 88 lines in your changes missing coverage. Please review.
✅ Project coverage is 30.76%. Comparing base (db2dd63) to head (ed2d035).

Files with missing lines Patch % Lines
...kg/local_object_storage/blobstor/fstree/reshape.go 79.87% 32 Missing ⚠️
pkg/local_object_storage/blobstor/fstree/fstree.go 80.30% 26 Missing ⚠️
cmd/neofs-node/config.go 0.00% 12 Missing ⚠️
...kg/local_object_storage/blobstor/fstree/control.go 81.03% 11 Missing ⚠️
pkg/local_object_storage/blobstor/fstree/head.go 87.87% 4 Missing ⚠️
...ject_storage/blobstor/fstree/fstree_write_linux.go 71.42% 2 Missing ⚠️
cmd/neofs-node/storage.go 0.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #4149      +/-   ##
==========================================
+ Coverage   30.32%   30.76%   +0.44%     
==========================================
  Files         673      674       +1     
  Lines       40673    40986     +313     
==========================================
+ Hits        12333    12611     +278     
- Misses      28340    28375      +35     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@roman-khimov
roman-khimov requested a balanced review from Copilot August 26, 2026 16:07

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Comment thread cmd/neofs-node/config.go
return fmt.Errorf("validate configuration: %w", err)
}
if blobstorLayoutChanged(oldCfg, newCfg) {
return errors.New("changing blobstor depth requires node restart")

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why can't it be done without it? Triggered via control service?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Runtime layout changes require more than updating configuration: the node must locate the active FSTree instance, persist the new reshape state, atomically switch primary/fallback layouts, and synchronize this transition with concurrent PUT, DELETE, GET and iteration operations. It must also define what happens if configuration reload succeeds only for some shards or the reshape worker cannot start.

A restart performs this transition during storage initialization, before the node starts serving requests. Reshaping itself is still online, and the persistent descriptor state makes it safe to resume after later restarts. Given that, adding a separate runtime control path does not seem worth the additional complexity and risk.

Comment thread docs/storage-node-configuration.md Outdated
Comment thread pkg/local_object_storage/blobstor/fstree/control.go Outdated
Comment thread pkg/local_object_storage/blobstor/fstree/fstree.go Outdated
Comment thread pkg/local_object_storage/blobstor/fstree/fstree.go Outdated
return err
}
t.reshapeMtx.Lock()
defer t.reshapeMtx.Unlock()

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure what you're trying to protect from.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It prevents a delete/rename race that could resurrect a deleted object.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rename is atomic. Proper sequencing can prevent this. Delete old path first, then delete new path. If old path is deleted before reshaper rename --- reshaper gets a rename error. If reshaper is first to rename then deleting old path fails, but deleting new path does.

Comment thread pkg/local_object_storage/blobstor/fstree/reshape.go Outdated
if err := util.MkdirAllX(filepath.Dir(newPath), t.Permissions); err != nil {
return false, fmt.Errorf("create destination directory for %q: %w", newPath, err)
}
if _, err := os.Lstat(newPath); err == nil {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why? Things can change between lstat and rename. You're trying to avoid this with reshapeMtx, but this doesn't matter much anyway, objects with the same address are the same objects and rename can be done safely. Then reshapeIterMtx will drastically affect performance, this background process will almost always hold the lock and regular operations will suffer badly.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I still think this lstat can be avoided.

Comment thread pkg/local_object_storage/blobstor/fstree/reshape.go Outdated
Comment thread pkg/local_object_storage/blobstor/fstree/reshape.go Outdated
@End-rey
End-rey force-pushed the fstree-online-reshape branch 3 times, most recently from b11b791 to f7a4027 Compare August 31, 2026 12:55
@End-rey
End-rey requested a review from roman-khimov August 31, 2026 13:17
@End-rey
End-rey force-pushed the fstree-online-reshape branch from f7a4027 to 9ab6efa Compare August 31, 2026 13:48
@roman-khimov
roman-khimov requested a balanced review from Copilot August 31, 2026 16:35

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@End-rey
End-rey requested a balanced review from Copilot August 31, 2026 17:41

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 18 out of 18 changed files in this pull request and generated 3 comments.

Comment thread pkg/local_object_storage/blobstor/fstree/reshape.go Outdated
Comment on lines +330 to +336
info, statErr := os.Stat(entry.path)
if statErr != nil {
return fmt.Errorf("stat file %q: %w", entry.path, statErr)
}
if err = sizeHandler(entry.addr, uint64(info.Size())); err != nil {
return err
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

Comment on lines +275 to +278
d.Depth = d.Reshape.ToDepth
d.Reshape = nil
if err = writeDescriptor(descPath, d); err != nil {
return fmt.Errorf("write completed reshape descriptor: %w", err)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

@End-rey
End-rey force-pushed the fstree-online-reshape branch 2 times, most recently from f7e1ac1 to 314eee8 Compare September 1, 2026 15:57
@End-rey
End-rey requested a balanced review from Copilot September 1, 2026 16:01

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

return nil
}

func (t *FSTree) reshapeLastProcessedPath() (string, error) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd expect it to be read once on FSTree init, parsing it again just adds some potential for errors.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.


func (t *FSTree) updateReshapeProgress(lastProcessedPath string) error {
descPath := t.descriptorPath()
f, err := os.Open(descPath)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can also keep the current version in mem, update last path there and flush it to disk, without reading the file.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

return err
}
t.reshapeMtx.Lock()
defer t.reshapeMtx.Unlock()

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rename is atomic. Proper sequencing can prevent this. Delete old path first, then delete new path. If old path is deleted before reshaper rename --- reshaper gets a rename error. If reshaper is first to rename then deleting old path fails, but deleting new path does.

func (t *FSTree) getPath(addr oid.Address) (string, error) {
p := t.treePath(addr)
t.reshapeMtx.RLock()
defer t.reshapeMtx.RUnlock()

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same with all other operations, old first, new afterwards --- no locking needed.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

}

t.reshapeMtx.Lock()
defer t.reshapeMtx.Unlock()

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Puts don't care at all, they always use new path.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed.

return err
}
*dirty = false
if err := t.updateReshapeProgress(relativePath); err != nil {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can write this file and have a single syncfs call for it and other meta (avoid syncReshape).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

if err := util.MkdirAllX(filepath.Dir(newPath), t.Permissions); err != nil {
return false, fmt.Errorf("create destination directory for %q: %w", newPath, err)
}
if _, err := os.Lstat(newPath); err == nil {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I still think this lstat can be avoided.

}
}
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👀

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

@End-rey
End-rey force-pushed the fstree-online-reshape branch 3 times, most recently from c153a12 to ed2d035 Compare September 4, 2026 15:31
Add secondary-depth fallback and background file relocation. Require restart for
FSTree layout changes.

Closes #3774.

Signed-off-by: Andrey Butusov <andrey@nspcc.io>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Online FSTree reshaping

3 participants