| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | |||||
| 3 | 4 | 5 | 6 | 7 | 8 | 9 |
| 10 | 11 | 12 | 13 | 14 | 15 | 16 |
| 17 | 18 | 19 | 20 | 21 | 22 | 23 |
| 24 | 25 | 26 | 27 | 28 | 29 | 30 |
| 31 |
- Kotlin
- Abstract Factory
- compose
- Observer Pattern
- builderPattern
- 추상팩토리패턴
- Design Pattern
- 옵저버 패턴
- 함수형프로그래밍
- material3
- 디자인패턴 #
- ㅋㅁ
- Functional Programming
- 프로토타입 패턴
- 추상 팩토리
- 코틀린
- designPattern
- android designsystem
- kotlin multiplatform
- PrototypePattern
- 코루틴
- 디자인패턴
- 팩토리 메소드
- Coroutines
- factory method
- 빌터패턴
- 안드로이드 디자인시스템
- 코틀린멀티플랫폼
- kmp
- define
- Today
- Total
오늘도 더 나은 코드를 작성하였습니까?
개발자가 알아야 하는 숫자1 - Latency Numbers 본문

| 작업 | 1993 (Norvig) | 2009 (Jeff Dean) | 2012 (SSD 시대) | 2024 (NVMe 시대) |
| L1 캐시 참조 | 10 ns | 0.5 ns | 0.5 ns | 0.5 ns |
| 분기 예측 실패 | — | 5 ns | 5 ns | 3 ns |
| L2 캐시 참조 | — | 7 ns | 7 ns | 4 ns |
| Mutex lock/unlock | 100 ns | 25 ns | 25 ns | 17 ns |
| 메인 메모리 참조 | 200 ns | 100 ns | 100 ns | 80 ns |
| 메모리 1MB 순차 읽기 | 5,000,000 ns (5,000 μs) |
250,000 ns (250 μs) |
250,000 ns (250 μs) |
100,000 ns (100 μs) |
| 1KB Zip 압축 | — | 3,000 ns (3 μs) | 3,000 ns (3 μs) | 2,000 ns (2 μs) |
| 1Gbps로 1KB 전송 | — | 10,000 ns (10 μs) | 10,000 ns (10 μs) | 1,000 ns (1 μs, 10Gbps) |
| SSD 랜덤 읽기 (SATA) | 없음 | 없음 | 150,000 ns (150 μs) |
100,000 ns (100 μs) |
| SSD 랜덤 읽기 (NVMe) | 없음 | 없음 | 없음 | 20,000 ns (20 μs) |
| SSD 1MB 순차 읽기 | 없음 | 없음 | 1,000,000 ns (1,000 μs) |
50,000 ns (50 μs, NVMe) |
| 같은 DtatCenter내 RTT | — | 500,000 ns (500 μs) |
500,000 ns (500 μs) |
500,000 ns (500 μs) |
| HDD seek | 10,000,000 ns (10,000 μs) |
10,000,000 ns (10,000 μs) |
10,000,000 ns (10,000 μs) |
10,000,000 ns (10,000 μs) |
| HDD 1MB 순차 읽기 | 30,000,000 ns (30,000 μs) |
20,000,000 ns (20,000 μs) |
20,000,000 ns (20,000 μs) |
20,000,000 ns (20,000 μs) |
| 한국 ↔ 미국 RTT | 200,000,000 ns (200,000 μs) |
150,000,000 ns (150,000 μs) |
150,000,000 ns (150,000 μs) |
150,000,000 ns (150,000 μs) |
중요하게 봐야할 것들
- 관통하는 통찰물리 한계 기반 숫자는 변하지 않는다 (빛의 속도, HDD 회전)
- 기술 기반 숫자만 격변한다 (SSD 등장으로 디스크 500배 빨라짐)
- 병목은 사라지지 않고 옮겨간다 (디스크에서 네트워크와 메모리로)
- 정확한 수치보다 자릿수(order of magnitude) 감각이 중요하다
- 표는 항상 만든 시점의 스냅샷 — 시간이 지나면 검증이 필요하다'
자릿수 감각 정리 및 무엇이 중요할까?
| 계층 | 단위 | 비유 |
| L1 캐시 | sub-ns | 손에 들고 있음 |
| 메인 메모리 | 100 ns | 책상 위 |
| NVMe SSD | 10~100 μs | 같은 방 책장 |
| SATA SSD | 100 μs ~ 1 ms | 옆방 |
| HDD | 10 ms | 도서관 가서 찾기 |
| 같은 DataCenter 네트워크 | 500 μs | 옆 사무실 전화 |
| 대륙 간 RTT | 150 ms | 해외로 편지 |
메모리 → NVMe SSD는 약 1,000배 차이입니다. 옛날엔 메모리 → HDD가 10만 배였으니, 격차가 100배 줄어든 것.
"RAM이 곧 디스크"라는 말이 어느 정도 농담이 아닌 시대.
Android 개발 관점에서 보면, Room에 로컬캐싱하는 이유.
모바일 NVMe(UFS 3.0~4.0)도 랜덤 읽기가 수십μs 수준이라, 네트워크 한 번 다녀오는 것(수십 ms)보다 1000배 빠르다.
*"Room은 디바이스 내부 SQLite를 호출하니까 수 ms 수준(전형적으로 1~10ms, 단순 쿼리는 그보다 빠름)에 끝납니다."
이게 "로컬 캐싱이 압도적으로 유리한" 물리적 근거입니다.
| 작업 | 시간 | 의미 | 1초 기준 환산 | 체감 |
| 메인 메모리 접근 | 100 ns | 가장 빠른 "데이터 가져오기"의 기준점. 모든 비교의 출발점. | 1초 | 손가락 한 번 튕기기 |
| NVMe SSD 랜덤 읽기 | 20,000 ns (20μs) |
로컬 저장소의 현실. 모바일 UFS도 비슷한 수준. | 3분 20초 | 라면 끓이기 |
| 같은 DC 내 RTT | 500,000 ns (500μs) |
마이크로서비스, Redis, DB 같은 백엔드 통신의 기준선. | 1시간 23분 | 영화 한 편 |
| 한 프레임 (60fps) | 16,666,666 ns (16.6ms) |
UI가 안 끊기게 만들 시간 예산. | 1일 22시간 | 주말 |
| 모바일 ↔ 서버 RTT | 50,000,000 ns (50ms) |
앱 → 서버 호출의 현실. 무선이 추가되는 순간 자릿수가 바뀜. | 5일 19시간 | 휴가 일주일 |
| 대륙 간 RTT | 150,000,000 ns (150ms) |
글로벌 서비스 설계의 물리적 한계. | 17일 8시간 | 보름 넘게 |
실무에서 이게 왜 중요한가
1. 마이크로서비스 호출 한 번 = 메모리 접근 5,000번
- 서비스 A가 서비스 B를 HTTP로 호출하면, 같은 DC 안이라도 최소 500μs는 깔립니다. 메모리에서 읽는 것보다 5,000배-6000배 느리다.
- "필요 없는 마이크로서비스 분리는 성능 재앙"
2. N+1 쿼리 문제 (서버, 로컬DB)
첫번째, 1번의 쿼리 100개의 게시글 가져오기
두번째, 100번의 쿼리 100개의 게시글의 각 작성자 가져오기.
// 1번째 쿼리: 게시글 100개 가져오기
val posts = postDao.getAllPosts() // SELECT * FROM Post LIMIT 100
// 그 다음 작성자 정보를 가져온다
posts.forEach { post ->
// 2번째, 3번째, ..., 101번째 쿼리: 매번 작성자 조회
val author = userDao.getUserById(post.userId) // SELECT * FROM User WHERE id = ?
println("${post.title} by ${author.name}")
}
JOIN 쿼리가 조금 무거운 작업이라도 한 번이면 끝날 일.
서버(백앤드)
환경: 앱 서버 ↔ DB 서버가 같은 데이터센터에 있고, 둘 사이는 내부망 네트워크로 통신.
기준 시간: DB 쿼리 1번 ≈ 0.5 ms (DataCenter 내 RTT 500μs + 쿼리 처리 약간)
| 방식 | 총 쿼리 수 | 시간 | 체감 |
| N+1번 쿼리 | 1 + 100 = 101번 | 약 50 ms | 느리다고 인식 시작 |
| JOIN 1번 | 1번 | 약 0.7 ms | 즉각적 |
약 70배 더 걸림.
안드로이드 로컬 (Room)
환경: 앱 안에서 SQLite를 직접 호출. 네트워크 없음, 디바이스 내부 파일 접근만.
기준 시간: Room 단순 쿼리 1번 ≈ 50~100 μs (네트워크 없음, 디스크 접근 + 소프트웨어 스택)
| 방식 | 총 쿼리 수 | 시간 | 체감 |
| N+1 | 1 + 100 = 101번 | 약 5-10 ms | 위험 (프레임의 절반 이상) |
| JOIN(@Relation) 1번 | 1번 | 약 0.3 ms | 여유 있음 |
약 20~30배 더 걸림.
3. 캐싱 판단의 기준선
"이 데이터를 Redis에 캐싱할 가치가 있나?"의 답이 여기서 나옵니다. Redis도 결국 네트워크(같은 DC RTT)호출이라 500μs 소요된다 그래서 **단순 메모리 캐시(앱 프로세스 내 HashMap)**가 Redis보다 압도적으로 빠릅니다. 다만 여러 서버가 공유해야 한다면 Redis로 가야한다
4. 모바일 앱(android native)에서는?
Android에서 서버 API 한 번 호출하면 보통 50ms 이상 깔립니다. (무선 구간 + 인터넷 구간 + 서버 처리가 다 포함)
반면 Room은 디바이스 내부 SQLite를 호출하니까 수 ms 수준(전형적으로 1~10ms, 단순 쿼리는 그보다 빠름)에 끝납니다. 무선도, 인터넷도, 서버 부하도 없이 그냥 UFS에서 파일 읽고 객체로 매핑하면 끝이니까요. 같은 데이터를 가져오는 데 자릿수가 한 단계 다른 비용이 들어갑니다. 이게 Single Source of Truth 패턴(네트워크는 백그라운드에서 Room에 동기화, UI는 Room만 본다)이 모던 안드로이드 아키텍처의 표준이 된 정량적 근거입니다.
