🏢 네이버 기술 스택 분석
네이버가 대한민국 최대 검색 포털에서 글로벌 기술 기업으로 성장한 기술 스택 심층 분석 - Java, Spring, Hadoop으로 구축한 초대규모 플랫폼
🏢 네이버 기술 스택 분석
한국 최대 검색 포털에서 글로벌 기술 플랫폼으로 진화한 네이버의 기술 스택을 심층 분석합니다.
"기술 플랫폼의 경계를 넘어" - 검색부터 AI까지 모든 것을 아우르는 기술력
📊 회사 개요
서비스 규모
- 일일 활성 사용자: 3,000만+ 명
- 월간 검색: 20억+ 건
- 서비스: 50+ 개 (검색, 쇼핑, 페이, 웹툰, 클라우드 등)
- 데이터 저장량: 100PB+
- 분당 요청: 1,000만+ 건
엔지니어링 조직
- 개발자: 3,000명+ (전체 직원 4,500+)
- 문화: "기술이 미래를 만든다"
- R&D 센터: 춘천, 대전, 일본, 베트남
- 오픈소스: 200+ 프로젝트 공개
기술적 도전과제
- 초대규모 검색: 수십억 웹 페이지 인덱싱
- 실시간 처리: 뉴스, 실시간 검색어
- AI/ML: 한국어 자연어 처리
- 글로벌 확장: 일본, 동남아 서비스
🏗️ 아키텍처 Overview
시스템 다이어그램
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Search │────►│ Query │────►│ Index │
│ Frontend │ │ Processor │ │ Cluster │
└─────────────┘ └─────────────┘ └─────────────┘
│ │ │
│ ┌─────▼─────┐ ┌─────▼─────┐
│ │ Cache │ │ Hadoop │
│ │ (Arcus) │ │ Cluster │
│ └───────────┘ └───────────┘
│ │ │
└────────────────────┤ │
┌─────▼─────┐ ┌─────▼─────┐
│ API │ │ ML │
│ Gateway │ │ Platform │
└───────────┘ └───────────┘
핵심 설계 원칙
- Scalability First: 무한 확장 가능
- High Availability: 24/7 무중단
- Data-Driven: 모든 결정은 데이터로
- Open Innovation: 오픈소스 우선
- Global Standard: 글로벌 수준 기술
기술 진화
- 1999-2005: 검색 엔진 개발
- 2006-2010: 플랫폼화, 오픈 API
- 2011-2015: 빅데이터, 클라우드
- 2016-2020: AI/ML, 글로벌 확장
- 2021-현재: 초거대 AI, 메타버스
🧩 기술 스택 상세
검색 엔진 기술
대규모 검색 시스템
// 네이버 검색 엔진 핵심 (Java)
@Component
public class NaverSearchEngine {
private final IndexManager indexManager;
private final QueryProcessor queryProcessor;
private final RankingEngine rankingEngine;
private final CacheManager cacheManager;
@GetMapping("/search")
public SearchResult search(
@RequestParam String query,
@RequestParam(defaultValue = "1") int page,
@RequestParam(defaultValue = "unified") String collection) {
// 1. 쿼리 정규화 및 분석
NormalizedQuery normalized = queryProcessor.normalize(query);
// 캐시 확인
String cacheKey = generateCacheKey(normalized, page, collection);
SearchResult cached = cacheManager.get(cacheKey);
if (cached != null && !isRealTimeQuery(normalized)) {
return cached;
}
// 2. 쿼리 분석
QueryAnalysis analysis = analyzeQuery(normalized);
// 3. 컬렉션별 검색
List<CollectionResult> results = new ArrayList<>();
if (collection.equals("unified")) {
// 통합검색
results = searchAllCollections(analysis);
} else {
// 특정 컬렉션 검색
results.add(searchCollection(collection, analysis));
}
// 4. 랭킹 및 정렬
SearchResult finalResult = rankingEngine.rank(results, analysis);
// 5. 캐시 저장
if (!isRealTimeQuery(normalized)) {
cacheManager.put(cacheKey, finalResult, getTTL(collection));
}
return finalResult;
}
private QueryAnalysis analyzeQuery(NormalizedQuery query) {
QueryAnalysis analysis = new QueryAnalysis();
// 형태소 분석
List<Token> tokens = morphAnalyzer.analyze(query.getText());
analysis.setTokens(tokens);
// 의도 분석
Intent intent = intentClassifier.classify(query, tokens);
analysis.setIntent(intent);
// 엔티티 추출
List<Entity> entities = entityExtractor.extract(tokens);
analysis.setEntities(entities);
// 쿼리 카테고리 분류
Category category = categoryClassifier.classify(query, entities);
analysis.setCategory(category);
return analysis;
}
private List<CollectionResult> searchAllCollections(QueryAnalysis analysis) {
// 병렬 검색 실행
CompletableFuture<CollectionResult> webFuture =
CompletableFuture.supplyAsync(() -> searchWeb(analysis));
CompletableFuture<CollectionResult> newsFuture =
CompletableFuture.supplyAsync(() -> searchNews(analysis));
CompletableFuture<CollectionResult> blogFuture =
CompletableFuture.supplyAsync(() -> searchBlog(analysis));
CompletableFuture<CollectionResult> shoppingFuture =
CompletableFuture.supplyAsync(() -> searchShopping(analysis));
// 결과 수집
return Stream.of(webFuture, newsFuture, blogFuture, shoppingFuture)
.map(CompletableFuture::join)
.filter(result -> result.getCount() > 0)
.collect(Collectors.toList());
}
}
// 분산 인덱싱 시스템
@Service
public class DistributedIndexer {
private final HBaseClient hbaseClient;
private final ElasticsearchClient esClient;
private final KafkaProducer<String, IndexDocument> producer;
public void indexDocument(Document document) {
// 1. 문서 전처리
ProcessedDocument processed = preprocessDocument(document);
// 2. 분산 저장
// HBase에 원본 저장
hbaseClient.put(
"documents",
document.getId(),
processed.toHBaseRow()
);
// 3. 역인덱스 생성
InvertedIndex invertedIndex = createInvertedIndex(processed);
// 4. Elasticsearch 인덱싱
esClient.index(
"web_index",
document.getId(),
invertedIndex.toElasticsearchDocument()
);
// 5. 실시간 업데이트 전파
producer.send(new ProducerRecord<>(
"index-updates",
document.getId(),
new IndexDocument(document.getId(), processed, invertedIndex)
));
}
private InvertedIndex createInvertedIndex(ProcessedDocument doc) {
InvertedIndex index = new InvertedIndex();
// 토큰화 및 위치 정보 저장
Map<String, List<Position>> tokenPositions = new HashMap<>();
List<Token> tokens = tokenizer.tokenize(doc.getContent());
for (int i = 0; i < tokens.size(); i++) {
Token token = tokens.get(i);
tokenPositions.computeIfAbsent(token.getValue(), k -> new ArrayList<>())
.add(new Position(i, token.getOffset(), token.getLength()));
}
// TF-IDF 계산
for (Map.Entry<String, List<Position>> entry : tokenPositions.entrySet()) {
String term = entry.getKey();
List<Position> positions = entry.getValue();
double tf = (double) positions.size() / tokens.size();
double idf = Math.log(totalDocuments / getDocumentFrequency(term));
index.addTerm(term, positions, tf * idf);
}
return index;
}
}
실시간 검색어 처리
// 실시간 급상승 검색어 시스템
@Component
public class RealtimeSearchTrendAnalyzer {
private final RedisTemplate<String, SearchQuery> redisTemplate;
private final TimeSeriesDB tsdb;
// 검색 쿼리 수집
@EventListener
public void collectSearchQuery(SearchEvent event) {
String query = event.getQuery();
long timestamp = event.getTimestamp();
// 시간대별 집계
String minuteKey = getMinuteKey(timestamp);
redisTemplate.opsForZSet().incrementScore(
minuteKey,
query,
1.0
);
// 시계열 데이터 저장
tsdb.addPoint(
"search.queries",
timestamp,
1.0,
Tags.of("query", query)
);
}
// 급상승 검색어 계산
@Scheduled(fixedDelay = 60000) // 1분마다
public void calculateTrends() {
long now = System.currentTimeMillis();
// 최근 10분 vs 이전 10분 비교
Map<String, Double> recent = getQueryCounts(now - 10 * MINUTE, now);
Map<String, Double> previous = getQueryCounts(now - 20 * MINUTE, now - 10 * MINUTE);
List<TrendingQuery> trends = new ArrayList<>();
for (Map.Entry<String, Double> entry : recent.entrySet()) {
String query = entry.getKey();
double recentCount = entry.getValue();
double previousCount = previous.getOrDefault(query, 1.0);
// 증가율 계산
double growthRate = (recentCount - previousCount) / previousCount;
// 급상승 조건: 200% 이상 증가 & 최소 검색수 100회
if (growthRate > 2.0 && recentCount > 100) {
trends.add(new TrendingQuery(
query,
recentCount,
growthRate,
calculateHotScore(recentCount, growthRate, now)
));
}
}
// 상위 20개 선정
trends.sort((a, b) -> Double.compare(b.getHotScore(), a.getHotScore()));
List<TrendingQuery> top20 = trends.stream()
.limit(20)
.collect(Collectors.toList());
// 결과 저장 및 전파
publishTrends(top20);
}
private double calculateHotScore(double count, double growth, long timestamp) {
// Newton's law of cooling 응용
long age = System.currentTimeMillis() - timestamp;
double timeFactor = Math.exp(-age / (5.0 * MINUTE));
return count * growth * timeFactor;
}
}
빅데이터 플랫폼
Hadoop 기반 데이터 처리
// 네이버 C3 (Cloud Computing Center) 플랫폼
public class NaverBigDataPlatform {
// MapReduce Job for 로그 분석
public static class LogAnalysisMapper extends Mapper<LongWritable, Text, Text, IntWritable> {
private final static IntWritable one = new IntWritable(1);
private Text word = new Text();
@Override
protected void map(LongWritable key, Text value, Context context)
throws IOException, InterruptedException {
// 로그 파싱
LogEntry entry = LogParser.parse(value.toString());
// 검색 쿼리 추출
if (entry.getType() == LogType.SEARCH) {
String query = entry.getQuery();
// 형태소 분석
List<String> tokens = MorphAnalyzer.analyze(query);
for (String token : tokens) {
word.set(token);
context.write(word, one);
}
}
// 사용자 행동 분석
String userId = entry.getUserId();
String action = entry.getAction();
context.write(new Text(userId + ":" + action), one);
}
}
public static class LogAnalysisReducer extends Reducer<Text, IntWritable, Text, IntWritable> {
@Override
protected void reduce(Text key, Iterable<IntWritable> values, Context context)
throws IOException, InterruptedException {
int sum = 0;
for (IntWritable val : values) {
sum += val.get();
}
context.write(key, new IntWritable(sum));
}
}
// Spark Streaming for 실시간 분석
public void runRealtimeAnalysis() {
SparkConf conf = new SparkConf()
.setAppName("NaverRealtimeAnalysis")
.set("spark.streaming.kafka.maxRatePerPartition", "10000");
JavaStreamingContext jssc = new JavaStreamingContext(conf, Durations.seconds(10));
// Kafka 스트림 생성
JavaPairInputDStream<String, String> messages = KafkaUtils.createDirectStream(
jssc,
String.class,
String.class,
StringDecoder.class,
StringDecoder.class,
kafkaParams,
topicsSet
);
// 실시간 로그 처리
JavaDStream<LogEntry> logs = messages
.map(tuple -> LogParser.parse(tuple._2()));
// 윈도우 기반 집계
JavaPairDStream<String, Integer> searchCounts = logs
.filter(log -> log.getType() == LogType.SEARCH)
.mapToPair(log -> new Tuple2<>(log.getQuery(), 1))
.reduceByKeyAndWindow(
(a, b) -> a + b,
Durations.minutes(10),
Durations.minutes(1)
);
// HBase에 저장
searchCounts.foreachRDD(rdd -> {
rdd.foreachPartition(partition -> {
HBaseConnection conn = HBaseConnectionPool.getConnection();
Table table = conn.getTable(TableName.valueOf("search_trends"));
partition.forEachRemaining(record -> {
Put put = new Put(Bytes.toBytes(record._1()));
put.addColumn(
Bytes.toBytes("counts"),
Bytes.toBytes("value"),
Bytes.toBytes(record._2())
);
table.put(put);
});
table.close();
});
});
jssc.start();
jssc.awaitTermination();
}
}
캐싱 시스템 (Arcus)
분산 캐시 구현
// 네이버가 개발한 Arcus (memcached 클러스터)
@Component
public class ArcusCache {
private final ArcusClient arcusClient;
@PostConstruct
public void init() {
// Arcus 클라이언트 초기화
ConnectionFactoryBuilder cfb = new ConnectionFactoryBuilder();
cfb.setProtocol(Protocol.TEXT);
cfb.setFailureMode(FailureMode.Cancel);
arcusClient = ArcusClient.createArcusClient(
"naver-search-cache",
new ZKConnectionFactory(zkAddress),
cfb
);
}
public <T> T get(String key, Class<T> type) {
try {
Future<Object> future = arcusClient.asyncGet(key);
Object value = future.get(100, TimeUnit.MILLISECONDS);
if (value != null) {
return objectMapper.convertValue(value, type);
}
} catch (TimeoutException e) {
// 캐시 타임아웃 - 통과
log.debug("Cache timeout for key: {}", key);
} catch (Exception e) {
log.error("Cache error for key: {}", key, e);
}
return null;
}
public void set(String key, Object value, int ttl) {
try {
String json = objectMapper.writeValueAsString(value);
arcusClient.set(key, ttl, json);
} catch (Exception e) {
log.error("Failed to set cache for key: {}", key, e);
}
}
// Collection 타입 지원
public void addToList(String key, Object value) {
CollectionAttributes attrs = new CollectionAttributes();
attrs.setMaxCount(1000);
attrs.setExpireTime(3600);
arcusClient.asyncLopInsert(
key,
-1, // tail insert
objectMapper.writeValueAsString(value),
attrs
);
}
public List<String> getList(String key, int count) {
CollectionFuture<List<Object>> future =
arcusClient.asyncLopGet(key, 0, count, false, false);
try {
List<Object> result = future.get(100, TimeUnit.MILLISECONDS);
return result.stream()
.map(Object::toString)
.collect(Collectors.toList());
} catch (Exception e) {
return Collections.emptyList();
}
}
}
AI/ML 플랫폼
CLOVA AI 시스템
# 네이버 CLOVA 자연어 처리
import torch
import torch.nn as nn
from transformers import BertModel, BertTokenizer
class ClovaNLPEngine:
def __init__(self):
# 한국어 특화 BERT 모델
self.tokenizer = BertTokenizer.from_pretrained('naver/kobert')
self.model = KoBertForSequenceClassification.from_pretrained('naver/kobert')
# GPU 사용
self.device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
self.model.to(self.device)
def analyze_intent(self, query: str) -> IntentResult:
# 토큰화
inputs = self.tokenizer(
query,
return_tensors='pt',
padding=True,
truncation=True,
max_length=128
)
# 의도 분류
with torch.no_grad():
outputs = self.model(**inputs.to(self.device))
predictions = torch.nn.functional.softmax(outputs.logits, dim=-1)
# 상위 의도 추출
top_intents = []
for idx, prob in enumerate(predictions[0]):
if prob > 0.1: # 10% 이상 확률
top_intents.append({
'intent': self.intent_labels[idx],
'confidence': float(prob)
})
return IntentResult(
query=query,
intents=sorted(top_intents, key=lambda x: x['confidence'], reverse=True)
)
def extract_entities(self, text: str) -> List[Entity]:
# NER (Named Entity Recognition)
tokens = self.tokenizer.tokenize(text)
inputs = self.tokenizer(text, return_tensors='pt')
with torch.no_grad():
outputs = self.ner_model(**inputs.to(self.device))
predictions = torch.argmax(outputs.logits, dim=2)
# BIO 태깅 디코딩
entities = []
current_entity = None
for idx, (token, label_id) in enumerate(zip(tokens, predictions[0])):
label = self.ner_labels[label_id]
if label.startswith('B-'):
if current_entity:
entities.append(current_entity)
current_entity = {
'text': token.replace('##', ''),
'type': label[2:],
'start': idx,
'end': idx
}
elif label.startswith('I-') and current_entity:
current_entity['text'] += token.replace('##', '')
current_entity['end'] = idx
else:
if current_entity:
entities.append(current_entity)
current_entity = None
return entities
# Papago 번역 엔진
class PapagoTranslator:
def __init__(self):
self.models = {}
self.load_models()
def translate(self, text: str, source: str, target: str) -> str:
# 언어 쌍별 특화 모델 선택
model_key = f"{source}_{target}"
if model_key not in self.models:
model_key = "multilingual"
model = self.models[model_key]
# 전처리
preprocessed = self.preprocess(text, source)
# 번역
translated = model.translate(preprocessed)
# 후처리
postprocessed = self.postprocess(translated, target)
return postprocessed
def preprocess(self, text: str, lang: str) -> str:
# 언어별 전처리
if lang == 'ko':
# 한국어 특수 처리
text = self.normalize_korean(text)
elif lang == 'ja':
# 일본어 특수 처리
text = self.normalize_japanese(text)
return text
모니터링 시스템 (Pinpoint)
APM (Application Performance Management)
// 네이버가 개발한 Pinpoint APM
@Aspect
@Component
public class PinpointTracer {
private final TraceContext traceContext;
@Around("@annotation(Traced)")
public Object trace(ProceedingJoinPoint joinPoint) throws Throwable {
// 트레이스 시작
Trace trace = traceContext.currentTraceObject();
if (trace == null) {
trace = traceContext.newTraceObject();
}
SpanEventRecorder recorder = trace.traceBlockBegin();
try {
// 메서드 정보 기록
recorder.recordServiceType(ServiceType.SPRING);
recorder.recordApi(getMethodDescriptor(joinPoint));
// 실행
Object result = joinPoint.proceed();
// 성공 기록
recorder.recordException(false);
return result;
} catch (Throwable t) {
// 예외 기록
recorder.recordException(t);
throw t;
} finally {
// 트레이스 종료
trace.traceBlockEnd();
// 성능 메트릭 수집
long elapsed = trace.getElapsedTime();
if (elapsed > SLOW_THRESHOLD) {
recordSlowTrace(joinPoint, elapsed);
}
}
}
// 분산 트레이싱
@Component
public class DistributedTracer {
public void propagateTrace(HttpRequest request) {
Trace trace = traceContext.currentTraceObject();
if (trace != null) {
// 트레이스 ID 전파
request.addHeader("Pinpoint-TraceID", trace.getTraceId().toString());
request.addHeader("Pinpoint-SpanID", trace.getSpanId());
request.addHeader("Pinpoint-pSpanID", trace.getParentSpanId());
request.addHeader("Pinpoint-Sampled", String.valueOf(trace.canSampled()));
request.addHeader("Pinpoint-Flags", String.valueOf(trace.getFlags()));
}
}
public void continueTrace(HttpServletRequest request) {
String traceId = request.getHeader("Pinpoint-TraceID");
if (traceId != null) {
// 전파된 트레이스 계속
TraceId remoteTraceId = TraceId.parse(traceId);
long parentSpanId = Long.parseLong(request.getHeader("Pinpoint-pSpanID"));
Trace trace = traceContext.continueTraceObject(remoteTraceId, parentSpanId);
trace.recordAcceptorHost(request.getRemoteAddr());
trace.recordRpc(request.getRequestURI());
}
}
}
}
기술 스택 요약:
- Backend: Java, Spring, Node.js
- 검색: Elasticsearch, Lucene, 자체 엔진
- 빅데이터: Hadoop, Spark, HBase
- 캐시: Arcus (자체 개발), Redis
- 데이터베이스: MySQL, MongoDB, Cassandra
- 인프라: 자체 데이터센터, NBP (Naver Business Platform)
- 모니터링: Pinpoint (자체 개발)
- AI/ML: TensorFlow, PyTorch, CLOVA
💡 핵심 기술 인사이트
1. 검색 기술의 정수
- 한국어 특화 형태소 분석
- 실시간 인덱싱
- 의도 기반 검색
2. 오픈소스 기여
- Pinpoint APM
- Arcus Cache Cluster
- nGrinder 성능 테스트
3. AI 기술 선도
- CLOVA AI 플랫폼
- Papago 번역
- 초거대 AI HyperCLOVA
📈 성과 & 지표
기술적 성과
- 검색 응답시간: < 50ms
- 시스템 가용성: 99.95%+
- 일일 처리 데이터: 10TB+
- 동시 접속자: 500만+ 명
- API 호출: 분당 1,000만+ 건
비즈니스 영향
- 국내 검색 점유율: 60%+
- 글로벌 진출: 일본 LINE, 동남아
- AI 서비스: 10+ 개 출시
- 개발자 생태계: 10만+ 개발자
🎓 네이버에서 배울 점
✅ 적용 가능한 패턴
- 대규모 검색 시스템: 분산 인덱싱
- 실시간 처리: 스트리밍 아키텍처
- 캐싱 전략: 다층 캐시 구조
- 오픈소스화: 핵심 기술 공개
- AI 통합: 검색과 AI의 융합
❌ 주의사항
- 운영 복잡도: 수많은 서비스 관리
- 레거시 부담: 오래된 시스템 유지
- 규모의 저주: 작은 변경도 큰 영향
- 경쟁 압박: 글로벌 기업과 경쟁
📚 추천 리소스
🔮 미래 전망
현재 집중 분야
- 초거대 AI: HyperCLOVA X
- 로봇공학: 서비스 로봇
- 디지털 트윈: 제2사옥 활용
- 클라우드: NBP 글로벌 확장
- 메타버스: 제페토, 아크버스
기술 투자 영역
- 양자 컴퓨팅: 미래 검색 기술
- 블록체인: LINE 블록체인
- 자율주행: 네이버랩스
- 5G/6G: 초연결 서비스
- 그린 IT: 친환경 데이터센터
"기술이 사람을 더 가깝게, 정보를 더 유용하게, 세상을 더 편리하게 만듭니다."
- 네이버 기술 조직
마지막 업데이트: 2025-01-28