2026년, 임베디드 시스템 엔지니어로서 우리가 마주하는 가장 흥미로운 도전 중 하나는 바로 엣지 디바이스에서 동작하는 AI Agent의 실시간 추론 데이터를 중앙 서버로 ‘얼마나 빠르고 안정적으로’ 전달하느냐 하는 것입니다. 단순한 데이터 전송을 넘어, 자율주행, 산업 자동화, 그리고 지능형 IoT 기기들은 밀리초 단위의 지연도 용납하지 않는 초저지연 통신을 요구하고 있습니다. 🚀
오늘은 LWIP(Lightweight IP) 스택을 사용하는 리소스 제한적인 임베디드 시스템에서 AI Agent가 생성하는 데이터를 실시간으로, 그리고 최적의 성능으로 전송하기 위한 심층적인 네트워크 스택 최적화 삽질기를 공유하고자 합니다. 🛠️
💡 문제의 발단: 엣지 AI Agent와 지연 시간의 전쟁
최근 진행했던 프로젝트에서는 ARM Cortex-M7 기반의 마이크로컨트롤러에 경량화된 AI Agent를 포팅했습니다. 이 Agent는 카메라 이미지로부터 특정 객체를 감지하고, 그 결과를 메타데이터(예: 객체 타입, 위치, 신뢰도) 형태로 JSON 메시지를 생성하여 실시간으로 중앙 관제 서버에 전송하는 역할을 담당했습니다.
초기 구현 단계에서, 표준 LWIP 설정과 TCP/IP 통신을 사용했을 때 다음과 같은 문제에 직면했습니다.
- 불규칙한 전송 지연 (Jitter): AI 추론 결과가 발생하는 시점은 비교적 일정했지만, 네트워크를 통해 서버에 도달하는 시간은 예상보다 불규칙했습니다. 특정 순간에는 수십 ms까지 지연되는 경우도 발생했습니다.
- 작은 패킷 처리 오버헤드: AI Agent가 생성하는 JSON 메시지는 평균 200~500바이트로 비교적 작았습니다. 작은 크기의 패킷이 빈번하게 전송되면서 TCP의 Nagle 알고리즘이나 지연된 ACK(Delayed ACK)와 같은 메커니즘이 오히려 네트워크 효율을 저하시키고 지연 시간을 늘리는 원인이 되었습니다.
- 리소스 제약: 임베디드 시스템의 RAM은 한정되어 있었고, 기본 LWIP 버퍼 설정이 실시간 데이터 처리에 최적화되어 있지 않았습니다.
목표는 AI Agent가 데이터를 생성한 후 5ms 이내에 서버에 도달하는 것이었습니다. 초기 벤치마크에서는 평균 12ms, 최대 50ms의 지연이 측정되었습니다. 🔥 이 문제를 해결하기 위해 네트워크 스택 깊숙한 곳까지 파고들었습니다.
🛠️ 삽질의 시작: LWIP 스택 최적화 딥 다이브
1. LWIP TCP/UDP 버퍼 및 메모리 풀 최적화
가장 먼저 고려한 것은 LWIP의 내부 버퍼 크기 및 메모리 풀 설정입니다. 이는 lwipopts.h 파일에서 수정할 수 있습니다.
MEM_ALIGNMENT: 데이터 정렬을 위한 값. 임베디드 시스템의 아키텍처에 맞게 4 또는 8바이트로 설정하여 메모리 접근 효율을 높입니다.MEM_SIZE: LWIP가 사용할 전체 힙 메모리 크기. 충분한 크기를 할당하되, 너무 크게 설정하여 다른 태스크의 메모리를 잠식하지 않도록 주의합니다.TCP_MSS(Maximum Segment Size): TCP 세그먼트의 최대 크기. 네트워크 경로의 MTU(Maximum Transmission Unit)를 고려하여 설정합니다. 이더넷의 경우 일반적으로 1460바이트 (1500 - 20(IP) - 20(TCP)).TCP_SND_BUF(Send Buffer Size): 송신 버퍼 크기. 충분히 크게 설정하여 AI Agent가 생성하는 버스트성 데이터를 한 번에 담아 보낼 수 있도록 합니다. 너무 작으면 TCP Flow Control에 의해 송신이 지연될 수 있습니다.TCP_WND(Receive Window Size): 수신 윈도우 크기. 수신 측이 수용할 수 있는 데이터 양을 결정하며, 송신 측의 전송 속도에 영향을 미칩니다. 송신 버퍼와 유사하게 설정하는 것이 일반적입니다.PBUF_POOL_SIZE&PBUF_POOL_BUFSIZE:pbuf는 LWIP의 핵심 데이터 구조로, 패킷 데이터를 저장합니다.PBUF_POOL_SIZE는pbuf풀의 개수,PBUF_POOL_BUFSIZE는 각pbuf의 크기입니다. 작은 패킷이 많다면PBUF_POOL_SIZE를 늘리고,PBUF_POOL_BUFSIZE는TCP_MSS보다 약간 크게 설정하는 것이 좋습니다.
// lwipopts.h
#define MEM_ALIGNMENT 4
#define MEM_SIZE (16 * 1024) // 16KB
#define TCP_MSS 1460
#define TCP_SND_BUF (4 * TCP_MSS) // 4x MSS
#define TCP_WND (4 * TCP_MSS)
#define PBUF_POOL_SIZE 16 // pbuf 개수 증가
#define PBUF_POOL_BUFSIZE (TCP_MSS + 40) // MSS + IP/TCP 헤더 여유분
TCP_SND_BUF와 TCP_WND를 충분히 확보함으로써, AI Agent가 잠시 빠르게 데이터를 생성하더라도 네트워크 스택이 이를 원활하게 처리할 수 있는 여유를 마련했습니다.
2. Nagle’s Algorithm 및 Delayed ACK 비활성화
앞서 언급했듯이, 작은 패킷이 빈번하게 발생하는 시나리오에서는 Nagle’s Algorithm과 Delayed ACK가 오히려 지연 시간을 유발합니다.
- Nagle’s Algorithm: 작은 TCP 세그먼트들이 네트워크를 불필요하게 많이 차지하는 것을 방지하기 위해, 보내지 않은 데이터가 있을 때 작은 데이터를 모아 한 번에 전송합니다. 이는 대역폭 효율성을 높이지만, 실시간성이 중요한 애플리케이션에서는 지연의 원인이 됩니다.
TCP_NODELAY옵션을 사용하여 비활성화할 수 있습니다. - Delayed ACK: 수신된 데이터에 대한 ACK를 즉시 보내지 않고, 추가적으로 보낼 데이터가 있거나 일정 시간(보통 200ms)이 경과할 때까지 기다렸다가 함께 보내는 방식입니다. 이는 ACK 패킷 수를 줄여 네트워크 부하를 경감하지만, 송신 측의 Flow Control을 지연시켜 전체적인 왕복 시간을 늘릴 수 있습니다. LWIP에서는
TCP_NO_DELAY_ACK나LWIP_TCP_SACK_OUT등의 설정으로 제어할 수 있습니다.
// 소켓 생성 후 Nagle's Algorithm 비활성화
int sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock < 0) {
// 에러 처리
}
int optval = 1;
// TCP_NODELAY를 1로 설정하여 Nagle's Algorithm 비활성화
if (lwip_setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, &optval, sizeof(optval)) < 0) {
// 에러 처리
}
// 필요하다면 SO_LINGER 설정 (선택적)
// struct linger {
// int l_onoff; // 0 = off, nonzero = on
// int l_linger; // linger time in seconds
// };
// struct linger l_opt = {1, 0}; // 즉시 닫고 버퍼 데이터를 버림
// lwip_setsockopt(sock, SOL_SOCKET, SO_LINGER, &l_opt, sizeof(l_opt));
// 데이터 전송
lwip_write(sock, json_data_buffer, json_data_len);
TCP_NODELAY를 적용한 결과, 작은 JSON 메시지들이 훨씬 빠르게 서버에 도달하는 것을 확인할 수 있었습니다.
3. RTOS 태스크 스케줄링 및 우선순위 조정
LWIP는 보통 별도의 태스크(예: tcpip_thread)에서 동작합니다. AI Agent 태스크와 네트워크 스택 태스크의 우선순위 설정은 전체 시스템의 실시간 성능에 지대한 영향을 미칩니다.
- 네트워크 태스크 우선순위 높이기: AI Agent가 데이터를 생성하면, 이 데이터를 처리하고 전송하는 네트워크 태스크가 지체 없이 실행될 수 있도록 AI Agent 태스크와 동등하거나 약간 더 높은 우선순위를 부여했습니다.
- AI Agent 태스크와 네트워크 태스크 간 동기화: AI Agent가 데이터 생성을 완료하면, 세마포어(Semaphore)나 메시지 큐(Message Queue)를 통해 네트워크 태스크에 즉시 알리도록 구현하여 폴링(Polling)으로 인한 지연을 없앴습니다.
// FreeRTOS 예시
// 네트워크 태스크 생성 (lwIP 내부에서 보통 생성됨)
// xTaskCreate(tcpip_thread, "lwIP_Task", configMINIMAL_STACK_SIZE * 3, NULL, osPriorityNormal + 1, &xLwipTaskHandle);
// AI Agent 데이터 송신 태스크
void ai_agent_send_task(void *pvParameters) {
// ... AI 추론 로직 ...
while(1) {
// AI Agent가 데이터 생성
generate_ai_data(&data_buffer, &data_len);
// 네트워크 소켓으로 데이터 전송
lwip_write(sock, data_buffer, data_len);
// 데이터 전송 후 다음 추론까지 대기 또는 수면
vTaskDelay(pdMS_TO_TICKS(100)); // 예시
}
}
// AI Agent 송신 태스크 우선순위를 네트워크 태스크와 유사하게 설정
// xTaskCreate(ai_agent_send_task, "AI_Send_Task", configMINIMAL_STACK_SIZE * 4, NULL, osPriorityNormal + 2, &xAISendTaskHandle);
적절한 태스크 우선순위 설정은 OS 스케줄러가 네트워크 트래픽을 지연 없이 처리하도록 보장하여 전체적인 지연 시간을 줄이는 데 결정적인 역할을 했습니다.
📊 벤치마크 결과: 삽질은 배신하지 않는다!
위와 같은 최적화 과정을 거친 후, 다시 벤치마크를 수행했습니다.
| 측정 항목 | 최적화 전 (평균/최대) | 최적화 후 (평균/최대) | 개선율 |
|---|---|---|---|
| 데이터 전송 지연 | 12ms / 50ms | 2.5ms / 6ms | 80% / 88% |
| CPU 사용률 | 15% | 18% | 소폭 증가 |
| RAM 사용률 | 3MB | 3.5MB | 소폭 증가 |
(측정 환경: STM32H7, FreeRTOS, LWIP 2.1.2, 100Mbps Ethernet)
지연 시간이 평균 2.5ms, 최대 6ms 수준으로 현저히 감소하여 목표치인 5ms 이내를 만족하는 것을 확인할 수 있었습니다! 🚀 CPU 및 RAM 사용률이 소폭 증가했지만, 이는 실시간성 확보를 위한 합리적인 트레이드오프였습니다.
맺음말: 엣지 AI 시대의 네트워크 엔지니어링
이번 삽질을 통해 엣지 AI Agent의 잠재력을 최대한 발휘하기 위해서는 AI 모델 최적화뿐만 아니라, 그 결과를 전달하는 네트워크 스택에 대한 깊은 이해와 튜닝이 필수적임을 다시 한번 깨달았습니다. 💡 특히 리소스 제약이 있는 임베디드 환경에서는 lwipopts.h의 매개변수 하나하나가 시스템 성능에 지대한 영향을 미칠 수 있습니다.
복잡한 네트워크 이론을 넘어, 실제 필드에서 발생하는 지연 문제에 직접 부딪히고 해결해나가면서 임베디드 네트워크 엔지니어로서의 깊이를 더할 수 있었습니다. 여러분도 엣지 AI 프로젝트에서 비슷한 네트워크 성능 문제에 직면한다면, 오늘 제가 공유한 팁들이 작은 실마리가 되기를 바랍니다. 다음 삽질도 기대해주세요! 🔥
DevBJ | No Bio, Just Log 기술 삽질로그