7. 멀티스레드 안전성
7.1 락 전략
컴포넌트 | 락 타입 | 읽기 | 쓰기 |
|---|---|---|---|
UBMS::CacheManager | shared_mutex | shared_lock (동시 다중) | unique_lock (배타적) |
UBMS::Chain | mutex | - | addBlock/rollBack 직렬화 |
UBMS::BlockCurrent | atomic shared_ptr + mutex | atomic load (락 없음) | mutex 보호 |
UBMS::BlockMetaFullCache | atomic shared_ptr + mutex | atomic load (락 없음) | COW 교체 |
UBMS::BlockPool | atomic shared_ptr + mutex | atomic load (락 없음) | COW 교체 |
UBMS::VolatileCache | shared_mutex | shared_lock | unique_lock |
UBMS::ConfirmedCache | shared_mutex | shared_lock | unique_lock |
UBMS::TxCurrent | shared_mutex | shared_lock | unique_lock |
DB | mutex | 모든 연산 직렬화 | 모든 연산 직렬화 |
7.2 TOCTOU 회피 설계
취약한 설계 (분리 호출):
Thread A: isSpentKey(key) → false (미소비)
--- 이 사이에 Thread B가 addBlock()으로 key 소비 ---
Thread A: addTx(key) → 이미 소비된 UTXO로 TX 생성 (이중 지불!)UBMS의 해결: 원자적 검사 함수
checkDoubleSpendWithPool(address, key):
// CacheManager의 shared_lock이 이미 걸린 상태
// 1단계: 확정 원장 소비 확인
// TX풀 잔돈이면 통과, 아니면 거부
// 2단계: TX풀 소비 확인 (같은 주소)
// 이미 소비 예약되었으면 거부
// 3단계: 전역 크로스-주소 이중소비 확인
// 다른 주소의 TX가 이미 사용했으면 거부
// 전부 통과하면 미소비 확인 완료원자적 검사 함수 전체 목록:
함수 | 검사 범위 | 용도 |
|---|---|---|
| 확정 + TX풀 + 전역 | 이중 지불 다단 검사 |
| 확정 + TX풀 잔돈 | 미소비 확인 |
| 확정 + TX풀 | UTXO 통합 조회 |
| DB + volatile + TX풀 | 입력 수집 |
| DB + volatile + TX풀 | 사용 가능 잔액 |
| DB + volatile + TX풀 | 스테이크 통합 조회 |
락 계층 설계 (교착 방지):
UBMS::Chain::m_mutex (std::mutex, 배타적) ← addBlock/rollBack 직렬화
└→ UBMS::CacheManager::m_mutex (shared_mutex) ← 읽기/쓰기 분리
└→ UBMS::VolatileCache::m_mutex ← 내부 보호
└→ UBMS::ConfirmedCache::m_mutex ← 내부 보호
└→ UBMS::TxCurrent::m_mutex ← 내부 보호
규칙: 외부 → 내부 순서로만 취득, 역순 불가 (교착 원천 차단)7.3 락프리 읽기 패턴
현재 블록, 블록 메타 전체캐시, 블록풀은 읽기 경로에서 잠금 없이 현재 상태의 스냅샷을 즉시 가져올 수 있도록 설계됨. 쓰기는 단일 경로로 제한하여 데이터 경합을 원천 차단하면서도, 읽기 쪽에 대기나 차단이 발생하지 않음.
7.4 TX풀 이중 소비 방지 — 4단 충돌 검사
isConflict_(reserved, tx):
검사 1: TX 내부 입력 중복
같은 TX 안에서 동일 UTXO를 두 번 참조하면 거부
검사 2: 주소별 예약 풀 이중 소비
같은 주소의 이전 TX가 이미 소비 예약한 UTXO면 거부
검사 3: 전역 크로스-주소 이중 소비
다른 주소의 TX가 이미 사용한 UTXO면 거부
→ m_globalSpentKeys (unordered_set, O(1) 조회)
검사 4: TX 내부 출력 인덱스 중복
같은 출력 인덱스를 두 번 생성하면 거부TX풀 예약 풀 (UtxoPool) 동작:
applyReserveAdd_(tx):
output 중 자기 주소로 돌아오는 잔돈 → available에 추가
input → spent_keys + m_globalSpentKeys에 등록
효과:
A가 5 UBMS UTXO로 3 UBMS 전송 시
→ spent_keys: {원본 5 UBMS key}
→ available: {잔돈 2 UBMS key} ← 다음 TX에서 즉시 사용 가능블록 확정 후 TX풀 정리 (3단계):
1) syncCurrentTx(confirmedTxList) → txid로 확정된 TX 제거
2) removeConflictingCurrentTx(spentKeys) → 충돌하는 미확정 TX 제거
3) cleanupCurrentTxByHeight(minHeight) → 너무 오래된 TX 제거8. 성능 분석
이하 O() 표기는 시간 복잡도(Time Complexity)를 나타낸다. O(1)은 데이터 양과 무관하게 일정한 시간, O(N)은 데이터 양에 비례하는 시간을 의미.
8.1 조회 성능
연산 | 경로 | 비고 |
|---|---|---|
잔액 조회 | DB(owner 인덱스) + volatile 필터링 | O(UTXO 개수) |
TX 존재 확인 | volatile(10블록) -> DB | 최신 TX는 메모리에서 즉시 |
UTXO 소비 확인 | volatile spentKeys -> DB | O(10) 최대 |
최근 TX 조회 | volatile 역순 -> DB | txid 타임스탬프 정렬 |
referral 조회 | ConfirmedCache 메모리 | O(1) 해시맵 |
8.2 쓰기 성능
연산 | 비용 | 비고 |
|---|---|---|
블록 추가 | O(TX수 + UTXO수) | volatile pushBack |
DB 커밋 | O(차분 크기) | 10블록마다 한 번, 배치 쓰기 |
롤백 | O(1) | volatile popBack, DB 건드리지 않음 |
8.3 I/O 성능 — 실측 벤치마크
측정 환경: AMD Ryzen 7 2700X (2018년 출시), 싱글스레드 (1 core), 3초 측정, localhost
8년 전 CPU의 단일 코어만으로 측정한 수치. 동일 세대 Ryzen 9 3900X에서는 싱글코어 기준 30% 이상 향상 확인
처리량 — 텍스트 프로토콜
메시지 크기 | 클라이언트 | 처리량 (msg/s) | 송신 | 수신 |
|---|---|---|---|---|
32B | 1 | 1,195,757 | 3,587,433 | 3,587,271 |
32B | 10 | 10,132,692 | 60,851,192 | 30,398,077 |
256B | 1 | 883,132 | 2,649,581 | 2,649,396 |
256B | 10 | 4,284,165 | 27,230,564 | 12,852,496 |
1KB | 1 | 801,805 | 2,405,416 | 2,405,416 |
1KB | 10 | 1,628,376 | 8,246,630 | 4,885,129 |
4KB | 1 | 320,268 | 1,299,084 | 960,806 |
4KB | 10 | 205,166 | 1,706,216 | 615,498 |
처리량 — 바이너리 프로토콜
메시지 크기 | 클라이언트 | 처리량 (msg/s) | 송신 | 수신 |
|---|---|---|---|---|
32B | 1 | 1,431,783 | 4,295,670 | 4,295,349 |
32B | 10 | 9,755,885 | 74,314,915 | 29,267,656 |
256B | 1 | 1,231,194 | 3,693,694 | 3,693,584 |
256B | 10 | 5,260,272 | 31,169,940 | 15,780,817 |
1KB | 1 | 627,774 | 1,883,322 | 1,883,322 |
1KB | 10 | 1,709,745 | 8,647,578 | 5,129,235 |
4KB | 1 | 509,816 | 1,687,959 | 1,529,449 |
4KB | 10 | 447,800 | 2,172,084 | 1,343,400 |
레이턴시 (64B echo, 1,000회 반복)
지표 | 값 |
|---|---|
평균 | 32.0 us |
p50 | 32.0 us |
p99 | 38.1 us |
min | 28.1 us |
max | 40.6 us |
연결 속도
지표 | 값 |
|---|---|
연결 속도 | 12,419 conn/s |
총 연결 | 1,000건 / 0.08초 |
실측 수치 요약
위 벤치마크는 libubmsio_uring.so 자체 실측값이며, 타 라이브러리와의 동일 조건 비교 측정은 수행하지 않았다. 다만 싱글스레드에서 1KB 메시지 170만 msg/s, 32B 메시지 1,013만 msg/s, p99 레이턴시 38us는 io_uring의 커널 수준 최적화를 실용적으로 잘 활용한 결과이며, 공개된 epoll 기반 라이브러리(libevent, libuv, Boost.Asio 등)의 벤치마크 자료와 대조해도 상당히 높은 수치.
주요 실측 수치:
- 바이너리 1KB, 10클라이언트: 1,709,745 msg/s (싱글스레드)
- 텍스트 1KB, 10클라이언트: 1,628,376 msg/s (싱글스레드)
- 32B 소형 메시지, 10클라이언트: 10,132,692 msg/s
- p99 레이턴시: 38.1us (64B echo RTT)
- 연결 속도: 12,419 conn/s (1,000건/0.08초)
- 멀티코어 활용 시 추가 향상 확인 (사무실 장비에서 20%+ 향상)io_uring 자체의 커널 수준 최적화(SQE 배치 제출, 커널 측 폴링, 시스템콜 감소)가 처리량의 주된 원인이며, libubmsio_uring.so는 이 위에 multishot, zero-copy, provided buffers, backpressure를 조합하여 블록체인 노드에 적합한 네트워크 계층을 구성.
벤치마크 바이너리(ubmsio_uring_benchmark)가 빌드 산출물에 포함되어 있으므로, 위 수치는 누구든 동일 환경에서 직접 재현할 수 있다.
8.4 메모리 사용 추정
VolatileCache ~50MB (10블록 x 5MB/블록)
ConfirmedCache ~10MB (referral tree + nodeInfo)
TxCurrent ~100MB (pending TX + 예약 풀)
BlockPool ~100MB (미확정 분기)
합계 ~260MB (정상 상태)
DB 수 GB ~ 수백 GB (블록 데이터)8.5 네트워크 최적화
메시지 압축 zlib (평문 메시지)
메시지 중복 제거 메시지 ID 해싱 + 윈도우 기반 중복 추적
공개키 캐싱 LRU 방식 (PEM 파싱 비용 제거)
배치 전송 sendmsg 일괄 전송

🎉 100% 당첨! UBMS 무료 코인 참여 이벤트 🎉
간단한 참여 완료 시 이벤트 보상을 100% 전원 지급해 드립니다! UBMS 커뮤니티만을 위한 특별 혜택을 지금 바로 확인하세요. 🎁
이벤트 참여하고 무료코인 받기 🎁