아니, 그러니까 왜 또 수학이죠?
솔직히 말하자면 나도 수학이 정말 싫다. 어렵기만 더럽게 어렵고 실효성 없는 것도 꽤 되었다. 특히나 나는 개발을 배우기 전을 생각해보면 심각한 수포자였다. 이런 상황에서 복잡한 미분이나 적분을 사용하려고 하면 무조건 일이 터진다. 얼마 전에도 개인적으로 개발하던 티 타이머에서 용출에 미분을 살짝 넣어보려고 했는데, 수학 실력의 부족으로 향만 입히는 수준인데도 휴리스틱 알고리즘보다 성능이 떨어져 버렸다. 아니, 그러니까 내가 말하고자 하는 것이 좀 옆길로 샜지만, 이건 수학을 위해서 수학을 하는 별로 실용적이지 않은 태도는 아니다. 직교라틴방진을 이용하면 보통 rwlock을 쓸지 뮤텍스를 쓸지, 아토믹을 쓸지 골치아파하면서 락을 설계할 수 있는 방법을 조금이나마 생각할 수 있다. 물론 이러한 방식은 가독성에 문제가 있으니 실무에서 말단 개발자가 함부로 썼다간 평판이 나빠질 수도 있으니까 되도록 자기 SSD에 넣어 두고 실무에선 그냥 락을 쓰도록 하자.
사실 그래서 결론만 따지고 보면 실무에서 기피하는 “아주 빠른데 가독성이 나쁜 코드”이다. 단박에 보자마자 이해는 안 되니까…
이 성질의 최초 발견 및 증명자는 최석정이며 명명한 사람은 레온하르트 오일러이다. 최석정도 오일러도 정말 학문에 대한 열정에 경의를 표하지 않을 수 없다.
그렇지만 무엇이 무엇을 호출하는지 격자 형태로 만들고 선을 그어 보면, 정말 우아한 모양이다. 한국만이 아니고 서양의 수학자들도 이 때의 증명은 대체로 아름답다는 감각이 있으니까, 여러모로 참고하면 재미가 있다.
락 없이 어떻게 겹치지 않고 자원을 쓰나요?
여기서 선으로 이어진 것들이 서로 함께 쓰이는 자원들이다. 어떤 자원도 중복으로 쓰이지 않기 때문에 구조적으로 락이 필요없다. 고성능 네트워크 래퍼 등을 작성하고 싶다면 쉬엄쉬엄 공부해 보는 편이 좋다.

