Files
seaweedFS/weed/filer/foundationdb
Chris Lu e8b7347031 Reduce memory allocations in hot paths (#7725)
* filer: reduce allocations in MatchStorageRule

Optimize MatchStorageRule to avoid allocations in common cases:
- Return singleton emptyPathConf when no rules match (zero allocations)
- Return existing rule directly when only one rule matches (zero allocations)
- Only allocate and merge when multiple rules match (rare case)

Based on heap profile analysis showing 111MB allocated from 1.64M calls
to this function during 180 seconds of operation.

* filer: add fast path for getActualStore when no path-specific stores

Add hasPathSpecificStore flag to FilerStoreWrapper to skip
the MatchPrefix() call and []byte(path) conversion when no
path-specific stores are configured (the common case).

Based on heap profile analysis showing 1.39M calls to this
function during 180 seconds of operation, each requiring a
string-to-byte slice conversion for the MatchPrefix call.

* filer/foundationdb: use sync.Pool for tuple allocation in genKey

Use sync.Pool to reuse tuple.Tuple slices in genKey(), reducing
allocation overhead for every FoundationDB operation.

Based on heap profile analysis showing 102MB allocated from 1.79M
calls to genKey() during 180 seconds of operation. The Pack() call
still allocates internally, but this reduces the tuple slice
allocation overhead by ~50%.

* filer: use sync.Pool for protobuf Entry and FuseAttributes

Add pooling for filer_pb.Entry and filer_pb.FuseAttributes in
EncodeAttributesAndChunks and DecodeAttributesAndChunks to reduce
allocations during filer store operations.

Changes:
- Add pbEntryPool with pre-allocated FuseAttributes
- Add EntryAttributeToExistingPb for in-place attribute conversion
- Update ToExistingProtoEntry to reuse existing Attributes when available

Based on heap profile showing:
- EncodeAttributesAndChunks: 69.5MB cumulative
- DecodeAttributesAndChunks: 46.5MB cumulative
- EntryAttributeToPb: 47.5MB flat allocations

* log_buffer: use sync.Pool for LogEntry in readTs

Add logEntryPool to reuse filer_pb.LogEntry objects in readTs(),
which is called frequently during binary search in ReadFromBuffer.

This function only needs the TsNs field from the unmarshaled entry,
so pooling the LogEntry avoids repeated allocations.

Based on heap profile showing readTs with 188MB cumulative allocations
from timestamp lookups during log buffer reads.

* pb: reduce gRPC metadata allocations in interceptor

Optimize requestIDUnaryInterceptor and WithGrpcClient to reduce
metadata allocations on every gRPC request:

- Use AppendToOutgoingContext instead of NewOutgoingContext + New()
  This avoids creating a new map[string]string for single key-value pairs

- Check FromIncomingContext return value before using metadata

Based on heap profile showing metadata operations contributing 0.45GB
(10.5%) of allocations, with requestIDUnaryInterceptor being the main
source at 0.44GB cumulative.

Expected reduction: ~0.2GB from avoiding map allocations per request.

* filer/log_buffer: address code review feedback

- Use proto.Reset() instead of manual field clearing in resetLogEntry
  for more idiomatic and comprehensive state clearing
- Add resetPbEntry() call before pool return in error path for
  consistency with success path in DecodeAttributesAndChunks

* log_buffer: reduce PreviousBufferCount from 32 to 4

Reduce the number of retained previous buffers from 32 to 4.
Each buffer is 8MB, so this reduces the maximum retained memory
from 256MB to 32MB for previous buffers.

Most subscribers catch up quickly, so 4 buffers (32MB) should
be sufficient while significantly reducing memory footprint.

* filer/foundationdb: use defer for tuple pool cleanup in genKey

Refactor genKey to use defer for returning the pooled tuple.
This ensures the pooled object is always returned even if
store.seaweedfsDir.Pack panics, making the code more robust.

Also simplifies the code by removing the temporary variable.

* filer: early-stop MatchStorageRule prescan after 2 matches

Stop the prescan callback after finding 2 matches since we only
need to know if there are 0, 1, or multiple matches. This avoids
unnecessarily scanning the rest of the trie when many rules exist.

* fix: address critical code review issues

filer_conf.go:
- Remove mutable singleton emptyPathConf that could corrupt shared state
- Return fresh copy for no-match case and cloned copy for single-match case
- Add clonePathConf helper to create shallow copies safely

grpc_client_server.go:
- Remove incorrect AppendToOutgoingContext call in server interceptor
  (that API is for outbound client calls, not server-side handlers)
- Rely on request_id.Set and SetTrailer for request ID propagation

* fix: treat FilerConf_PathConf as immutable

Fix callers that were incorrectly mutating the returned PathConf:

- filer_server_handlers_write.go: Use local variable for MaxFileNameLength
  instead of mutating the shared rule

- command_s3_bucket_quota_check.go: Create new PathConf explicitly when
  modifying config instead of mutating the returned one

This allows MatchStorageRule to safely return the singleton or direct
references without copying, restoring the memory optimization.

Callers must NOT mutate the returned *FilerConf_PathConf.

* filer: add ClonePathConf helper for creating mutable copies

Add reusable ClonePathConf function that creates a mutable copy of
a PathConf. This is useful when callers need to modify config before
calling SetLocationConf.

Update command_s3_bucket_quota_check.go to use the new helper.

Also fix redundant return statement in DeleteLocationConf.

* fmt

* filer: fix protobuf pool reset to clear internal fields

Address code review feedback:

1. resetPbEntry/resetFuseAttributes: Use struct assignment (*e = T{})
   instead of field-by-field reset to clear protobuf internal fields
   (unknownFields, sizeCache) that would otherwise accumulate across
   pool reuses, causing data corruption or memory bloat.

2. EntryAttributeToExistingPb: Add nil guard for attr parameter to
   prevent panic if caller passes nil.

* log_buffer: reset logEntry before pool return in error path

For consistency with success path, reset the logEntry before putting
it back in the pool in the error path. This prevents the pooled object
from holding references to partially unmarshaled data.

* filer: optimize MatchStorageRule and document ClonePathConf

1. Avoid double []byte(path) conversion in multi-match case by
   converting once and reusing pathBytes.

2. Add IMPORTANT comment to ClonePathConf documenting that it must
   be kept in sync with filer_pb.FilerConf_PathConf fields when
   the protobuf evolves.

* filer/log_buffer: fix data race and use defer for pool cleanup

1. entry_codec.go EncodeAttributesAndChunks: Fix critical data race -
   proto.Marshal may return a slice sharing memory with the message.
   Copy the data before returning message to pool to prevent corruption.

2. entry_codec.go DecodeAttributesAndChunks: Use defer for cleaner
   pool management, ensuring message is always returned to pool.

3. log_buffer.go readTs: Use defer for pool cleanup, removing
   duplicated resetLogEntry/Put calls in success and error paths.

* filer: fix ClonePathConf field order and add comprehensive test

1. Fix field order in ClonePathConf to match protobuf struct definition
   (WormGracePeriodSeconds before WormRetentionTimeSeconds).

2. Add TestClonePathConf that constructs a fully-populated PathConf,
   calls ClonePathConf, and asserts equality of all exported fields.
   This will catch future schema drift when new fields are added.

3. Add TestClonePathConfNil to verify nil handling.

* filer: use reflection in ClonePathConf test to detect schema drift

Replace hardcoded field comparisons with reflection-based comparison.
This automatically catches:
1. New fields added to the protobuf but not copied in ClonePathConf
2. Missing non-zero test values for any exported field

The test iterates over all exported fields using reflect and compares
src vs clone values, failing if any field differs.

* filer: update EntryAttributeToExistingPb comment to reflect nil handling

The function safely handles nil attr by returning early, but the comment
incorrectly stated 'attr must not be nil'. Update comment to accurately
describe the defensive behavior.

* Fix review feedback: restore request ID propagation and remove redundant resets

1. grpc_client_server.go: Restore AppendToOutgoingContext for request ID
   so handlers making downstream gRPC calls will automatically propagate
   the request ID to downstream services.

2. entry_codec.go: Remove redundant resetPbEntry calls after Get.
   The defer block ensures reset before Put, so next Get receives clean object.

3. log_buffer.go: Remove redundant resetLogEntry call after Get for
   same reason - defer already handles reset before Put.
2025-12-12 12:51:48 -08:00
..
2025-11-19 20:06:57 -08:00

FoundationDB Filer Store

This package provides a FoundationDB-based filer store for SeaweedFS, offering ACID transactions and horizontal scalability.

Features

  • ACID Transactions: Strong consistency guarantees with full ACID properties
  • Horizontal Scalability: Automatic data distribution across multiple nodes
  • High Availability: Built-in fault tolerance and automatic failover
  • Efficient Directory Operations: Optimized for large directory listings
  • Key-Value Support: Full KV operations for metadata storage
  • Compression: Automatic compression for large entry chunks

Installation

Prerequisites

  1. FoundationDB Server: Install and configure a FoundationDB cluster
  2. FoundationDB Client Libraries: Install libfdb_c client libraries
  3. Go Build Tags: Use the foundationdb build tag when compiling

Building SeaweedFS with FoundationDB Support

go build -tags foundationdb -o weed

Configuration

Basic Configuration

Add the following to your filer.toml:

[foundationdb]
enabled = true
cluster_file = "/etc/foundationdb/fdb.cluster"
api_version = 740
timeout = "5s"
max_retry_delay = "1s"
directory_prefix = "seaweedfs"

Configuration Options

Option Description Default Required
enabled Enable FoundationDB filer store false Yes
cluster_file Path to FDB cluster file /etc/foundationdb/fdb.cluster Yes
api_version FoundationDB API version 740 No
timeout Operation timeout duration 5s No
max_retry_delay Maximum retry delay 1s No
directory_prefix Directory prefix for organization seaweedfs No

Path-Specific Configuration

For path-specific filer stores:

[foundationdb.backup]
enabled = true
cluster_file = "/etc/foundationdb/fdb.cluster"
directory_prefix = "seaweedfs_backup"
location = "/backup"

Environment Variables

Configure via environment variables:

export WEED_FOUNDATIONDB_ENABLED=true
export WEED_FOUNDATIONDB_CLUSTER_FILE=/etc/foundationdb/fdb.cluster
export WEED_FOUNDATIONDB_API_VERSION=740
export WEED_FOUNDATIONDB_TIMEOUT=5s
export WEED_FOUNDATIONDB_MAX_RETRY_DELAY=1s
export WEED_FOUNDATIONDB_DIRECTORY_PREFIX=seaweedfs

FoundationDB Cluster Setup

Single Node (Development)

# Start FoundationDB server
foundationdb start

# Initialize database
fdbcli --exec 'configure new single ssd'

Multi-Node Cluster (Production)

  1. Install FoundationDB on all nodes
  2. Configure cluster file (/etc/foundationdb/fdb.cluster)
  3. Initialize cluster:
    fdbcli --exec 'configure new double ssd'
    

Docker Setup

Use the provided docker-compose.yml in test/foundationdb/:

cd test/foundationdb
make setup

Performance Considerations

Optimal Configuration

  • API Version: Use the latest stable API version (720+)
  • Directory Structure: Use logical directory prefixes to isolate different SeaweedFS instances
  • Transaction Size: Keep transactions under 10MB (FDB limit)
  • Batch Operations: Use transactions for multiple related operations

Monitoring

Monitor FoundationDB cluster status:

fdbcli --exec 'status'
fdbcli --exec 'status details'

Scaling

FoundationDB automatically handles:

  • Data distribution across nodes
  • Load balancing
  • Automatic failover
  • Storage node addition/removal

Testing

Unit Tests

cd weed/filer/foundationdb
go test -tags foundationdb -v

Integration Tests

cd test/foundationdb
make test

End-to-End Tests

cd test/foundationdb
make test-e2e

Troubleshooting

Common Issues

  1. Connection Failures:

    • Verify cluster file path
    • Check FoundationDB server status
    • Validate network connectivity
  2. Transaction Conflicts:

    • Reduce transaction scope
    • Implement retry logic
    • Check for concurrent operations
  3. Performance Issues:

    • Monitor cluster health
    • Check data distribution
    • Optimize directory structure

Debug Information

Enable verbose logging:

weed -v=2 server -filer

Check FoundationDB status:

fdbcli --exec 'status details'

Security

Network Security

  • Configure TLS for FoundationDB connections
  • Use firewall rules to restrict access
  • Monitor connection attempts

Data Encryption

  • Enable encryption at rest in FoundationDB
  • Use encrypted connections
  • Implement proper key management

Limitations

  • Maximum transaction size: 10MB
  • Single transaction timeout: configurable (default 5s)
  • API version compatibility required
  • Requires FoundationDB cluster setup

Support

For issues specific to the FoundationDB filer store:

  1. Check FoundationDB cluster status
  2. Verify configuration settings
  3. Review SeaweedFS logs with verbose output
  4. Test with minimal reproduction case

For FoundationDB-specific issues, consult the FoundationDB documentation.