Dev Docs Performance Tuning

Performance Tuning

Audited against Netatalk d63155e13 on 2026-10-10. This describes the current source checkout; older releases may differ.

Scope and measurement

This page documents performance controls implemented in the current Netatalk source. Results depend on clients, concurrency, filesystem, and platform. Link speed alone does not determine buffer sizes, cache budgets, or database choice.

The earlier page mixed obsolete Netatalk defaults with community kernel, macOS, and ZFS recipes and unsupported performance estimates. Those recipes are not maintained here as source-code facts. Use the relevant operating system’s documentation for its tuning controls, and measure the deployed workload before changing defaults.

Current controls

Defaults come from afp_config_parse() in libatalk/util/netatalk_conf.c; constants are in include/atalk/globals.h, include/atalk/dsi.h, and include/atalk/netatalk_conf.h.

[Global] option Default What it controls
server quantum 1,048,576 bytes (1 MiB) Advertised maximum DSI write-data payload
dsireadbuf 32 Incoming DSI readahead allocation multiplier
tcpsndbuf, tcprcvbuf 0 Leave socket buffers to OS; nonzero requests SO_SNDBUF/SO_RCVBUF
dircache size 65,536 Live directory/file cache capacity per worker
dircache mode arc Adaptive replacement; lru also supported
dircache validation freq 100 Sample filesystem validation every Nth eligible lookup
dircache rfork budget 32,768 KiB Resource-fork content budget per worker; 0 disables
dircache rfork maxsize 1,024 KiB Largest fork admitted to the content cache
use sendfile yes Permit compiled sendfile paths for eligible reads
recvfile no Permit compiled splice/recvfile path for eligible writes
splice size 1,048,576 bytes Requested recvfile pipe capacity
strict locking no Temporary POSIX locks around data I/O
spotlight results limit 10,000 Search result/candidate bound; 0 is unlimited

multi protocol = yes changes default validation to 1 and default strict locking to yes, and excludes that volume from resource-fork caching unless the budget was explicitly set. These effects must be accounted for when comparing configurations.

DSI buffering

server quantum is a protocol limit on write data, not a filesystem block size or a universal maximum reply size. The configured range is 32,000 bytes to 256 MiB. libatalk/dsi/dsi_tcp.c can align a session quantum to TCP MSS within bounded growth, accounting for DSI/AFP write-frame overhead.

dsireadbuf allocates incoming network readahead in libatalk/dsi/dsi_init.c and libatalk/dsi/dsi_tcp.c. The configured multiplier is bounded to 6–1,024, then reduced if necessary so the configured product fits NETATALK_READBUF_LIMIT (1 GiB) and address-space limits. This product limit can reduce the multiplier below 6 for a large quantum.

Increasing readahead can help when dsi_peek() repeatedly fills its buffer, but consumes memory per session. It does not prefetch file data from disk. There is no code requirement that a 10 GbE connection allocate at least 128 MiB, nor that a ZFS record size match the quantum.

afp_over_dsi() applies explicit socket-buffer requests and logs configured and actual values using getsockopt(). The kernel can adjust or reject requests. Inspect those values and observed transfer behavior instead of assuming a requested size equals allocated kernel memory.

Memory accounting

A useful planning calculation for the default receive-side user buffers is:

command buffer:       1 MiB
DSI readahead buffer: 32 × 1 MiB = 32 MiB
subtotal:            approximately 33 MiB per DSI worker

This is an allocation estimate before session alignment and other overhead. It is not total RSS. Add reply buffers, the directory cache and ARC ghosts, names/paths, AD metadata, resource-fork buffers actually populated, database state, open forks, thread stacks, and kernel socket/pipe memory. Heap allocations are not all necessarily resident at once.

ARC retains full struct dir ghost entries, so memory use can exceed a calculation based only on the live-entry count. The resource-fork budget is a separate limit, not memory automatically consumed at startup. Multiply measured process memory by the number of workers and include disconnected/sleeping sessions retained for reconnect.

Cache and validation tradeoffs

Use Caching Architecture for algorithms, validity checks, and diagnostics. A lower validation frequency value performs more stat calls; a higher value allows longer stale windows for changes outside AFP. Internal ordinary hints are best effort and do not replace validation.

The 16-slot parent-directory fd cache accelerates validation with relative stat operations; PFD_REGROUND_INTERVAL is 256 uses. It has its own convergence behavior under external parent-directory replacement. See etc/afpd/pfd_cache.c and test/afpd/subtests_pfd.c.

Cache hit rates depend on workload and warm-up. A universal 90% target or claimed 80–95% of line-rate throughput is not an implementation guarantee.

Sendfile and recvfile

Sendfile depends on the platform, selected fork storage, and build feature detection. It is not synonymous with all outgoing data avoiding copies. Implementation is in libatalk/adouble/ad_sendfile.c and libatalk/dsi/dsi_read.c.

Recvfile depends on splice support and recvfile = yes. libatalk/adouble/ad_recvfile.c lazily creates a per-session pipe, requests capacity where supported, and records the capacity the kernel grants. splice size is bounded from 4 KiB to 8 MiB and rounded to a page boundary by the parser. A denied pipe resize falls back to usable capacity; it does not justify documenting the requested value as guaranteed.

Failure paths maintain DSI frame alignment and map file-size/write errors appropriately. Relevant regression coverage is in test/afpd/subtests_recvfile.c. Measure CPU cost and transfer rate with the option off and on for eligible writes.

Metadata, database, and search workload

SQLite is mandatory/default and uses WAL with a 3-second busy timeout. MySQL is optional. Neither is universally fastest; distinguish query latency, directory traversal, write contention, and cache warm-up. There are no BDB cache/checkpoint controls in this checkout.

Spotlight backend choice changes what is indexed and where work runs. CNID name searches, Localsearch’s external index, and Xapian’s embedded index are different workloads. spotlight results limit applies across backends. See CNID Database and Data Flow.

Repeatable evaluation

  1. Record source revision, build options, configuration, platform, storage format, client version, and session count.
  2. Separate bulk transfer, small-file creation, directory enumeration, metadata lookup, and search workloads.
  3. Compare cold and warm runs. Record throughput, latency, per-worker memory, CPU, I/O, contention, and cache statistics.
  4. Change one relevant option, repeat the same workload, and check correctness as well as speed.
  5. Include external modifications when evaluating multi protocol volumes and retained sessions when evaluating memory.

Build the bundled tools with -Dwith-testsuite=true. test/testsuite/lantest.c measures AFP workloads; test/testsuite/speedtest.c and test/testsuite/speedtest_local_vfs.c provide transfer/local VFS comparisons. Their current command-line usage and test/README.md are the reference for invocation. Diagnostics are described in Troubleshooting.

Footnotes

Generated from the Netatalk GitHub Wiki

Last updated 2026-10-21