정말 깔끔하다. 물론 그로부터 얼마 지나지 않아 서양의 수학자가 이것을 모른 채로 재발견했고, 최석정은 이름짓지 않은 이 방진에 이름을 부여해 줬다지만, 이런 게 국뽕이 아니면 다른 게 국뽕인가. 이름을 붙인 수학자도, 이것을 처음 발견한 최석정도 대단히 열정적인 주체이다.
비유하자면, 일반적인 라틴 행렬은 “서로 다른 요리사가 조리하게 하는 것”이다. 요리사들은 밥을 지어야 한다. 어느 집의 부모님인 철수, 영희, 민수는 셰어하우스에 살고, 집의 밥솥은 가마솥, 냄비, 전기밥솥이 있다. 각 밥솥은 하나씩만 있어서 어떤 사람은 꼭 가마솥이나 냄비를 쓰게 되는 상황이다. 이럴 때 철수도 전기밥솥이 배정되고 영희에게도 전기밥솥이 배정되면 철수나 영희 중 한 사람은 집밥을 하지 못한다. 결국 모두가 밥을 하기 위해선 누군가는 냄비나 가마솥을 써야 한다.
이러한 문제를 해결하기 위해 “서로 다른 요리사가 서로 다른 밥솥을 쓰게 하는 것”이 직교 라틴행렬이며, 최석정이란 멋진 수학자는 현대어로 번역하자면 “두 라틴행렬의 직교”라고 서술했다. 이것을 수학자가 아닌 현대 공학자인 우리 입장에서 보면 “서로 다른 호스트가 서로 다른 클라이언트에만 요청하는 것”같은 시각을 가질 수 있다.
아무튼, 이런 성질을 잘 활용하면 락 없이 설계할 수 있다는 것은 더 말할 필요도 없이 눈에 들어온다. 꿩 먹고 알 먹고, 누이 좋고 매부 좋은 것 아닌가?
게다가, 과거의 수학 연산 도구는 계산기가 아닌 판이나 표 형태가 많았는데, 과거 우리나라에선 산판을 썼다. 산판이라고 하면 뭐낙 판이 알아서 계산을 해 줄 것처럼 보이지만 실상은 그냥 2차원 배열로도 퉁칠 수 있는 그냥 판이다.
그렇다면 최석정이 발견한 규칙, 그리고 2차원 배열, 몇 가지 상태 저장을 위한 코드를 짜면 된다.
이것을 일단 코드로 짜서 성능을 재 보도록 하자.
코드와 성능
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <pthread.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <time.h>
/**
* SOURCE: Choi Seok-jeong's Proof Method of Orthogonal Latin Squares (1700ca).
* * ARCHITECTURE: Lock-Free Parallel Network Ingress (Generic Implementation)
* * PRINCIPLE: Spatial Isolation via Orthogonality.
* * Traditional parallel servers rely on Mutex Locks or Atomic operations to
* manage shared buffers, which inevitably causes pipeline stalls and cache
* contention. This implementation uses Choi's Latin Square to ensure that
* each worker thread possesses a mathematically guaranteed private path
* within the shared memory lattice.
*/
#define PORT 8080
#define CLUSTER_SIZE 4 // Assigned based on CPU core count (2^n)
#define MASK (CLUSTER_SIZE - 1)
#define SLOT_SIZE 1024
typedef struct {
char data[SLOT_SIZE];
uint64_t timestamp;
} LogSlot;
/* The 'Sanpan' (Counting Board) - Shared Buffer Lattice */
LogSlot shared_buffer[CLUSTER_SIZE][CLUSTER_SIZE];
uint64_t total_packets = 0;
void* network_worker(void* arg) {
uintptr_t tid = (uintptr_t)arg;
int server_fd, new_socket;
struct sockaddr_in address;
int opt = 1;
int addrlen = sizeof(address);
char buffer[SLOT_SIZE] = {0};
/* Socket Initialization */
if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) exit(1);
setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt));
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons(PORT);
if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) exit(1);
if (listen(server_fd, 10) < 0) exit(1);
while(1) {
if ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen)) < 0) continue;
ssize_t valread = read(new_socket, buffer, SLOT_SIZE);
if (valread > 0) {
/**
* [CORE LOGIC: DETERMINISTIC SLOT ISOLATION]
* By iterating through the matrix and checking (row + col) % size == tid,
* each thread identifies its exclusive slots.
* This avoids "False Sharing" by interleaving memory access patterns.
*/
for (uint32_t r = 0; r < CLUSTER_SIZE; r++) {
for (uint32_t c = 0; c < CLUSTER_SIZE; c++) {
if (((r + c) & MASK) == (uint32_t)tid) {
/* Thread-safe write without any hardware locks */
memcpy(shared_buffer[r][c].data, buffer, SLOT_SIZE);
shared_buffer[r][c].timestamp = (uint64_t)time(NULL);
__sync_fetch_and_add(&total_packets, 1);
}
}
}
}
close(new_socket);
}
return NULL;
}
int main() {
pthread_t threads[CLUSTER_SIZE];
printf("[Core-Sys] Initializing Lock-free Parallel Server on Port %d\n", PORT);
printf("[Core-Sys] Logic: Choi Seok-jeong's Matrix Orthogonality\n");
for (uintptr_t i = 0; i < CLUSTER_SIZE; i++) {
pthread_create(&threads[i], NULL, network_worker, (void*)i);
}
/* Throughput Monitoring Loop */
while(1) {
sleep(1);
printf("Throughput: %lu pkts/sec | Write Strategy: Deterministic Lattice\n", total_packets);
total_packets = 0;
}
return 0;
}
부하 스크립트는 이러하다.
wrk -t4 -c100 -d10s http://localhost:8080/
[Core-Sys] Initializing Lock-free Parallel Server on Port 8080
[Core-Sys] Logic: Choi Seok-jeong's Matrix Orthogonality
Throughput: 0 pkts/sec | Write Strategy: Deterministic Lattice
Throughput: 0 pkts/sec | Write Strategy: Deterministic Lattice
Throughput: 168060 pkts/sec | Write Strategy: Deterministic Lattice
Throughput: 211364 pkts/sec | Write Strategy: Deterministic Lattice
Throughput: 205504 pkts/sec | Write Strategy: Deterministic Lattice
Throughput: 211124 pkts/sec | Write Strategy: Deterministic Lattice
Throughput: 207054 pkts/sec | Write Strategy: Deterministic Lattice
Throughput: 191524 pkts/sec | Write Strategy: Deterministic Lattice
Throughput: 190728 pkts/sec | Write Strategy: Deterministic Lattice
별 것도 없이 빈약한 서버에서 무난하게 1초 당 16만-20만 개의 패킷을 처리했다. 만약 이러한 것이 고성능 서버 곳곳에 박힌다고 생각해 보자. 그렇다면 RWLock 기반에서 TTL 캐시 벤치마크를 돌릴 때 자주 걸리는 병목인 12M-13M을 돌파할 수도 있는 셈이다. 실제로 이 블로그에 공개적으로 홍보할 수준은 아니지만, 개인적으로 작성했을 때는 안정적으로 16M-17M으로 수렴하는 꽤 상용 라이브러리와 가까운 성능을 보였다.
결론
이것은 실무에서 썼다간 정말로 혼날 수 있다. 그래서 자세하게 하나하나 원리를 설명하는 것 자체가 위험히다. 그러나 미식가가 밥을 살기 위해서만 먹지 않듯이, 개발을 특별히 즐거워하는 사람이라면 더 이상 개발이 재미없어지기 전에 의무감으로 하는 개발이 아닌 즐거운 개발을 해 봐야 한다. 그러니까, 실컷 재미난 것들을 교과서에서 가져와서 공부하고, 제미나이를 통해 읽어 볼 만한 이론을 찾아 보자. 재미없는 개발만 하다가 죽을 수는 없는 노릇이다.
즐겨라, 그리고 이건 한 번 쯤 써 볼 만하다. 이 블로그를 우연히 스쳐가는 익명의 모두가 재밌는 개발을 함께 이어나갈 수 있는 세대가 되었으면 좋겠다.