Skip to main content

SwiftNIO Notes 1

SwiftNIO Notes 1

Today, I’m putting together some notes on the options you can configure when bootstrapping a server using SwiftNIO, along with their benefits, based on what I’ve been learning recently.

serverChannelOption(ChannelOptions.backlog, value: 256)

I like to think of this one simply as “the size of the waiting room outside your server’s front door.” When a user tries to connect to the server, certain low-level operations—like the TCP handshake—happen at the OS level before SwiftNIO even knows about the connection attempt. Let’s say you have a single server instance, and around 500 people try to connect simultaneously within milliseconds. The server won’t be able to process them all at once, causing connections to drop.

To prevent this, this option configures the OS to queue up to 256 completed OS-level handshakes in a backlog if SwiftNIO isn’t ready to accept them yet. When a network spike hits, this prevents connection drops from a single-server perspective by queuing as many incoming connections as allowed.

serverChannelOption(ChannelOptions.socket(SocketOptionLevel(SOL_SOCKET), SO_REUSEADDR), value: 1)

When a server boots up, it binds a socket to a specific IP address and port. If the server crashes later or you intentionally shut it down, trying to reboot it immediately often triggers an “address already in use” error.

This happens because the OS doesn’t close the network socket immediately. Even after the server process is gone, there might still be stray data packets traveling through the network. To prevent these old packets from accidentally bleeding into a brand-new connection, the OS temporarily locks down that port.

While this is a great safety feature, it means developers have to sit around for a minute waiting to restart the server during development. Setting this option to 1 (which means True) turns on the feature that bypasses this lockdown.

childChannelOption(ChannelOptions.socket(IPPROTO_TCP, TCP_NODELAY), value: 1)

As far as I know, SwiftNIO is essentially a Swift port of Netty (which was written in Java). Because of that, it inherits similar terminology. Here, a “Channel” basically refers to a connection.

From what I understand, a Server Channel is the main connection that binds to the operating system’s port and consumes OS resources. A Child Channel, on the other hand, refers to the individual connections from clients (like web or mobile apps) hitting that main server connection. So generally speaking, a single Server Channel can have nn number of Child Channels.

This particular option turns off the built-in network buffering system (Nagle’s Algorithm) for every individual child connection.

By default, operating systems try to optimize how packets (data) are sent over the internet to save bandwidth. Instead of frequently sending tiny chunks of data, the OS waits a brief moment to batch those small chunks together before firing them off. While this is great for bandwidth efficiency, it introduces latency for the end-user. Turning this option on prioritizes speed over packet bundling efficiency.

childChannelOption(ChannelOptions.maxMessagesPerRead, value: 16)

If a specific client sends a massive amount of data all at once, SwiftNIO could end up dedicating all of its resources to processing just that one client.

To prevent this hogging, even if a client fires off a massive stream of data, this option caps the processing at a maximum of 16 chunks. Once it hits that limit, the server temporarily pauses that client to serve other pending client connections. This keeps things fair across all connected clients. (When reading up on system design, I noticed there are actually quite a few algorithms dedicated to solving this exact problem).

childChannelOption(ChannelOptions.recvAllocator, value: AdaptiveRecvByteBufferAllocator())

When data arrives from a client, SwiftNIO needs to allocate RAM (a buffer) on the host OS so the Swift server code can read it.

If the server blindly allocates a fixed 64KB for every single read operation, but the user only sends a tiny 100-byte text message, it’s incredibly wasteful. Conversely, if the server only allocates 1KB but the user uploads a 5MB image file, the server has to read a tiny bit, pause, allocate more memory, and read again. This back-and-forth makes things incredibly slow.

This option solves that by dynamically allocating RAM based on the actual size of the incoming data the client is sending.

Note: These are notes I’ve put together after digging into things I wasn’t clear on and asking AI for clarification, so I can’t guarantee everything is 100% correct. I just wanted to write this out while studying to test how well I actually understand these concepts!