🏢 카카오 기술 스택 분석

32 min read
kakaojavakotlinspringmysqlhbasekafkakubernetesmessagingfintechsuper-app

카카오가 5천만 한국인의 일상을 연결하는 기술 스택 심층 분석 - Java, Kotlin, MySQL로 구축한 국민 메신저부터 슈퍼앱까지

🏢 카카오 기술 스택 분석

한국인의 일상 속 모든 서비스를 하나로 연결하는 카카오의 기술 스택을 심층 분석합니다.
"Connect Everything" - 카카오톡에서 시작해 슈퍼앱으로 진화한 기술 여정


📊 회사 개요

서비스 규모

  • 월간 활성 사용자: 4,700만+ 명 (한국 인구 90%+)
  • 일일 메시지: 100억+ 건
  • 서비스: 100+ 개 (톡, 페이, 택시, 뱅크 등)
  • 일일 API 호출: 2,000억+ 건
  • 데이터 처리량: 일일 10PB+

엔지니어링 조직

  • 개발자: 4,000명+ (전체 직원 10,000+)
  • 문화: "같이가치" (함께의 가치)
  • 조직: 크루 단위 자율 운영
  • 오픈소스: 100+ 프로젝트 공개

기술적 도전과제

  1. 대규모 트래픽: 전국민이 사용하는 서비스
  2. 실시간성: 밀리초 단위 메시징
  3. 안정성: 국가 기반 서비스 수준
  4. 다양성: 100+ 서비스 통합

🏗️ 아키텍처 Overview

시스템 다이어그램

┌─────────────┐     ┌─────────────┐     ┌─────────────┐
│  KakaoTalk  │────►│    LOCO     │────►│   Message   │
│   Clients   │     │  Protocol   │     │   Server    │
└─────────────┘     └─────────────┘     └─────────────┘
       │                    │                    │
       │              ┌─────▼─────┐       ┌─────▼─────┐
       │              │   Kapi     │       │   MySQL    │
       │              │   Gateway  │       │  Cluster   │
       │              └───────────┘       └───────────┘
       │                    │                    │
       └────────────────────┤                    │
                      ┌─────▼─────┐       ┌─────▼─────┐
                      │   Daum     │       │   HBase    │
                      │  Platform  │       │   Store    │
                      └───────────┘       └───────────┘

핵심 설계 원칙

  1. Service Mesh: 마이크로서비스 아키텍처
  2. Multi-Tenancy: 서비스별 독립 운영
  3. Horizontal Scaling: 수평 확장 우선
  4. Fault Tolerance: 장애 격리
  5. 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조원+

🎓 카카오에서 배울 점

✅ 적용 가능한 패턴

  1. 독자 프로토콜: 특화된 요구사항 해결
  2. 서비스 메시: 대규모 마이크로서비스
  3. 통합 플랫폼: 슈퍼앱 아키텍처
  4. 지역화: 로컬 최적화
  5. 오픈 이노베이션: 개발자 생태계

❌ 주의사항

  1. 복잡성: 100+ 서비스 관리
  2. 레거시: 오래된 시스템과 공존
  3. 규제: 금융, 개인정보 보호
  4. 경쟁: 글로벌 플랫폼과 경쟁

📚 추천 리소스


🔮 미래 전망

현재 집중 분야

  1. AI 플랫폼: 카카오 브레인, GPT 경쟁
  2. 블록체인: Klaytn 생태계 확장
  3. 모빌리티: 자율주행, 스마트 모빌리티
  4. 헬스케어: 디지털 헬스케어 플랫폼
  5. 메타버스: 가상 세계 플랫폼

기술 투자 영역

  • 초거대 AI: 한국어 특화 LLM
  • Web3: 탈중앙화 서비스
  • 엣지 컴퓨팅: 더 빠른 응답
  • 양자 컴퓨팅: 미래 보안
  • 6G: 차세대 통신

"우리는 기술로 더 나은 세상을 만들고, 사람과 사람, 사람과 기술을 연결합니다."

  • 카카오 기술 조직

마지막 업데이트: 2025-01-28

Found this helpful? Share it with others!
Tweet

🔗 Related Content

You might also be interested in these articles

🏢 company

🏢 배달의민족 기술 스택 분석

배달의민족이 한국 배달 문화를 혁신하며 동남아로 확장한 기술 스택 심층 분석 - Java, Spring, Kotlin으로 구축한 O2O 플랫폼

51 min read
baemin, woowa +10
Read more
🏢 company

🏢 Airbnb 기술 스택 분석

에어비앤비가 전 세계 400만 개의 숙소를 연결하는 글로벌 마켓플레이스 기술 스택 심층 분석 - Ruby on Rails에서 Service-Oriented Architecture로의 진화

24 min read
airbnb, ruby +12
Read more
🏢 company

🏢 네이버 기술 스택 분석

네이버가 대한민국 최대 검색 포털에서 글로벌 기술 기업으로 성장한 기술 스택 심층 분석 - Java, Spring, Hadoop으로 구축한 초대규모 플랫폼

34 min read
naver, java +10
Read more
🏢 company

🏢 Stripe 기술 스택 분석

스트라이프가 전 세계 수백만 비즈니스의 결제를 처리하는 기술 스택 심층 분석 - Ruby, Go, Java로 구축한 금융 인프라의 미래

33 min read
stripe, ruby +11
Read more
🏢 company

🏢 토스 기술 스택 분석

토스가 한국 금융을 혁신하며 유니콘으로 성장한 기술 스택 심층 분석 - Node.js, React Native, Kotlin으로 만든 모바일 금융 플랫폼

39 min read
toss, nodejs +8
Read more

Found this helpful?

Help us improve this content by contributing on GitHub or sharing your feedback with the community.