🏢 카카오 기술 스택 분석
카카오가 5천만 한국인의 일상을 연결하는 기술 스택 심층 분석 - Java, Kotlin, MySQL로 구축한 국민 메신저부터 슈퍼앱까지
🏢 카카오 기술 스택 분석
한국인의 일상 속 모든 서비스를 하나로 연결하는 카카오의 기술 스택을 심층 분석합니다.
"Connect Everything" - 카카오톡에서 시작해 슈퍼앱으로 진화한 기술 여정
📊 회사 개요
서비스 규모
- 월간 활성 사용자: 4,700만+ 명 (한국 인구 90%+)
- 일일 메시지: 100억+ 건
- 서비스: 100+ 개 (톡, 페이, 택시, 뱅크 등)
- 일일 API 호출: 2,000억+ 건
- 데이터 처리량: 일일 10PB+
엔지니어링 조직
- 개발자: 4,000명+ (전체 직원 10,000+)
- 문화: "같이가치" (함께의 가치)
- 조직: 크루 단위 자율 운영
- 오픈소스: 100+ 프로젝트 공개
기술적 도전과제
- 대규모 트래픽: 전국민이 사용하는 서비스
- 실시간성: 밀리초 단위 메시징
- 안정성: 국가 기반 서비스 수준
- 다양성: 100+ 서비스 통합
🏗️ 아키텍처 Overview
시스템 다이어그램
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ KakaoTalk │────►│ LOCO │────►│ Message │
│ Clients │ │ Protocol │ │ Server │
└─────────────┘ └─────────────┘ └─────────────┘
│ │ │
│ ┌─────▼─────┐ ┌─────▼─────┐
│ │ Kapi │ │ MySQL │
│ │ Gateway │ │ Cluster │
│ └───────────┘ └───────────┘
│ │ │
└────────────────────┤ │
┌─────▼─────┐ ┌─────▼─────┐
│ Daum │ │ HBase │
│ Platform │ │ Store │
└───────────┘ └───────────┘
핵심 설계 원칙
- Service Mesh: 마이크로서비스 아키텍처
- Multi-Tenancy: 서비스별 독립 운영
- Horizontal Scaling: 수평 확장 우선
- Fault Tolerance: 장애 격리
- Data Locality: 지역별 데이터 센터
기술 진화
- 2010-2012: KakaoTalk 출시, LOCO 프로토콜
- 2013-2015: 플랫폼화, Daum 합병
- 2016-2018: 카카오페이, 카카오뱅크
- 2019-2021: AI 플랫폼, 클라우드
- 2022-현재: 블록체인, 메타버스
🧩 기술 스택 상세
Backend 기술
LOCO 프로토콜 (카카오톡 메시징)
// 카카오톡 LOCO 프로토콜 구현
@Component
public class LocoProtocolHandler {
private final MessageService messageService;
private final SessionManager sessionManager;
private final EncryptionService encryptionService;
// LOCO 패킷 구조
public class LocoPacket {
private int packetId;
private short statusCode;
private String method;
private byte[] body;
private long timestamp;
public byte[] serialize() {
ByteBuffer buffer = ByteBuffer.allocate(1024);
buffer.putInt(packetId);
buffer.putShort(statusCode);
buffer.putInt(method.length());
buffer.put(method.getBytes(StandardCharsets.UTF_8));
buffer.putInt(body.length);
buffer.put(body);
buffer.putLong(timestamp);
return Arrays.copyOf(buffer.array(), buffer.position());
}
}
@EventListener
public void handleIncomingPacket(LocoPacket packet) {
// 1. 패킷 복호화
byte[] decrypted = encryptionService.decrypt(
packet.getBody(),
sessionManager.getSessionKey(packet.getSessionId())
);
// 2. 메서드별 처리
switch (packet.getMethod()) {
case "MSG":
handleMessage(decrypted);
break;
case "READ":
handleReadReceipt(decrypted);
break;
case "PING":
handlePing(packet.getSessionId());
break;
case "JOIN":
handleJoinChannel(decrypted);
break;
default:
log.warn("Unknown method: {}", packet.getMethod());
}
}
private void handleMessage(byte[] data) {
Message message = Message.deserialize(data);
// 메시지 저장
CompletableFuture<Void> saveFuture = CompletableFuture
.runAsync(() -> messageService.save(message));
// 수신자들에게 전달
CompletableFuture<Void> deliveryFuture = CompletableFuture
.runAsync(() -> {
List<Long> recipients = message.getChatRoom().getMembers();
recipients.parallelStream().forEach(userId -> {
Session session = sessionManager.getSession(userId);
if (session != null && session.isActive()) {
sendPacket(session, createMessagePacket(message));
} else {
// 푸시 알림 발송
pushNotificationService.send(userId, message);
}
});
});
// 읽음 상태 업데이트
CompletableFuture<Void> statusFuture = CompletableFuture
.runAsync(() -> messageService.updateDeliveryStatus(
message.getId(),
DeliveryStatus.DELIVERED
));
// 모든 작업 완료 대기
CompletableFuture.allOf(saveFuture, deliveryFuture, statusFuture)
.exceptionally(throwable -> {
log.error("Message handling failed", throwable);
return null;
});
}
}
// 카카오톡 서버 확장성
@Configuration
@EnableKafka
public class KakaoTalkServerConfig {
@Bean
public ConsistentHashRouter messageRouter() {
return new ConsistentHashRouter(
Arrays.asList("server1", "server2", "server3"),
1000 // virtual nodes
);
}
@Bean
public MessageShardingStrategy shardingStrategy() {
return new MessageShardingStrategy() {
@Override
public String getShardKey(Message message) {
// 채팅방 ID 기반 샤딩
return String.valueOf(message.getChatRoomId() % 100);
}
};
}
@Bean
public LoadBalancer loadBalancer() {
return LoadBalancer.builder()
.algorithm(Algorithm.LEAST_CONNECTIONS)
.healthCheck(Duration.ofSeconds(5))
.failoverThreshold(3)
.build();
}
}
카카오페이 금융 플랫폼
// 카카오페이 결제 처리 시스템
@Service
@Transactional
class PaymentService(
private val accountService: AccountService,
private val transactionRepository: TransactionRepository,
private val fraudDetectionService: FraudDetectionService,
private val notificationService: NotificationService
) {
suspend fun processPayment(request: PaymentRequest): PaymentResult {
// 1. 사기 탐지
val fraudScore = fraudDetectionService.evaluate(request)
if (fraudScore.isHighRisk()) {
return PaymentResult.rejected(
reason = "보안 정책에 의해 거부되었습니다",
code = "FRAUD_DETECTED"
)
}
// 2. 계좌 잔액 확인
val sourceAccount = accountService.getAccount(request.sourceAccountId)
if (!sourceAccount.hasSufficientBalance(request.amount)) {
return PaymentResult.failed(
reason = "잔액이 부족합니다",
code = "INSUFFICIENT_BALANCE"
)
}
// 3. 분산 트랜잭션 시작
return withDistributedTransaction { transactionId ->
try {
// 출금
val debitResult = accountService.debit(
accountId = request.sourceAccountId,
amount = request.amount,
transactionId = transactionId
)
// 입금
val creditResult = accountService.credit(
accountId = request.targetAccountId,
amount = request.amount,
transactionId = transactionId
)
// 거래 기록
val transaction = Transaction(
id = transactionId,
type = TransactionType.TRANSFER,
sourceAccountId = request.sourceAccountId,
targetAccountId = request.targetAccountId,
amount = request.amount,
status = TransactionStatus.COMPLETED,
timestamp = Instant.now()
)
transactionRepository.save(transaction)
// 알림 발송
coroutineScope {
launch {
notificationService.notifyPaymentCompleted(
request.sourceAccountId,
transaction
)
}
launch {
notificationService.notifyPaymentReceived(
request.targetAccountId,
transaction
)
}
}
PaymentResult.success(
transactionId = transactionId,
timestamp = transaction.timestamp
)
} catch (e: Exception) {
// 롤백
rollbackTransaction(transactionId)
PaymentResult.failed(
reason = "결제 처리 중 오류가 발생했습니다",
code = "INTERNAL_ERROR"
)
}
}
}
// QR 결제
suspend fun processQRPayment(qrCode: String, amount: Money): PaymentResult {
// QR 코드 파싱
val merchantInfo = parseQRCode(qrCode)
// 가맹점 검증
if (!merchantService.isValid(merchantInfo.merchantId)) {
return PaymentResult.failed(
reason = "유효하지 않은 가맹점입니다",
code = "INVALID_MERCHANT"
)
}
// 결제 한도 확인
val dailyLimit = userService.getDailyPaymentLimit(currentUserId)
val todayTotal = transactionRepository.getTodayTotal(currentUserId)
if (todayTotal + amount > dailyLimit) {
return PaymentResult.failed(
reason = "일일 결제 한도를 초과했습니다",
code = "LIMIT_EXCEEDED"
)
}
// 생체 인증 요청
val biometricResult = requestBiometricAuth()
if (!biometricResult.isSuccess) {
return PaymentResult.failed(
reason = "인증에 실패했습니다",
code = "AUTH_FAILED"
)
}
// 결제 처리
return processPayment(
PaymentRequest(
sourceAccountId = currentUser.primaryAccountId,
targetAccountId = merchantInfo.accountId,
amount = amount,
description = merchantInfo.name
)
)
}
}
// 블록체인 기반 디지털 자산 관리 (Klaytn)
@Component
class KlaytnWalletService(
private val caver: Caver,
private val keyringRepository: KeyringRepository
) {
suspend fun createWallet(userId: Long): WalletInfo {
// 키링 생성
val keyring = caver.wallet.keyring.generate()
// 안전한 저장 (HSM 사용)
val encryptedKey = encryptWithHSM(keyring.key.privateKey)
keyringRepository.save(
UserId = userId,
address = keyring.address,
encryptedPrivateKey = encryptedKey
)
// 계정 활성화를 위한 초기 KLAY 전송
val faucetResult = requestFaucet(keyring.address)
return WalletInfo(
address = keyring.address,
balance = faucetResult.amount,
createdAt = Instant.now()
)
}
suspend fun transferKLAY(
fromUserId: Long,
toAddress: String,
amount: BigInteger
): TransactionReceipt {
// 키링 로드
val keyring = loadKeyring(fromUserId)
caver.wallet.add(keyring)
// 가스비 추정
val gasPrice = caver.rpc.klay.getGasPrice().send().value
val gas = estimateGas(keyring.address, toAddress, amount)
// 트랜잭션 생성
val valueTransfer = ValueTransfer.create(
caver,
keyring.address,
toAddress,
amount,
gas,
gasPrice
)
// 서명 및 전송
caver.wallet.sign(keyring.address, valueTransfer)
val receipt = caver.rpc.klay.sendRawTransaction(valueTransfer).send()
// 결과 저장
saveTransaction(
userId = fromUserId,
txHash = receipt.transactionHash,
amount = amount,
toAddress = toAddress
)
return receipt
}
}
Frontend 기술
카카오톡 웹/앱
// 카카오톡 React Native 앱
import React, { useEffect, useState } from "react";
import { View, FlatList, TextInput, TouchableOpacity } from "react-native";
import { useKakaoTalk } from "@kakao/react-native-sdk";
interface ChatRoomProps {
roomId: string;
}
export const ChatRoom: React.FC<ChatRoomProps> = ({ roomId }) => {
const { loco, user } = useKakaoTalk();
const [messages, setMessages] = useState<Message[]>([]);
const [inputText, setInputText] = useState("");
useEffect(() => {
// 메시지 구독
const unsubscribe = loco.subscribe(roomId, (packet) => {
switch (packet.method) {
case "MSG":
handleNewMessage(packet.data);
break;
case "READ":
handleReadReceipt(packet.data);
break;
case "DELETED":
handleDeletedMessage(packet.data);
break;
}
});
// 이전 메시지 로드
loadMessages();
return unsubscribe;
}, [roomId]);
const loadMessages = async () => {
const history = await loco.getMessages(roomId, {
limit: 50,
before: messages[0]?.id,
});
setMessages((prev) => [...history, ...prev]);
};
const sendMessage = async () => {
if (!inputText.trim()) return;
// 낙관적 업데이트
const tempMessage: Message = {
id: `temp_${Date.now()}`,
content: inputText,
sender: user,
timestamp: Date.now(),
status: "sending",
};
setMessages((prev) => [...prev, tempMessage]);
setInputText("");
try {
// LOCO 프로토콜로 전송
const sentMessage = await loco.sendMessage(roomId, {
type: "text",
content: inputText,
mentions: extractMentions(inputText),
});
// 임시 메시지를 실제 메시지로 교체
setMessages((prev) =>
prev.map((msg) => (msg.id === tempMessage.id ? sentMessage : msg))
);
} catch (error) {
// 전송 실패 처리
setMessages((prev) =>
prev.map((msg) =>
msg.id === tempMessage.id ? { ...msg, status: "failed" } : msg
)
);
}
};
const renderMessage = ({ item }: { item: Message }) => {
const isMyMessage = item.sender.id === user.id;
return (
<MessageBubble
message={item}
isMyMessage={isMyMessage}
onLongPress={() => showMessageOptions(item)}
/>
);
};
return (
<View style={styles.container}>
<FlatList
data={messages}
renderItem={renderMessage}
keyExtractor={(item) => item.id}
inverted
onEndReached={loadMessages}
onEndReachedThreshold={0.5}
/>
<MessageInput
value={inputText}
onChangeText={setInputText}
onSubmit={sendMessage}
attachments={
<AttachmentPicker
onImageSelect={sendImage}
onVideoSelect={sendVideo}
onFileSelect={sendFile}
/>
}
/>
</View>
);
};
// 카카오페이 웹 SDK
export class KakaoPaySDK {
private apiKey: string;
private environment: "sandbox" | "production";
async requestPayment(params: PaymentParams): Promise<PaymentResult> {
// 결제 준비 API 호출
const response = await fetch(`${this.baseURL}/v1/payment/ready`, {
method: "POST",
headers: {
Authorization: `KakaoAK ${this.apiKey}`,
"Content-Type": "application/x-www-form-urlencoded",
},
body: new URLSearchParams({
cid: params.cid,
partner_order_id: params.orderId,
partner_user_id: params.userId,
item_name: params.itemName,
quantity: params.quantity.toString(),
total_amount: params.totalAmount.toString(),
tax_free_amount: params.taxFreeAmount.toString(),
approval_url: params.approvalUrl,
cancel_url: params.cancelUrl,
fail_url: params.failUrl,
}),
});
const data = await response.json();
// 결제 페이지로 리다이렉트
if (data.next_redirect_pc_url) {
window.location.href = data.next_redirect_pc_url;
} else if (data.next_redirect_mobile_url) {
window.location.href = data.next_redirect_mobile_url;
}
return data;
}
async approvePayment(pgToken: string, tid: string): Promise<ApprovalResult> {
const response = await fetch(`${this.baseURL}/v1/payment/approve`, {
method: "POST",
headers: {
Authorization: `KakaoAK ${this.apiKey}`,
"Content-Type": "application/x-www-form-urlencoded",
},
body: new URLSearchParams({
cid: this.cid,
tid: tid,
partner_order_id: this.currentOrder.orderId,
partner_user_id: this.currentOrder.userId,
pg_token: pgToken,
}),
});
return response.json();
}
}
데이터 인프라
대규모 데이터 처리
// Spark 기반 데이터 파이프라인
object KakaoDataPipeline {
def main(args: Array[String]): Unit = {
val spark = SparkSession.builder()
.appName("KakaoDataPipeline")
.config("spark.sql.adaptive.enabled", "true")
.config("spark.sql.adaptive.coalescePartitions.enabled", "true")
.getOrCreate()
import spark.implicits._
// 카카오톡 메시지 분석
val messages = spark
.readStream
.format("kafka")
.option("kafka.bootstrap.servers", "kafka-cluster:9092")
.option("subscribe", "kakaotalk.messages")
.option("startingOffsets", "latest")
.load()
val messageData = messages
.select(
from_json($"value".cast("string"), messageSchema).as("data")
)
.select("data.*")
// 실시간 통계
val stats = messageData
.withWatermark("timestamp", "10 minutes")
.groupBy(
window($"timestamp", "5 minutes"),
$"chatRoomType"
)
.agg(
count("*").as("messageCount"),
approx_count_distinct("userId").as("activeUsers"),
avg("messageLength").as("avgLength")
)
// HBase에 저장
stats.writeStream
.foreachBatch { (batchDF: DataFrame, batchId: Long) =>
batchDF.foreachPartition { partition =>
val hbaseConnection = HBaseConnection.create()
val table = hbaseConnection.getTable(TableName.valueOf("message_stats"))
partition.foreach { row =>
val put = new Put(Bytes.toBytes(s"${row.getAs[String]("window")}_${row.getAs[String]("chatRoomType")}"))
put.addColumn(
Bytes.toBytes("stats"),
Bytes.toBytes("count"),
Bytes.toBytes(row.getAs[Long]("messageCount"))
)
table.put(put)
}
table.close()
}
}
.start()
// 사용자 행동 분석
val userBehavior = spark
.read
.format("delta")
.load("/data/user_behavior")
val recommendations = userBehavior
.join(userProfiles, "userId")
.where($"eventType" === "view" || $"eventType" === "purchase")
.groupBy("userId")
.agg(
collect_list(
struct($"itemId", $"eventType", $"timestamp")
).as("history")
)
.select(
$"userId",
generateRecommendations($"history").as("recommendations")
)
// ML 모델 적용
val model = PipelineModel.load("/models/recommendation_model")
val predictions = model.transform(recommendations)
// Redis에 캐싱
predictions.foreachPartition { partition =>
val jedis = new Jedis("redis-cluster", 6379)
partition.foreach { row =>
jedis.setex(
s"rec:${row.getAs[String]("userId")}",
3600,
row.getAs[Seq[String]]("recommendations").mkString(",")
)
}
jedis.close()
}
}
}
기술 스택 요약:
- Backend: Java, Kotlin, Spring Boot, Node.js
- Frontend: React Native, React, Vue.js
- 데이터베이스: MySQL, MongoDB, HBase, Redis
- 메시지 큐: Kafka, RabbitMQ
- 빅데이터: Spark, Hadoop, Presto
- 인프라: Kubernetes, 자체 IDC + 퍼블릭 클라우드
- 블록체인: Klaytn (자체 개발)
💡 핵심 기술 인사이트
1. LOCO 프로토콜의 효율성
- 독자적인 바이너리 프로토콜
- 최소한의 네트워크 오버헤드
- 효율적인 배터리 사용
2. 멀티 서비스 통합
- 하나의 계정으로 모든 서비스
- 서비스 간 원활한 데이터 공유
- 통합 결제 시스템
3. 대규모 트래픽 처리
- 샤딩을 통한 수평 확장
- 지역별 데이터 센터
- 효율적인 캐싱 전략
📈 성과 & 지표
기술적 성과
- 메시지 전송 지연: < 100ms
- 시스템 가용성: 99.95%+
- 동시 접속자: 4,000만+ 명
- 초당 메시지 처리: 200만+ 건
- API 응답시간: P99 < 200ms
비즈니스 영향
- 국내 점유율: 95%+ (메신저 시장)
- MAU: 4,700만+ (한국 인구 90%+)
- 카카오페이 거래액: 연간 100조원+
- 플랫폼 매출: 연간 7조원+
🎓 카카오에서 배울 점
✅ 적용 가능한 패턴
- 독자 프로토콜: 특화된 요구사항 해결
- 서비스 메시: 대규모 마이크로서비스
- 통합 플랫폼: 슈퍼앱 아키텍처
- 지역화: 로컬 최적화
- 오픈 이노베이션: 개발자 생태계
❌ 주의사항
- 복잡성: 100+ 서비스 관리
- 레거시: 오래된 시스템과 공존
- 규제: 금융, 개인정보 보호
- 경쟁: 글로벌 플랫폼과 경쟁
📚 추천 리소스
🔮 미래 전망
현재 집중 분야
- AI 플랫폼: 카카오 브레인, GPT 경쟁
- 블록체인: Klaytn 생태계 확장
- 모빌리티: 자율주행, 스마트 모빌리티
- 헬스케어: 디지털 헬스케어 플랫폼
- 메타버스: 가상 세계 플랫폼
기술 투자 영역
- 초거대 AI: 한국어 특화 LLM
- Web3: 탈중앙화 서비스
- 엣지 컴퓨팅: 더 빠른 응답
- 양자 컴퓨팅: 미래 보안
- 6G: 차세대 통신
"우리는 기술로 더 나은 세상을 만들고, 사람과 사람, 사람과 기술을 연결합니다."
- 카카오 기술 조직
마지막 업데이트: 2025-01-28