문제 정의

운전과 같이 사용자가 휴대폰을 사용하지 말아야 하는 상황에서 사용자가 휴대폰을 사용하는 내역을 파악하는 기능을 개발하고자 하는 필요가 있었다. 그래서, 다음과 같은 Prompt로 가능한 방법에 대해서 제안을 해달라고 요청했다.

"너는 pytorch 및 tensorflow의 전문가이고, 동영상 처리에 대한 전문가야. Android에 대해서도 잘 알고 있는 전문가야. 휴대폰의 사용자가 휴대폰 사용하는 동안 어떤 어플리케이션을 사용하고 있는지 알아 내는 모듈을 개발하려고해. 이를 알아내는 방법을 여러가지 제안해줘"

이에 대해서 다음 사항들을 제안했다.

  1. Accessibility Services 활용
  2. UsageStatsManager 활용
  3. Foreground Service 및 Activity Recognition API
  4. Logcat 활용 (루팅된 기기)
  5. Screen Overlay 탐지 (Android 10 이하)
  6. Custom System-level API 개발 (OEM 권한 필요)
  7. ML 기반 비디오/스크린 분석 (실험적)

특히, 다음 제안과 권장 사항도 제공받았다.

  • 보안 및 개인정보 보호 우선: 민감한 정보를 다루는 작업이므로 사용자 동의를 확실히 받는 것이 중요합니다.
  • 기술적 가능성 검토: Android 버전, 권한 정책, 배터리 소모 등을 면밀히 분석하세요.
  • 효율적인 구현: UsageStatsManager와 Accessibility Service의 조합을 1차적으로 추천합니다.

초기 Prompting

제안 받은 데로, UsageStatsManager를 사용하려고 했고, 조금 더 쉽게 접근하기 위해서 아래 Prompting으로 시작했다. 특히, 우리 협력 팀 중 하나가 유사한 기능을 UsageStatsManager를 사용하여 구현하였다.

 

"너는 android application 개발자야. 다음 요구 사항을 만족하는 application을 만들어줘. 추가적으로 필요한 정보가 있다면, 확인하고 제안해줘. 프로그램을 작성하는 순서를 상세히 설명해줘.

  1. MainActivity를 가지고 있어
  2. LifecycleService를 가지고 있어. MainActivity에서 이 Service를 구동해
  3. MainActivity에는 Start 버튼을 가지고 있어. 이 버튼을 클릭하면, Service 동작 시간을 측정 시작해. 그리고, Start 버튼이 Stop 버튼으로 변경돼.
  4. MainActivity의 Stop 버튼을 클릭하면, 휴대폰에서 사용자가 다른 어플리케이션을 사용한 내용을 정리해서 알려주는 기능을 가지고 있어. 이 부분은 UsageStatsManager에서 사용 이력을 확인할 수 있어."

제공받은 코드로 테스트를 했지만, Service로 구동시키는 것이 쉽지 않아, MainActivity에서 버튼으로 시작하고 Stop으로 마무리 하도록 했다.

추가 진행

"UsageStatsHelper의 getUsageStats의 기능을 개선해줘. MainActivity에서 Start 버턴을 누른 시간 부터 Stop 버튼 누른 시간 사이에 사용된 앱에 대해서 정보를 출력해줘. 사용시간이 0 이상인 package 정보만 출력해줘. 특정 package의 경우는 제외할 수 있도록 해줘."

UsageStatsHelper.kt 을 다음과 같이 받았다.

import android.app.usage.UsageStats
import android.app.usage.UsageStatsManager
import android.content.Context
import android.content.pm.PackageManager
import java.text.SimpleDateFormat
import java.util.*

object UsageStatsHelper {

    // 특정 패키지를 제외할 수 있도록 리스트를 설정
    private val excludedPackages = setOf(
       "com.android.systemui" // 시스템 UI는 보통 제외하는 것이 좋음
    )

    fun getUsageStats(context: Context, startTime: Long, endTime: Long): String {
        val usageStatsManager =
            context.getSystemService(Context.USAGE_STATS_SERVICE) as UsageStatsManager

        val usageStatsList: List<UsageStats> = usageStatsManager.queryUsageStats(
            UsageStatsManager.INTERVAL_BEST, startTime, endTime
        )

        if (usageStatsList.isEmpty()) {
            return "사용 기록 없음. 권한이 필요할 수 있습니다."
        }

        val packageManager = context.packageManager
        val sdf = SimpleDateFormat("HH:mm:ss", Locale.getDefault())
        val result = StringBuilder()

        // 사용 시간이 0보다 크고 제외 대상이 아닌 앱만 필터링
        val filteredStats = usageStatsList
            .filter { it.totalTimeInForeground > 0 && it.packageName !in excludedPackages }
            .sortedByDescending { it.totalTimeInForeground } // 가장 많이 사용한 앱 순으로 정렬

        if (filteredStats.isEmpty()) {
            return "해당 시간 동안 사용된 앱이 없습니다."
        }

        filteredStats.forEach { stat ->
            val packageName = stat.packageName
            val appName = getAppName(packageManager, packageName)
            val lastUsed = sdf.format(Date(stat.lastTimeUsed))
            val totalTime = formatTime(stat.totalTimeInForeground)

            result.append("$appName ($packageName) 사용 시간: $totalTime (마지막 사용: $lastUsed)\n")
        }

        return result.toString()
    }

    // 패키지명으로부터 앱 이름을 가져오는 함수
    private fun getAppName(packageManager: PackageManager, packageName: String): String {
        return try {
            val applicationInfo = packageManager.getApplicationInfo(packageName, 0)
            packageManager.getApplicationLabel(applicationInfo).toString()
        } catch (e: PackageManager.NameNotFoundException) {
            packageName // 앱 이름을 가져오지 못하면 패키지명 반환
        }
    }

    // 밀리초 단위를 "시간:분:초" 형식으로 변환하는 함수
    private fun formatTime(milliseconds: Long): String {
        val seconds = milliseconds / 1000
        val hours = seconds / 3600
        val minutes = (seconds % 3600) / 60
        val remainingSeconds = seconds % 60

        return String.format("%02d:%02d:%02d", hours, minutes, remainingSeconds)
    }
}

실재로 App 사용된 시간 파악

여러가지 테스트를 하고 알게된 것도 많았다. 가장 중요한 이슈는 우리가 파악하고자 하는 기간 동안에 실재로 app이 사용된 duration보다 더 길게 나오는 이슈가 있었다.

"startTime과 endTime의 duration보다 실재 사용한 duration이 더 길게 나오는 것으로 보여. 위의 queryUsageStats의 lastUsedTime을 이용해서 startTime과 endTime 사이 사용된 앱을 찾아내줘. 그리고, 다른 방법으로 startTime과 endTime 사이에서 실재로 app이 사용된 시간을 파악해서 출력할 수 있도록 getUsageStats 함수를 업데이트 해줘."

import android.app.usage.UsageStats
import android.app.usage.UsageStatsManager
import android.content.Context
import android.content.pm.PackageManager
import java.text.SimpleDateFormat
import java.util.*

object UsageStatsHelper {

    // 특정 패키지를 제외할 수 있도록 리스트 설정
    private val excludedPackages = setOf(
       "com.android.systemui" // 시스템 UI 제외
    )

    fun getUsageStats(context: Context, startTime: Long, endTime: Long): String {
        val usageStatsManager =
            context.getSystemService(Context.USAGE_STATS_SERVICE) as UsageStatsManager

        val usageStatsList: List<UsageStats> = usageStatsManager.queryUsageStats(
            UsageStatsManager.INTERVAL_BEST, startTime, endTime
        )

        if (usageStatsList.isEmpty()) {
            return "사용 기록 없음. (데이터가 업데이트되지 않았을 수 있음)"
        }

        val packageManager = context.packageManager
        val sdf = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault())
        val result = StringBuilder()

        // ✅ `lastTimeUsed`를 기반으로 `startTime ~ endTime` 사이에 실제 사용된 앱만 필터링
        val filteredStats = usageStatsList.filter { 
            it.totalTimeInForeground > 0 &&
            it.lastTimeUsed in startTime..endTime &&  // 마지막 사용 시간이 startTime ~ endTime 사이에 있어야 함
            it.packageName !in excludedPackages
        }.sortedByDescending { it.totalTimeInForeground } // 가장 많이 사용한 앱 순으로 정렬

        if (filteredStats.isEmpty()) {
            return "해당 시간 동안 사용된 앱이 없습니다."
        }

        filteredStats.forEach { stat ->
            val packageName = stat.packageName
            val appName = getAppName(packageManager, packageName)
            val lastUsed = sdf.format(Date(stat.lastTimeUsed))

            // ✅ 실재로 `startTime ~ endTime` 사이에서 사용된 시간을 계산
            val actualUsageTime = calculateActualUsageTime(stat, startTime, endTime)
            val formattedTime = formatTime(actualUsageTime)

            result.append("$appName ($packageName) 사용 시간: $formattedTime (마지막 사용: $lastUsed)\n")
        }

        return result.toString()
    }

    // ✅ 앱이 `startTime ~ endTime` 사이에서 실재로 사용된 시간을 계산하는 함수
    private fun calculateActualUsageTime(stat: UsageStats, startTime: Long, endTime: Long): Long {
        val lastUsed = stat.lastTimeUsed
        val totalTime = stat.totalTimeInForeground

        // ✅ `lastTimeUsed`가 `startTime ~ endTime`에 포함된 경우
        return if (lastUsed in startTime..endTime) {
            // 앱이 `startTime` 이전부터 실행 중이었다면 `startTime`부터 사용 시간 계산
            val adjustedStartTime = maxOf(startTime, lastUsed - totalTime)
            val adjustedEndTime = minOf(endTime, lastUsed)
            adjustedEndTime - adjustedStartTime
        } else {
            0L // 사용되지 않은 경우 0 반환
        }
    }

    // 패키지명으로부터 앱 이름을 가져오는 함수
    private fun getAppName(packageManager: PackageManager, packageName: String): String {
        return try {
            val applicationInfo = packageManager.getApplicationInfo(packageName, 0)
            packageManager.getApplicationLabel(applicationInfo).toString()
        } catch (e: PackageManager.NameNotFoundException) {
            packageName // 앱 이름을 가져오지 못하면 패키지명 반환
        }
    }

    // 밀리초 단위를 "시간:분:초" 형식으로 변환하는 함수
    private fun formatTime(milliseconds: Long): String {
        val seconds = milliseconds / 1000
        val hours = seconds / 3600
        val minutes = (seconds % 3600) / 60
        val remainingSeconds = seconds % 60

        return String.format("%02d:%02d:%02d", hours, minutes, remainingSeconds)
    }
}

두 번째 개선

"calculateActualUsageTime을 UsageStatsManager.queryEvents(startTime, endTime)를 사용하여 앱의 실행 이벤트(ACTIVITY_RESUMED, ACTIVITY_PAUSED)를 추적해서 계산해줘."

import android.app.usage.UsageEvents
import android.app.usage.UsageStats
import android.app.usage.UsageStatsManager
import android.content.Context
import android.content.pm.PackageManager
import java.text.SimpleDateFormat
import java.util.*

object UsageStatsHelper {

    // 특정 패키지를 제외할 수 있도록 리스트 설정
    private val excludedPackages = setOf(
       "com.android.systemui" // 시스템 UI 제외
    )

    fun getUsageStats(context: Context, startTime: Long, endTime: Long): String {
        val usageStatsManager =
            context.getSystemService(Context.USAGE_STATS_SERVICE) as UsageStatsManager

        val usageStatsList: List<UsageStats> = usageStatsManager.queryUsageStats(
            UsageStatsManager.INTERVAL_BEST, startTime, endTime
        )

        if (usageStatsList.isEmpty()) {
            return "사용 기록 없음. (데이터가 업데이트되지 않았을 수 있음)"
        }

        val packageManager = context.packageManager
        val sdf = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault())
        val result = StringBuilder()

        // ✅ `lastTimeUsed`를 기반으로 `startTime ~ endTime` 사이에 실제 사용된 앱만 필터링
        val filteredStats = usageStatsList.filter { 
            it.totalTimeInForeground > 0 &&
            it.lastTimeUsed in startTime..endTime &&  // 마지막 사용 시간이 startTime ~ endTime 사이에 있어야 함
            it.packageName !in excludedPackages
        }.sortedByDescending { it.totalTimeInForeground } // 가장 많이 사용한 앱 순으로 정렬

        if (filteredStats.isEmpty()) {
            return "해당 시간 동안 사용된 앱이 없습니다."
        }

        filteredStats.forEach { stat ->
            val packageName = stat.packageName
            val appName = getAppName(packageManager, packageName)
            val lastUsed = sdf.format(Date(stat.lastTimeUsed))

            // ✅ `queryEvents()`를 사용하여 실제 사용된 시간을 계산
            val actualUsageTime = calculateActualUsageTime(context, packageName, startTime, endTime)
            val formattedTime = formatTime(actualUsageTime)

            result.append("$appName ($packageName) 사용 시간: $formattedTime (마지막 사용: $lastUsed)\n")
        }

        return result.toString()
    }

    // ✅ `queryEvents()`를 사용하여 앱의 실행 이벤트 추적
    private fun calculateActualUsageTime(context: Context, packageName: String, startTime: Long, endTime: Long): Long {
        val usageStatsManager =
            context.getSystemService(Context.USAGE_STATS_SERVICE) as UsageStatsManager

        val events = usageStatsManager.queryEvents(startTime, endTime)
        val event = UsageEvents.Event()

        var totalUsageTime = 0L
        var lastResumedTime: Long? = null

        // 이벤트 스트림을 읽어가면서 ACTIVITY_RESUMED 및 ACTIVITY_PAUSED를 분석
        while (events.hasNextEvent()) {
            events.getNextEvent(event)

            if (event.packageName == packageName) {
                when (event.eventType) {
                    UsageEvents.Event.ACTIVITY_RESUMED -> {
                        lastResumedTime = event.timeStamp
                    }
                    UsageEvents.Event.ACTIVITY_PAUSED -> {
                        if (lastResumedTime != null) {
                            val usageDuration = event.timeStamp - lastResumedTime
                            if (usageDuration > 0) {
                                totalUsageTime += usageDuration
                            }
                        }
                        lastResumedTime = null
                    }
                }
            }
        }

        return totalUsageTime
    }

    // 패키지명으로부터 앱 이름을 가져오는 함수
    private fun getAppName(packageManager: PackageManager, packageName: String): String {
        return try {
            val applicationInfo = packageManager.getApplicationInfo(packageName, 0)
            packageManager.getApplicationLabel(applicationInfo).toString()
        } catch (e: PackageManager.NameNotFoundException) {
            packageName // 앱 이름을 가져오지 못하면 패키지명 반환
        }
    }

    // 밀리초 단위를 "시간:분:초" 형식으로 변환하는 함수
    private fun formatTime(milliseconds: Long): String {
        val seconds = milliseconds / 1000
        val hours = seconds / 3600
        val minutes = (seconds % 3600) / 60
        val remainingSeconds = seconds % 60

        return String.format("%02d:%02d:%02d", hours, minutes, remainingSeconds)
    }
}

결과의 포맷 정리

사용한 앱들의 정리를 위해서 Map형태로 정리하기로 하였다.

"Application 사용시간을 확인할 때, getUsageStats의 결과 물로 application 이름, package 이름 그리고 각 앱의 사용 시작, 사용 끝 시간을 포함하는 Map형태로 반환되도록 구성해줘."

import android.app.usage.UsageEvents
import android.app.usage.UsageStatsManager
import android.content.Context
import android.content.pm.PackageManager
import java.text.SimpleDateFormat
import java.util.*

object UsageStatsHelper {

    // 특정 패키지를 제외할 수 있도록 리스트 설정
    private val excludedPackages = setOf(
       "com.android.systemui" // 시스템 UI 제외
    )

    /**
     * 📌 사용된 애플리케이션 정보를 반환하는 함수
     * @return Map<String, List<Pair<Long, Long>>>
     * - Key: 앱 이름 (패키지 이름 포함)
     * - Value: (사용 시작 시간, 사용 종료 시간) 리스트
     */
    fun getUsageStats(context: Context, startTime: Long, endTime: Long): Map<String, List<Pair<Long, Long>>> {
        val usageStatsManager =
            context.getSystemService(Context.USAGE_STATS_SERVICE) as UsageStatsManager

        val packageManager = context.packageManager
        val result = mutableMapOf<String, MutableList<Pair<Long, Long>>>()

        // ✅ `queryEvents()`를 사용하여 ACTIVITY_RESUMED 및 ACTIVITY_PAUSED 추적
        val usageEvents = usageStatsManager.queryEvents(startTime, endTime)
        val event = UsageEvents.Event()

        val activeApps = mutableMapOf<String, Long>() // 현재 실행 중인 앱 저장

        while (usageEvents.hasNextEvent()) {
            usageEvents.getNextEvent(event)

            val packageName = event.packageName
            if (packageName in excludedPackages) continue // 제외할 패키지 필터링

            val appName = getAppName(packageManager, packageName)

            when (event.eventType) {
                UsageEvents.Event.ACTIVITY_RESUMED -> {
                    activeApps[packageName] = event.timeStamp // 사용 시작 시간 저장
                }
                UsageEvents.Event.ACTIVITY_PAUSED -> {
                    val startTimestamp = activeApps.remove(packageName) ?: continue // 시작 시간이 없으면 패스
                    val endTimestamp = event.timeStamp
                    result.getOrPut("$appName ($packageName)") { mutableListOf() }
                        .add(Pair(startTimestamp, endTimestamp)) // 시작, 종료 시간 저장
                }
            }
        }

        return result
    }

    // 패키지명으로부터 앱 이름을 가져오는 함수
    private fun getAppName(packageManager: PackageManager, packageName: String): String {
        return try {
            val applicationInfo = packageManager.getApplicationInfo(packageName, 0)
            packageManager.getApplicationLabel(applicationInfo).toString()
        } catch (e: PackageManager.NameNotFoundException) {
            packageName // 앱 이름을 가져오지 못하면 패키지명 반환
        }
    }
}

마무리 하며...

이전에도 설명했듯이, 진행하면서 학습이 발생하기 때문에 여러 iteration을 돌리는 것이 중요하다는 것을 다시금 깨닫게 된다. 중간에 다른 인터페이스도 사용해보고, 테스트하면서 알게된 부분을 가지고 계속 개선하면서 현재까지 결과를 얻었다. 이 코드를 가지고 팀원들에게 피드백을 받으면서 진행하는 것이 필요한 부분이다.

들어가며

최근 ChatGPT와 같은 대규모 언어 모델(LLM) 도구들이 보편화되면서, 이를 학습에 활용하는 것에 대한 우려의 목소리도 함께 커지고 있다. “AI가 대신 글을 써주면, 학생들이 스스로 사고하고 표현할 기회를 잃는 것 아니냐”는 걱정이다. 일예로, LLM 분야의 대가인 제프리 힌튼은, 앞으로는 많은 지식 노동이 대체될 수 있으며 배관공(Plumber)과 같은 수작업 직업이 더 나은 선택이 될 수 있다고 경고하기도 했다.

 

하지만 나는 이 우려가, 마치 자동차와 비행기가 등장했을 때 “사람들이 걷지 않아 건강을 해칠 것”이라고 걱정하던 상황과 닮아 있다고 생각한다. 그 이후에도 우리는 여전히 걷고, 달리고, 운동하며 몸을 단련해왔다. 중요한 것은 도구의 유무가 아니라, 도구를 어떻게 사용하는가이다.

나의 활용 방식

AI를 활용하는 방식에는 여러 스펙트럼이 존재한다. 그 중 두 가지 대표적인 활용 방식이 자주 떠오른다. 하나는 아이언맨과 자비스, 다른 하나는 프로도와 간달프 의 관계이다.

 

아이언맨은 자신이 잘 아는 기술과 전투의 세계에서 자비스의 도움을 받아 더 빠르고, 더 효율적으로 문제를 해결한다. 자비스는 아이언맨을 대신해 사고하지 않는다. 대신 반복적인 작업을 덜어주고, 그의 아이디어를 구현하는 데 필요한 실현 가능성과 속도를 높여준다. 이 과정 속에서도 아이언맨은 계속해서 학습하고 성장한다. AI는 내 사고력을 대체하는 것이 아니라, 내 역량을 증폭하는 파트너다. 이와 관련해 박태웅 한빛미디어 의장도 최근 LLM은 ‘증폭기(amplifier)’와 같은 존재라고 언급한 바 있다. 이 말에 깊이 공감한다.

 

반면, 프로도의 여정은 더 탐험에 가깝다. 그는 낯선 세계로 떠나며, 어떤 위험과 교훈이 기다리는지 알지 못한다. 이때 간달프는 모든 것을 대신해주는 존재가 아니라, 방향을 제시하고 조언을 건네며 프로도가 스스로 성장할 수 있도록 돕는다.
이는 내가 아직 익숙하지 않은 도메인에서 AI의 도움을 받으며 시행착오를 겪고 배워가는 모습과 닮아 있다.

개발 영역에서는 이러한 두 가지 접근이 “Vibe Coding”이라는 흐름 속에 잘 드러난다. 익숙한 도메인에서는 LLM과 협업하여 더 빠르게 프로토타입을 만들고, 반복 실험과 피드백을 통해 인사이트를 얻는다. 낯선 기술을 다룰 때는, AI가 멘토이자 실험 파트너가 되어 나의 몰입과 탐험을 도와준다.

 

이 모든 과정에서 가장 중요한 것은 “Vibe”, 즉 몰입과 호기심이다. 일이 너무 쉬우면 흥미를 잃고, 너무 어렵다면 지치고 포기하게 된다. 그래서 탐험할 때는 단계별(iterative)로 도전 과제를 나누고, 적절한 난이도를 유지하는 것이 핵심이다. 또한, AI가 생성한 결과를 그저 수동적으로 받아들이는 데 그쳐선 안 된다. 예를 들어, Cursor 같은 도구를 활용해 코드를 실행해보고 에러를 분석하거나, 내가 이해한 방식대로 코드를 수정해 다시 피드백을 주는 과정이 중요하다. 피드백을 주고받는 상호작용 속에서 비로소 진짜 학습이 일어난다.

맺으며

AI 시대의 학습은 ‘무기력하게 도움을 받는 것’이 아니다. 강력한 도구와 협업하면서 몰입하는 경험이다. 자비스처럼 효율을 높이거나, 간달프처럼 새로운 세계로 이끌려 가며 우리는 더 나은 배움을 할 수 있다. 중요한 것은, 그 안에 나의 호기심, 열정, 주도권이 살아 있느냐이다.

 

변화는 빠르다. 이러한 때에 불안함을 느끼지 않는 사람이 얼마나 될까? 하지만 AI를 두려워하기보다, 내 성장의 증폭기로 삼을 수 있다면 그것은 결코 위협이 아닌 기회가 될 것이다. 머리 속으로 되내어 보자. "이거 흥미로운데?"

문제 정의

내가 가지고 있는 Synology NAS에서 서버 처럼 news를 요약해서 정리하는 것을 만들고 싶었다. Discord와 연동도 좋지만, Obsidian을 개인 지식 관리로 쓰고 있고 이 Obsidian의 저장소를 OneDrive로 하고 있어서 이 부분을 활용하고 싶었다.

"Synology NAS에서 매일 아침에 news crawling을 해서, markdown file로 특정 폴더 파일에 요약 정보 및 링크를 요약해서 저장하는 기능을 넣고 싶어. 어떻게 구성할지 몇가지 방법을 제안해줘."

우선 방안에 대해서 알고 싶어서 위와 같이 Prompting을 하였다. 그리고, 제안한 방법들의 요약을 정리해 보면 다음과 같다.

  1. Synology Task Scheduler + Python 스크립트
  2. Docker 컨테이너 활용
  3. Crawlab 활용 (Docker 기반 크롤링 플랫폼)
  4. Obsidian 플러그인 활용

2번 3번은 알고 있는 지식이 거의 없어서 난이도가 높았다. 4번은 자세히 읽었을 때에는 Synology로 하는 방법이 아니었다. 1번이 requests, BeautifulSoup, markdownify와 같은 들어 본적이 있는 python package를 사용하는 방법이지 시도해보기로 했다.

처음 코드 생성

다음과 같은 Prompting으로 첫 코드를 얻었다.

"방법 1에서 Ptyno 스크립트 실행하는 방법을 detail하게 설명해줘. 어떤 python script를 사용해야 하는지 알려주고, 실재로 Synology에서 python script 테스트 하는 방법도 알려줘. 그리고, Task Scheduler에 해당 script 등록하고 테스트해 보는 방법도 상세하게 알려줘."

생성해준 코드를 적용하기 위한 환경 설정도 물어 보면서, pip라든과 관련된 package를 설치하고 테스트를 했다. 한번에 동작하지 않았다. 처음 제공한 파일들은 관련된 method만 제공하고 실제로 그 method를 호출하는 main 코드가 없어서 추가 요청을 했다.

디버깅

추가적인 동작확인을 위해서 다음과 같이 Prompting을 추가로 진행했다.

"위에서 이야기 한데로 스크립트를 실행했지만, 결과물은 나오지 않았어. 예를 들어 naver의 경우에도 링크를 만들어서 browser에서 열어 보면 결과물도 잘 나왔어. 하지만, 추출된 뉴스는 하나도 없는데 어떻게 디버깅을 할지 알려줘."

관련해서 중간 생성하는 debugging용 html 파일을 저장하게 했다. 이 html을 넣으면서 naver, daum, google의 분석을 하는 것을 디버깅했다. daum은 뉴스를 추출하지 못했고, naver는 링크를 잘 가져오지만 역시 dynamic이어서 요약까지 잘 하지 못했다. 그래도, google은 잘 처리해 주어서 최종 코드를 얻을 수 있었다.

최종 코드

지금까지 정리된 코드를 다음과 같이 얻었다.

# news_summary.py

import requests
from bs4 import BeautifulSoup
from openai import OpenAI
from datetime import datetime
import os

# 전역 디버그 설정
DEBUG_MODE = False
DEBUG_PATH = "/volume2/backups/scripts/debug/"

headers = {'User-Agent': 'Mozilla/5.0'}

def get_naver_news(query, max_results=3):
    url = f"https://search.naver.com/search.naver?where=news&query={query}"
    res = requests.get(url, headers=headers)

    # 디버깅 HTML 저장
    if DEBUG_MODE:
        with open(f"{DEBUG_PATH}naver_debug.html", "w", encoding="utf-8") as f:
            f.write(res.text)
    soup = BeautifulSoup(res.text, 'html.parser')
    items = soup.select('a[href^="https://n.news.naver.com/"]')[:max_results]
    
    # 디버깅 뉴스 갯수 출력
    if DEBUG_MODE:
        print(f"[NAVER] 뉴스 개수: {len(items)}")
    return [(a.text.strip(), a['href']) for a in items]

def get_google_news(query, max_results=3):
    url = f"https://news.google.com/search?q={query}&hl=ko&gl=KR&ceid=KR:ko"
    headers = {
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
                      "AppleWebKit/537.36 (KHTML, like Gecko) "
                      "Chrome/113.0.0.0 Safari/537.36"
    }
    res = requests.get(url, headers=headers)

    # DEBUG 모드 설정
    if DEBUG_MODE:
        with open(os.path.join(DEBUG_PATH, "google_debug.html"), "w", encoding="utf-8") as f:
            f.write(res.text)
    soup = BeautifulSoup(res.text, "html.parser")
    items = soup.select("a.JtKRv")[:max_results]
    news_list = []
    for a in items:
        title = a.text.strip()
        href = a.get("href", "")
        # 상대경로를 절대경로로 변경
        if href.startswith("./"):
            href = "https://news.google.com" + href[1:]
        news_list.append((title, href))
    if DEBUG_MODE:
        print(f"[Google] 뉴스 개수: {len(news_list)}")
    return news_list

def fetch_article_content(url):
    try:
        res = requests.get(url, headers=headers, timeout=5)
        soup = BeautifulSoup(res.text, 'html.parser')
        paragraphs = soup.select('article p')
        text = '\n'.join([p.text.strip() for p in paragraphs if p.text.strip()])
        return text[:1000]
    except Exception as e:
        return f"(본문 수집 실패: {e})"

def fetch_naver_article_content(url):
    try:
        headers = {
            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)',
            'Referer': 'https://www.naver.com'
        }
        res = requests.get(url, headers=headers, timeout=5)
        soup = BeautifulSoup(res.text, 'html.parser')
        article = soup.select_one('article')
        if article:
            paragraphs = article.find_all(['p', 'div'])
            text = '\n'.join([p.get_text(strip=True) for p in paragraphs if p.get_text(strip=True)])
            return text[:2000]
        else:
            return "(기사 본문을 찾을 수 없음)"
    except Exception as e:
        return f"(본문 수집 실패: {e})"

def summarize_with_gpt(title, url):
    client = OpenAI(api_key=os.getenv("OPENAI_API_KEY") or "YOUR KEY")
    if "n.news.naver.com" in url:
        content = fetch_naver_article_content(url)
        prompt = f"다음은 뉴스 기사입니다. 기사의 제목을 첫 줄에 포함해주세요. 그리고, 핵심 내용을 3줄 이내로 요약해 주세요.\n\n제목: {title}\n본문:\n{content}"
    else:
        content = fetch_article_content(url)
        prompt = f"다음은 뉴스 기사입니다. 핵심 내용을 3줄 이내로 요약해 주세요:\n\n제목: {title}\n내용: {content}"
    try:
        response = client.chat.completions.create(
            model="gpt-3.5-turbo",
            messages=[{"role": "user", "content": prompt}]
        )
        return response.choices[0].message.content.strip()
    except Exception as e:
        return f"(요약 실패: {e})"

def run_news_summary(query):
    result = {"네이버": [], "구글": []}
    for portal, func in [("네이버", get_naver_news), ("구글", get_google_news)]:
        try:
            for title, url in func(query):
                summary = summarize_with_gpt(title, url)
                result[portal].append((title, url, summary))
        except Exception as e:
            result[portal].append(("수집 실패", "#", str(e)))
    return result

def save_all_queries_to_markdown(all_results, all_queries, output_dir="/volume2/backups/Obsidian/Newbie/0. Inbox"):
    today = datetime.now().strftime('%Y-%m-%d')
    filename = os.path.join(output_dir, f"multi_news_ALL_{today}.md")
    with open(filename, 'w', encoding='utf-8') as f:
        f.write(f"# 📰 {today} - 전체 키워드 뉴스 요약\n\n")
        for query in all_queries:
            f.write(f"\n---\n\n## 🔍 키워드: {query}\n\n")
            result = all_results[query]
            for portal, items in result.items():
                f.write(f"### 🔎 {portal} 뉴스\n")
                for title, url, summary in items:
                    f.write(f"- [{title}]({url})\n  > {summary}\n\n")
    return filename

if __name__ == "__main__":
    query_file = "/volume2/backups/Obsidian/Newbie/news_query_temrs.md"
    if not os.path.exists(query_file):
        print(f"❌ 쿼리 파일 없음: {query_file}")
    else:
        with open(query_file, 'r', encoding='utf-8') as f:
            content = f.read()
        queries = [q.strip() for q in content.split(',') if q.strip()]

        all_results = {}
        for q in queries:
            print(f"▶️ 수집 시작: {q}")
            result = run_news_summary(q)
            all_results[q] = result

        final_path = save_all_queries_to_markdown(all_results, queries)
        print(f"✅ 전체 결과 저장 완료: {final_path}")

마무리

위의 코드를 Task Scheduler에 등록하고 매일 아침에 관련된 폴더에 위치하게 하고 이 폴더의 파일을 One Drive와 싱크해서 Mobile의 Obidian에서 접근하도록 했다. 여전히 naver는 링크가 잘 못되기도 하고, 제목도 제데로 뽑지 못하기도 하다. 결국은 Selenium을 쓰라고 하는데, Synology는 X11 GUI를 미지원 하기 때문에 Docker 사용을 제안 받았다. 이 부분은 현재로는 허들이 높아서 단계별로 시도해보기로 한다.

ChatGPT를 학습 측면으로 많이 쓰다가 요즘은 Vibe Coding, 즉 개발자가 직접 코드를 작성하기보다는 자연어로 문제를 설명하면 대형 언어 모델(LLM)이 이를 기반으로 코드를 생성해서 활용하는 방식으로 활용도가 하는 증가하고 있다. 이에 대해서 몇 가지 경험들을 공유하기 전에 '들어가기' 측면에서 돌아보며 생각 났던 것을 적어 보려고 한다.

언어로서의 프로그래밍 랭귀지

얼마 전(그래도 2025년 초)에 ChatGPT에게 물었을 때, Tier 1 언어에 다양한 언어가 있다는 것을 본 적 있다. 영어 (English, 가장 자신 있다고 한다), 중국어 (Mandarin), 스페인어 (Spanish), 프랑스어 (French), 독일어 (German), 일본어 (Japanese), 한국어 (Korean)가 포함되어 있다. 특히, 예전에는 한국어로 Prompting을 하면 영어로 번역해서 결과를 가져오고 그 결과를 한글로 번역(심지어 구글 Translator를 쓰는 것도 봤었다)해서 결과를 내었었다. 그래서 그런지, 요즘은 한글로 Prompting을 해도 결과가 아주 좋다. 이 정도라면 언어 학습에 ChatGPT를 안쓰는 것은 바보 같은 일이 되었다고 생각된다. 요즘 영어 학습 시 내가 쓴 글에 대해서 Correct하는데 자주 사용한다.

그렇다면, Computer Language 에서도 Tier 1이 있지 않을까 궁금해서 다음과 같이 물어봤었다. "너는 소프트웨어 개발 전문 Agent야. Vibe coding을 하려는 사용자에게 도움을 줄 때, 가장 자신이 있는 컴퓨터 언어에는 어떤 것들이 있는지 알려줘." ChatGPT는 Python, JavaScript (Node.js & Browser), Processing / p5.js, Rust, Kotlin까지 5개를 이야기 했었다. Gemini는 Python (파이썬), JavaScript (자바스크립트), Java (자바), C++ (씨쁠쁠), C# (씨샵)을 언급했다. 추가로 Swift, Kotlin, PHP, Ruby, Go, TypeScript 등 다양한 현대 프로그래밍 언어에 대해서도 능숙하게 이해하고 코드를 생성하거나 설명할 수 있다고 하긴 했다. 일반 언어 학습도 마찬가지이지만, 이제 프로그래밍 랭귀지 학습도 LLM을 사용하지 않는 것은 좋은 피드백을 놓치는 일인 것이라는게 내 생각이다. 기존 코드 분석이나 내가 작성한 코드의 리뷰도 LLM으로 하면 좋다.

동어 반복이지만, Coding도 어찌 보면 글을 쓰는 일이다. 앞에서 말했듯이 ChatGPT가 좋은 언어 선생인데 활용하지 않는 것은 이상한 일이 되고 있다.

나의 개발 환경

개발을 해보려면, 환경은 중요하다. 사실 많은 개발자들이 Web 기반으로 하고 있어서 Cursor가 유용한 도구이고 Vibe Coding을 할 수 있다. 하지만, 나 같이 Mobile에서 개발을 하고 있거나, 업무 자동화와 같은 것이 유용한 사람도 있다. 이와 같이 개발 환경이 다양할 수 있다.

우선 내가 가지고 있는 환경들을 정리해 보자. 물론, 자신의 환경도 정리해 보고 추가로 확장해 나갈 수도 있겠다. 즉, 개인 프로젝트로 Cloud에 개발해 볼 수도 있는 일이다.

  • 집의 HW: Synology NAS, Laptop, 개인용으로 쓰는 Android Smart Phone이 있다. 뒤에 이야기 하겠지만, NAS가 서버 처럼 동작할 수 있어서 자동화에 활용을 할 수 있다.
  • 회사의 HW에서: Phone/Tablet등이 많고, 개인용 Windows PC도 있다.

SW적 개발 환경은 다음과 같이 이야기 할 수 있다.

  • 언어적으로는 예전에 C/C++을 했지만, 요즘은 살펴 볼 기회가 점점 적어 지고 있다.도리어 Python이 업무 자동화(Web Scrapping)와 AI 관련해서 교육 및 업무 하면서 더 많이 쓰고 있다.
  • Android를 사용해야 하기 때문에 Java/Kotlin도 보게 되는 상황이다.
  • IDE의 측면에서는 Python을 위해서 PyCharm과 Android 개발을 위해서 Android Studio를 사용한다. 일반적인 text editor인 Notepadd++도 사용한다. Linux 환경에 정말 쓸 editor가 없다면 vi를 쓰기도 한다.

이렇게 해야 한다는 것이 아니고 나의 예이다. 내가 가지고 있는 것으로 시작하면 된다. 이런 것을 잘 모르겠다? 그럼 뒤에도 설명하겠지만, 아주 간단한 사용법 부터 익히면 좋겠다.

가장 먼저 할일은? 바로 문제 정의

내가 요즘 담당하고 있는 업무 TPM이고, 개발자가 아니어서 프로그래밍은 눈으로만 보거나 관련된 글을 읽는 것이 다라고 생각하고 살았던 것 같다. Vibe Coding은 생각만 하고 있던 것들을 LLM이 Coding Agent로서 바로 내 옆에 나 대신 코딩을 해주는 전문가가 생긴 것과 마찬가지다.

이렇게 하고 싶은, 하고 싶었던 것들 떠올려 LLM에게 물어 보면 된다.

  • Android 폰의 특정 시간 (시작 시간에서 끝나는 시간 사이) 동안, 사용자가 사용한 Application과 사용한 시간을 측정하고 싶다.
  • 특정 시간에 그날 날씨 예보를 파악해서 사용자에게 Activity를 제안하고 싶다.
  • 매일 뉴스를 찾아서 내가 사용하는 메모 앱(Obsidian)에 요약해서 저장하고 싶다.
  • 관심있는 주식 전망에 대해서 현재 날짜 기준으로 최신 뉴스나 정보를 검색 예상해 준다.
  • Android 폰에서 비디오 재생하는 간단한 Sample 앱을 만들어 보고 싶다.

이러한 것들이 간단하지만 문제 정의의 시작이 될 수 있다. 위의 내용들을 그냥 ChatGPT에 먼저 적고 순차적으로 답변들을 검토하면서 진행해도 큰 문제가 없었다.

Prompting

사실 ChatGPT 같은 경우는 특별히 prompting을 예전처럼 아주 구체적으로 적지 않고, 어설프게 해도 상당히 좋은 결과들이 나온다. 하지만, 몇 번의 시도에서 느낀 것을 적어 본다.

  • 문제 해결의 여러 방법을 제안하고 비교 요청. 이 부분은 Architecturing, Designing 단계로도 볼 수 있다. 즉, 우리가 해결하고자 하는 문제에 대해서 어떤 구조, 어떤 접근 방법이 가능한지 알 수 있다.
  • 작고 쉽게 접근이 가능해야 한다. 여러 제안 중에서 너무 새롭거나 허들이 높다면, 더 세밀하게 나눠봐야 한다. Android Programming을 전혀 해보지 않았다면 Hello World라도 짜보고 접근한다거나, Docker를 전혀 해보지 않은 상태에서 이 방법을 선택하는 것은 너무 어렵다.
  • 이터레이션을 해야 한다. 제안 된 것을 ChatGPT가 실행해 볼 수도 있지만, 그렇지 않은 경우도 많다. 또한, Oudated된 정보를 가지고 있는 경우도 많다. 그렇기 때문에, 제공 받은 코드는 실행을 해봐야 한다. Cursor와 같이 IDE 환경이라면 바로 Deploy도 하고 실행도 해볼 수 있지만, ChatGPT는 그렇지지 않은 환경이라 실행하지 않으면 알기 어렵다.
  • Debugging도 도와 달라고 하면 잘 도와 준다. 분석을 위한 중간 결과물이나 실행 시 중요 정보를 출력하는 Logging이나 Print를 삽입해주기도 한다. 웹 페이지에 접근해서 내용을 가져오는 동작을 의미하는 Web Scrapping이나 Web Crawling의 경우 중간 결과물로 html을 저장하기도 하고 이 저장된 html을 제공하면 디버깅 포인트를 찾기도 한다.

이와 같이 다양한 접근 방법에 대해서 고려하고 작은 스텝으로 진행하면서 실행하여 LLM에게 Feedback하면서 진행하는 것이 유용하다.

마무리 하며

몇 번의 Vibe Coding을 하고 돌아 본 것이라 부족한 부분이 많고 나중에 생각이 바뀔 수도 있겠다. 이후에는 실재 사례들을 정리해 보겠다. 다른 하고 싶은 아이디어들이 떠오른다면, 추가적으로 실행해 볼 수 있겠다. 혹시, 같이 해보고 싶은 아이디어를 제공해 준다면, 함께 시도해 볼 수도...

TL;DR
Google Cloud Platform Professional Cloud Architect(PCA) 시험 Pass!
집에서 세팅하기 싫어서 오프라인 시험 장소가서 시험봄.
시험 준비는 Dump가 최고인 듯. 틀린 문제 반복은 Anki.

 

시험 경험

강남의 SRTC에 가서 시험보았다. 집에서 한시간 이상 걸리는 거리였지만, 집에서 설정하고 하는 것이 더 번거롭고 마침 서울에서 다른 일도 보기로 했다.

입장이 매우 깐깐했다.
시험 신청 싸이트에서 이메일로 전송하여 받은 시험번호 표시된 이메일 출력본
여권과 신용카드를 신분증 2개로 보여주었다.
시험 장소 들어가면 휴대폰 끄고 악세사리도 제거해달라고 하고 주머니를 모두 비웠다.

시험 시작하고 초반에 10문제 가까이 sample project문제 나와서 에너지가 많이 들었다. 하지만 실재로는 마지막 10분에 문제 잘 복기해서, 답을 찾았던 것 같았다. 마킹했던 7문제에서 4~5문제를 다시 수정했고, 마킹을 지워서 Review All할 때 마킹한 문제가 없어질때까지 반복했다. 완료 후, 최종 Submit 눌렀었다.

중간에 고민했을 때, 2 시간의 배분을 고민했었다. 총 50문제니, 12~13문제 30분 안에 풀면 되는 거여서 페이스를 잘 유지하기도 하고 시간도 잘 사용했던 같다. 특히, 눈이 아파서 3번 정도 눈 감고 심호흡 6~7번 해서 잘 마무리 한 것 같다.

후반에서야 문제를 특히 꼼꼼히 읽었던 것같다. Dump 문제 풀 때에도 실수 많이 하던 것 중에 하나는 문제를 꼼꼼히 읽지 않아서 키워드를 놓치는 경우가 있었다. 한문장씩, 키워드를 찾아보고 보기에서 지워 가는 형식으로 문제를 풀었다. 아까 이야기 했듯이 복기 하는 시간이 10분 정도 소요 되었다.

시험 준비

학습의 피드백을 많이 받기 위해서 기출 문제 모음으로 볼 수 있는 Dump를 풀어보기를 권한다. 물론 Cloud를 잘 모르는 상태에서 Dump 부터 풀면 정말 힘들 수도 있다. 하지만, Dump를 풀면서 아는 범위를 늘려가는게 한달 이상씩 전체를 훝는 Cloud 강의를 보면서 멍하게 시간 보내는 것 보다는 더 알찰 것 같다.

이러한 결론이 나온거는 24년 12월 부터 이런 저런 강의 사이트 보면서 시간만 보냈었다. 하지만, 기억나는 것은 그리 많지 않아서 더 그렇다.

25년 2월 부터, 본격 Dump 보기 시작한 듯 하다. PCA 문제가 조금 알게 되면 햇갈리는 보기 2개 정도에서 왔다 갔다 하는 문제가 생기기 시작한다. 정말 햇갈리게 하는 문제가 많다. 찾아 보고, 왜그런지 확인해야 한다. ChatGPT도 잘 맞추기는 하지만 Dump 답과 비교해보면 틀리기도 한다. 할루시네이션이라고 하기 보다는 문제가 정말 햇갈리는 문제 같다.

틀린 문제 반복은 Anki를 활용했다. 잊을만하면 나오고 또 틀리기도 하고, Dump Udemy에 있는 것 6개 다 풀고 Anki에 오답노트 햇갈리는 문제 등록해 놓고 반복을 했다. 4월 목표였지만, 결국은 5월 말로 신청했고 걱정도 많이 했지만, 결국은 통과!

이후는 어떻게?

문제가 조금씩 나올거라 학습을 위해서 Anki는 반복할까 한다. 2년 유효하니 지켜보자. 

가상화와 클라우드

우리가 흔히 쓰는 ‘가상화’라는 말, 단순히 “하드웨어 없이 소프트웨어로 컴퓨터를 흉내 내는 기술”이라고만 볼 수 없다. 가상화는 훨씬 더 본질적인 시스템 구조의 변화라고 할 수 있다. 가상화는 컴퓨터 자원(CPU, 메모리, 디스크 등)을 논리적으로 분리해서, 하나의 물리 자원을 여러 개의 가상 머신처럼 사용할 수 있게 해준다. 이 덕분에 우리는 아래 그림과 같이 동일한 물리 서버 안에서 여러 개의 운영체제를 동시에 돌릴 수 있어. 개발자 입장에서 테스트 환경과 운영 환경을 분리할 수 있고, 기업 입장에서는 자원 활용률을 극대화할 수 있지.

가상화[1]

 

가상화는 단순히 컴퓨터만 가상화하는 것이 아니다. 아래 처럼 앱, 네트워크, 저장소까지도 가상화가 가능하다.

다양한 가상

 

클라우드 서비스 모델

많은 사람들이 클라우드를 ‘인터넷에 있는 서버’ 정도로만 생각하는데, 사실 클라우드는 서비스 모델에 따라 굉장히 다양한 형태로 제공된다. 돼. 우리가 가상화 기술을 바탕으로 어떤 수준에서 자원을 사용할지를 기준으로, 아래 그림과 같이 클라우드는 크게 4가지로 나뉜다.

클라우드 서비스 구분[2]

 

 

아래 표에서는 각 서비스들의 특징을 간단히 설명하고 어떠한 예가 있는지 예를 보여 준다.

 

항목 IaaS PaaS FaaS SaaS
제어 범위 가장 넓음 중간 매우 좁음 없음
유연성 높음 중간 낮음 매우 낮음
개발/운영 부담 높음 중간 매우 낮음 없음
예시 서비스 AWS EC2, GCE Heroku, App Engine AWS Lambda, Azure Func Gmail, Google Docs

 

(IaaS)가상화 예제: Virtualbox Beginners Guide

IaaS(Infrastructure as a Service)는 말 그대로 인프라를 서비스로 제공하는 형태다. 이 모델에서 우리는 가상 머신을 생성하고, 운영체제를 설치하고, 네트워크 설정까지 직접 할 수 있다. 그런데 AWS EC2나 Google Cloud VM Compute Engine처럼 바로 돈이 드는 클라우드 플랫폼은 무료 테스트 계정이 있지만, 좀 부담스러러울 수 있다. 그래서 로컬에서 가상화를 체험할 수 있는 VirtualBox 같은 도구가 입문자에겐 최고의 선택이라고 할 수 있다. VirtualBox는 오라클에서 만든 무료 가상 머신 관리 프로그램으로, 내 PC 위에서 다른 운영체제를 마치 클라우드처럼 돌릴 수 있게 해준다[3].

 

 

VirtualBox와 AWS EC2의 비교를 아래 표에서 살펴 볼 수 있다.

항목 VirtualBox (로컬 IaaS 체험) AWS EC2 (클라우드 IaaS)
사용 위치 내 컴퓨터 클라우드 (AWS 서버)
비용 무료 사용량 기반 과금
네트워크 구성 직접 설정 필요 (NAT/브리지) 보안 그룹, VPC, 서브넷 구성
유연성 매우 높음 매우 높음
실제 서비스 운영 불가능 (로컬 환경 한계) 가능 (스케일링, 글로벌 서비스)

 

(PaaS) 컨테이너(Container)

컨테이너는 말 그대로 애플리케이션과 그 실행 환경(라이브러리, 설정, 의존성 등)을 하나로 포장(wrap)한 기술이다. 마치 앱 하나가 독립된 환경에서 실행되도록 만들어주는 작은 가상 머신 같지만, 사실상 훨씬 가볍고 빠른 구조이다.

컨테이너는 커널을 공유하고, 프로세스 단위로 동작하기 때문에 시작도 빠르고 자원도 덜 쓴다.

 

PaaS(Platform as a Service)는 개발자가 애플리케이션을 빠르게 배포하고 실행할 수 있는 플랫폼이다. 운영체제나 미들웨어 같은 건 전혀 신경 쓸 필요 없이, 코드만 넣으면 자동으로 실행된다.

 

이런 PaaS 환경에서 컨테이너는 다음과 같은 방식으로 동작해:

  1. 커널 기능 기반 격리: Linux namespace, cgroup, chroot/jail를 이용한다.
  2. 컨테이너=하나의 리눅스 프로세스: 호스트 OS 입장에선 그냥 프로세스 하나로 보인다. 컨테이너 내부에선 완전한 독립 환경처럼 작동한다
  3. VM과의 차이점: VM은 하드웨어까지 가상화한다. 컨테이너는 OS만 공유하고, 나머지는 독립적이다.

 

두 가지 컨테이너 방식

1. 시스템 컨테이너 (System Container)

  • OS 전체를 흉내 내는 방식
  • LXC, LXD 같은 기술
  • init 시스템을 가지고 있고, 다중 프로세스를 실행할 수 있어
  • 예: Waydroid – 안드로이드 OS를 리눅스 위에서 직접 실행

2. 애플리케이션 컨테이너 (Application Container)

  • 하나의 애플리케이션만 실행하는 데 집중
  • 도커(Docker)가 대표적
  • 단일 프로세스만 실행하기 때문에 확장성, 관리 효율성이 매우 뛰어남

 

도커(Docker)의 탄생과 혁신

도커는 2013년에 Solomon Hykes가 발표하면서 큰 반향을 일으켰다[4]. 그전에도 LXC와 같은 컨테이너 기술은 존재했지만, 도커는 다음의 요소를 조합해 개발자 중심의 경험으로 혁신했다. 이 발표에서 많은 참가자들이 마지막에 환호하는 것을 볼 수 있다.

  • Dockerfile로 컨테이너 정의
  • 이미지 빌드 & 레지스트리 업로드
  • 레이어 캐시와 재사용
  • 실행 단순화: docker run

 

 

실제 PaaS 환경에서는 도커와 같은 컨테이너 기반 인프라 위에 플랫폼이 구성된다. 예를 들어:

  • Google App Engine → 내부적으로 컨테이너 사용
  • Heroku → buildpack과 slug 방식이지만, 점차 컨테이너 기반으로 전환
  • Kubernetes → 컨테이너 오케스트레이션의 표준

즉, 오늘날의 PaaS는 대부분 컨테이너 기술 위에 존재한다. 컨테이너가 없었다면 이렇게 손쉽게 서비스 배포하는 시대는 오지 않았을 것이다.

 

(FaaS) AWS/Azure 펑션 서비스

FaaS는 서버리스(Serverless) 컴퓨팅의 핵심 구성요소야. 여기서 "Serverless"란 서버가 없다는 뜻이 아니라, 서버의 운영과 관리를 개발자가 신경 쓸 필요가 없다는 것을 의미한다. 클라우드 제공자가 서버 자원을 자동으로 할당하고, 이벤트가 발생할 때만 코드가 실행되도록 도와주는 구조야. 사용자는 함수 단위로 코드를 작성하고 업로드하면 된다. 실행 시간만큼만 비용이 부과되기 때문에, 짧고 간헐적인 작업에 매우 효율적이다. 클라우드 서비스에서 제공하는 서비스들의 예를 살펴 보자.

 

AWS Lambda

  • 최초 출시: 2014년
  • 지원 언어: Python, Node.js, Java, Go, .NET, Ruby 등
  • 동작 방식:
    • 특정 트리거(S3, API Gateway, DynamoDB 등)를 통해 자동 실행
    • 예: 이미지 업로드 시 썸네일 생성

AWS Lamda 서비스[5]

특징

  • 100ms 단위 과금
  • 동시성 자동 확장
  • VPC 연결 가능
  • 최대 15분까지 실행 가능 (기본은 수 초~수 분)

 

Azure Functions

  • Microsoft의 서버리스 플랫폼
  • 지원 언어: C#, JavaScript, Python, PowerShell 등
  • 트리거 기반 아키텍처: Blob 저장소, HTTP 요청, Event Hub, Cosmos DB 등과 연동

Azure Function 서비스[5]

특징

  • Consumption Plan(실행 시간 기반 과금)
  • Durable Functions 지원 (복잡한 워크플로우 처리 가능)
  • CI/CD 연동이 쉬움 (GitHub Actions, Azure DevOps)

FaaS의 장점과 한계

항목 장점 한계
비용 사용한 만큼만 과금, Idle 시 과금 없음 고빈도 실행 시 IaaS/PaaS보다 오히려 비쌀 수 있음
확장성 자동 확장 (수천 개 함수 병렬 실행) 콜드 스타트(초기 실행 지연) 이슈 존재
관리 용이성 OS, 서버, 네트워크 관리 불필요 실행 시간 제한 (15분), 상태 저장 어려움
개발 속도 빠른 개발 및 배포 가능 로컬 개발/디버깅이 까다로움

 

 

FaaS는 짧고 빠르게 끝나는 작업, 고립된 기능, 빈번하지 않은 요청 처리에 유리하다. 다음과 같은 시나리오에서 특히 강력하다.

  • 주기적 백업 / 스케줄러 작업
  • 웹훅 처리 (GitHub, Stripe 등)
  • 이미지/영상 처리
  • 채팅봇 응답
  • 이벤트 기반 마이크로서비스

 

마무리하며

 

여기서는 [6]에서 설명하는 후반부의 클라우드와 관련해서 가상화 → IaaS → 컨테이너 기반 PaaS → FaaS에 이르기까지 살펴 보았다. 

 

참고문서

[1] What is virtualization and how it works, https://www.cloud4u.com/blog/how-virtualization-works/

[2] FaaS, PaaS, SaaS, and IaaS Cloud Models — Choose the Best One for Your Digital Commerce, https://virtocommerce.com/blog/paas-saas-faas-iaas-cloud-models

[3] How to Use VirtualBox (Beginners Guide), https://youtu.be/sB_5fqiysi4

[4] The future of Linux Containers, https://youtu.be/wW9CAH9nSLs

[5] FaaS Orchestration of Parallel Workloads, https://www.researchgate.net/publication/337362540_FaaS_Orchestration_of_Parallel_Workloads

[6] 리눅스 프로세스에서 클라우드까지, https://docs.google.com/presentation/d/1PGIYqT5s-qyB4pshJqCFftZ-3r9b4iWWzrdO8dj6nvE/edit#slide=id.p

개발자로서 프로그래밍 언어를 배우는 것이 가장 기본이 되겠지만, 경력을 쌓다 보면 큰 그림의 개념들에 대해서 조금씩 알게 된다. 마치 우리가 발을 딛고 있는 땅 혹은 지구라는 것에 대해서 조금씩 알게 되고 공부하게 되는 것과 비슷하다. 하지만, 이러한 것들도 학습하다 보면 너무 크고 어려운 개념들이 많다. 여기서는 거기서도 아주 기본이 되는 컴퓨터의 구조와 가상화에 대해서 살펴 보자. 발표 자료[1]로 만는 것을 글로도 다시 작성해 본다. 글이 길어져 2개로 나누어 작성한다.

 

1. Process vs Thread – 기본부터 다시 보기

컴퓨터에서 프로그램이 실행된다는 건 ‘프로세스’가 만들어진다는 뜻이다. 상반되게 ‘스레드’는 이 프로세스 내부에서 다시 만들어져서 일어나는 작업 단위로 볼 수 있다.프로세스는 독립적인 주소 공간을 가지고 있어서 다른 프로세스랑 격리된다. 반면 스레드는 같은 프로세스 안에서 주소 공간을 공유해. 그래서 스레드 간 통신은 빠르고, 프로세스 간 통신은 조금 더 복잡하게 된다.

 

Process vs Thread[2]

2. 가상 메모리 – 물리 메모리를 넘어선 확장

리눅스 시스템에서 모든 프로세스는 '가상 주소(Virtual Address)'를 사용한다. 실제로는 이 주소가 우리가 알고 있는 실제 메모리인 물리 메모리나 하드 디스크와 같은 스왑 공간에 매핑된다. 64비트 시스템에서는 최대 256TB까지 접근 가능하다는 점이 상상 이상일 수 있겠다. 이 개념이 중요한 이유는, 각 프로세스가 자신만의 메모리를 가진 것처럼 행동할 수 있게 해줘서 시스템 안정성과 보안을 크게 높여준다.

Virtual Address[2]

3. IPC – 프로세스 간 소통의 기술

프로세스는 고립돼 있으니 서로 대화를 하려면 특별한 기술이 필요하다. 이걸 IPC(Inter Process Communication)라고 한다. 대표적인 예를 살펴 보면 다음과 같다.

  • 공유 메모리: 다른 프로세스에서 함께 접근 가능한 메모리 공간을 만들어 주는 것이다. 빠르지만 동기화가 중요하다.
  • 메시지 패싱: 명확하고 안전하지만 상대적으로 느리다.

[3]

4. 시스템 콜 – 유저 공간과 커널의 다리

우리가 코드를 짜서 실행하면, 실제로는 OS 커널에게 일을 시켜야 해. 그때 사용하는 게 ‘시스템 콜’이다.파일을 읽고 쓰거나, 새로운 프로세스를 만들거나 할 때 반드시 커널의 도움이 필요하다. 위에서 설명한 IPC도 시스템콜로 제공할 수 있다. 아래 그림은 우리가 Linux에서 간단한 프로그램을 작성하고 실행할 때, 발생하는 동작의 간략화 하여 보여 주는 그림이다.

 

OS 동작 설명[4]

 

5. 프로세스 스케줄링 – CPU 자원의 분배자

OS의 동작을 프로세스 처리에 대해서 시간 축으로 살펴 보면 동작에 대해서 좀 더 이해할 수 있다. 하나의 CPU가 수십 개의 프로세스를 동시에 처리하는 것처럼 보이는 건 스케줄링 덕분이다. 우선순위, 타임 슬라이스 등 다양한 정책이 있는데, 이건 성능에 큰 영향을 줄 수 있다. 여기서는 사용자 프로그램 2개 (User Prog. A, User Prog. B)가 어떻게 시스템 콜(Kernel)과 동작하여 하나의 CPU에서 처리되는지 시간 축으로 볼 수 있다.

 

스케줄링 예제[5]

 

마치며

여기서는 컴퓨터 구조(Computer Architecture)와 운영체제(Operating System) 중 개발자가 알고 있으면 도움이 될 만하다고 생각하는 중요한 컨셉을 아주 단순화 하여 살짝 식만 맛보았다. 이 내용들의 상세 내용들을 다루려면 대학교 학부의 한학기 과정 2개 정도를 다뤄야 할 수도 있다. 중요한 것은 이 글은 단순히 리눅스 프로세스와 같은 개념을 나열하였다기 보다는 우리가 웹 서비스와 같은 클라우드에 구현하는 기능들에 대해서 이해하기 위한 개념들 사이의 '연결 고리'를 설명한 것이라 할 수 있다. 시스템을 설계하거나 클라우드 아키텍처를 고민할 때 이 흐름을 이해하고 있으면 완전히 다른 수준의 대화가 가능해 질 것이다. 다음에는 좀 더 상위로 올라가서 가상화를 이용한 클라우드의 개념에 대해서 살펴 보자.

 

참고문헌

[1] 리눅스 프로세스에서 클라우드까지, https://docs.google.com/presentation/d/1PGIYqT5s-qyB4pshJqCFftZ-3r9b4iWWzrdO8dj6nvE/edit#slide=id.g22c5ba03f75_0_20

[2] Difference between Process and Thread in Java, https://javarevisited.blogspot.com/2015/12/difference-between-thread-and-process.html#axzz7ySnCqnZ1

[3] Virtual memory, https://en.wikipedia.org/wiki/Virtual_memory

[4] Inter Process Communication (IPC), https://www.geeksforgeeks.org/inter-process-communication-ipc/

[5] Terminologies: Kernel and Utility, https://velog.io/@sangh00n/User-program-and-System-Call

[6] User program and System Call, https://velog.io/@sangh00n/User-program-and-System-Call

네트워크 애플리케이션 성능은 Latency(지연 시간), Bandwidth(대역폭), 그리고 모바일 기기에서의 전력 소모라는 세 가지 주요 요소로 나눌 수 있다. HTTP/2와 HTTP/3는 기존 HTTP/1.1의 한계를 극복하기 위해 설계되었으며, 각 프로토콜이 이러한 성능 측면에서 어떻게 개선되었는지 살펴보자.

 

HTTP/2: Multiplexing과 Header 압축을 통한 효율성[1]

  • Multiplexing: HTTP/1.1은 하나의 연결에서 하나의 요청-응답만 처리할 수 있는 문제점(Head-of-Line Blocking)을 가지고 있다. HTTP/2는 단일 TCP 연결에서 여러 요청과 응답을 동시에 처리할 수 있도록 개선하여 Latency를 크게 줄였다.
  • Header Compression: HTTP/2는 HPACK을 사용하여 헤더를 압축한다. 이는 반복적인 헤더 전송에 소요되는 Bandwidth를 절약하는 데 유리하다.
  • Push Promise: 서버에서 클라이언트가 요청하지 않은 리소스를 미리 전송할 수 있는 HTTP/2의 기능으로 페이지 로드 속도를 향상시킨다.

HTTP/3: UDP 기반의 QUIC 프로토콜로 Latency 최적화 [3]

  • UDP 기반: HTTP/3는 TCP 대신 QUIC(Quick UDP Internet Connections) 프로토콜을 기반으로 설계되었습니다. QUIC는 연결 설정 속도가 빠르고 패킷 손실에도 연결이 유지되므로 Latency를 최소화한다.
  • TLS 통합: QUIC는 TLS 1.3을 통합하여 보안을 강화하면서도 연결 수립 단계를 줄여 초기 Latency를 줄인다.
  • 멀티플렉싱 개선: HTTP/2는 단일 TCP 연결에서 발생하는 Head-of-Line Blocking 문제를 해결했지만, TCP 수준에서 여전히 동일한 문제가 발생했다. HTTP/3는 QUIC을 통해 이 문제를 해결하였다.
  • 모바일 기기 친화적: 모바일 환경에서 네트워크 변경(예: Wi-Fi에서 LTE로 전환) 시 연결을 유지할 수 있어 전력 소모와 재연결 Latency가 줄어든다.

 

성능 측면 분석

  1. Latency
    • HTTP/2: TCP의 3-way handshake로 인한 초기 연결 지연이 존재.
    • HTTP/3: QUIC의 0-RTT Handshake로 초기 연결 Latency 최소화.
  2. Bandwidth
    • HTTP/2: 데이터 압축 및 멀티플렉싱으로 대역폭 효율 증가.
    • HTTP/3: 패킷 재전송 감소와 멀티플렉싱으로 Bandwidth 활용 최적화.
  3. 전력 소모
    • HTTP/2: 다수의 연결 유지 시 전력 소모가 증가 가능.
    • HTTP/3: 네트워크 변화 시 연결을 유지하므로 전력 소모 감소.

 

모바일 환경에서의 성능 최적화

  • HTTP/2HTTP/3 모두 모바일 친화적인 특성을 가지지만, HTTP/3는 특히 고속 네트워크 변경 및 불안정한 환경에서 강력한 성능을 제공한다.
  • QUIC의 효율성 덕분에 스트리밍 및 게임 같은 애플리케이션에서 HTTP/3 채택이 증가하고 있다.

참고자료

[1] IETF RFC 7540, https://datatracker.ietf.org/doc/html/rfc7540

[2] IETF RFC 9000, https://datatracker.ietf.org/doc/html/rfc9000

 

 

 

웹을 이루는 수많은 기술 중에서 진짜 뼈대라 할 수 있는 것이 바로 HTTP이다. 브라우저에서 URL을 입력하면 그 뒤에서 움직이는 가장 핵심적인 프로토콜이 이 HTTP이다. HTTP가 어떻게 발전해 왔는지, 왜 계속 진화해야 했는지를 살펴보자.

1. HTTP 0.9: 한 줄로 시작된 혁명

1991년 팀 버너스 리가 만든 HTTP 0.9는 믿기 어려울 만큼 단순했다.

  • 클라이언트는 단 한 줄짜리 요청(GET /index.html)을 보냄
  • 서버는 HTML 문서만 반환
  • 헤더도 없고, 연결도 요청마다 끊김

지금 관점에선 너무 부족해 보이지만, 이 단순함 덕분에 웹이 빠르게 확산될 수 있었다. 흥미로운 건 아직도 telnet 명령으로 HTTP 0.9 방식 요청을 실험해볼 수 있다.

2. HTTP 1.0: 웹 대중화의 기반

1990년대 초, 모자익(Mosaic) 브라우저의 등장과 함께 HTTP는 대중에게 본격적으로 노출되기 시작했다. 이 시점에 등장한 HTTP 1.0은 다음과 같은 변화가 있었다.

  • 요청/응답에 헤더가 추가됨 (User-Agent, Content-Type 등)
  • 다양한 MIME 타입 지원
  • 여전히 요청마다 연결을 끊는 구조

RFC 1945로 정리되긴 했지만, 이건 공식 표준이라기보단 ‘정보 제공용 문서(Informational)’였다. HTTP 1.0의 가장 큰 한계는 매번 새로운 TCP 연결을 생성해야 한다는 점이다. 느린 시작(slow start)과 3-way 핸드셰이크 비용을 생각하면, 성능 측면에서 효율적이지 않았다.

3. HTTP 1.1: 커넥션 재사용으로의 전환

1997년 등장한 HTTP 1.1은 웹 성능을 획기적으로 끌어올린 버전이다. 핵심은 ‘Keep-Alive’를 기본으로 채택했다는 점이다.

주요 기능:

  • Persistent Connections (기본적으로 연결 유지)
  • Chunked Transfer-Encoding (스트리밍 전송 지원)
  • Pipelining (연속 요청)
  • 캐시 제어 및 콘텐츠 협상

여기서부터 웹은 단순한 텍스트 문서를 넘어서 하이퍼미디어 전송 플랫폼으로 진화하기 시작했다. 이미지, 동영상, 다양한 언어와 문자셋, 쿠키 기반 상태 관리 등도 이때 본격적으로 쓰이기 시작했다.

4. HTTP/2: 성능 재도약

웹의 무게가 점점 더 커지자, HTTP/1.1으로는 버티기 힘든 시대가 왔다. 그래서 2012년, HTTPbis 워킹 그룹이 HTTP/2 개발을 시작했다.

HTTP/2의 목표:

  • 낮은 레이턴시
  • 높은 처리량
  • 기존 애플리케이션 수정 없이도 적용 가능

핵심은 단일 TCP 연결에서 멀티플렉싱을 지원한다는 것이다. 즉, 하나의 연결 안에서 동시에 여러 리소스를 주고받을 수 있으니 브라우저가 더 빠르고 효율적으로 동작할 수 있게 된다.

기존의 요청-응답 구조는 그대로 유지하면서 내부 전송 방식만 개선한 것이 특징이다. 덕분에 기존 서버나 클라이언트는 큰 수정 없이 HTTP/2의 이점을 누릴 수 있다.

 

정리하며: 왜 이걸 알아야 할까?

소프트웨어 아키텍처나 멀티미디어 서비스 설계를 고민할 때, 전송 계층의 성능은 전체 사용자 경험에 지대한 영향을 끼쳐. 특히 아래와 같은 상황에서 HTTP의 동작 원리를 이해하는 게 중요하다.

  • CDN 및 캐시 정책 설계
  • 스트리밍 서비스 아키텍처 구축
  • 모바일/저지연 환경에서의 콘텐츠 전송 최적화
  • SPA나 모바일 앱의 백엔드 API 호출 구조 설계

HTTP의 역사는 단순한 기술 진화가 아니라, 사용자 경험을 최적화하고자 하는 노력의 결정체이다. 브라우저 네트워킹을 이해하는 건, 결국 우리가 만드는 서비스의 퍼포먼스를 좌우하는 핵심이 된다고 볼 수 있다.

참고문헌

[1] 일리아 그리고릭, "구글 엔지니어에게 듣는 네트워킹과 웹 성능 최적화 기법", 정해권, 오현주 공역, 인사이트(insight)

모바일 네트워크는 우리가 스마트폰을 사용하는 방식, 그리고 모바일 앱이 동작하는 근본 구조에 지대한 영향을 준다. 특히 5G는 단순한 속도 향상을 넘어, 완전히 새로운 가능성과 도전을 함께 가져왔다. 이번 글에서는 모바일 네트워크의 진화를 간단히 짚어보고, 5G의 핵심 목표와 모바일 앱 관점에서 꼭 알아야 할 특징을 정리해보자.

5G의 핵심 목표: Speed, Coverage, Connectivity

5G의 비전은 단순히 "더 빠르게"가 아니다. 다음 세 가지 목표가 중심이다:

  • Speed: 최고 속도 10Gbps 이상을 목표로 하며, 4G보다 최대 100배 빠르다. 이는 고해상도 영상 스트리밍, AR/VR 같은 애플리케이션을 실시간으로 가능하게 한다.
  • Coverage: 기존보다 훨씬 넓은 커버리지를 확보하고, 밀집 지역에서도 연결 품질을 유지할 수 있도록 설계되었다.
  • Connectivity: 단말 간 연결 수용량이 폭발적으로 늘어난다. 자율주행차, IoT 센서, 웨어러블 기기들이 동시에 연결되어도 문제없도록 하는 것이 핵심이다.

모바일 애플리케이션 개발 관점에서의 5G 특징

모바일 앱을 개발하거나 서비스를 설계하는 입장이라면, 5G 환경에 맞는 접근이 필요하다. 다음은 중요한 특징이다:

전력 효율과 RRC 상태 관리

5G에서는 4G와 유사하게 연결 상태를 관리한다.

  • 스마트폰은 항상 네트워크와 연결되어 있는 WiFi와는 달리, 필요할 때만 연결되는 무선 특성이 있다.
  • RRC(Radio Resource Control)는 데이터 전송 시에만 라디오 리소스를 할당함으로써 전력 소모를 줄인다.
  • 따라서 앱에서 데이터를 계속 조금씩 보내는 것보다, 몰아서 한 번에 전송하는 전략이 배터리 효율에 유리하다.

네트워크 지연(latency) 고려

  • 5G의 초저지연(1ms 이하)은 클라우드 게임, 원격 제어, 라이브 인터랙션 앱에서 핵심 자산이다.
  • 하지만 사용자의 위치나 단말 상태에 따라 RRC 연결 전환 지연이 여전히 발생할 수 있으므로, 앱은 이를 고려한 버퍼링, 프리로드 전략이 필요하다.

5G와 엣지 컴퓨팅(Edge Computing)의 만남

5G의 등장은 단지 네트워크 속도를 높이는 것을 넘어, 엣지 컴퓨팅(Edge Computing)과 결합해 새로운 컴퓨팅 패러다임을 만들어내고 있다. 특히 모바일 앱과 서비스가 요구하는 초저지연, 대용량 데이터 처리, 프라이버시 보호 같은 요소들이 엣지 컴퓨팅과 만날 때 큰 시너지를 낸다.

엣지 컴퓨팅이란?

엣지 컴퓨팅은 데이터를 사용자와 가까운 곳, 즉 "네트워크의 엣지(edge)", 예를 들어 기지국, 지역 서버, 또는 사용자 디바이스 근처의 마이크로 데이터 센터에서 처리하는 방식이다.

  • 기존에는 모든 요청이 중앙 클라우드로 갔기 때문에, 거리로 인한 지연(latency)이 발생했고,
  • 대역폭 소모도 컸으며,
  • 개인정보 보호 측면에서도 이슈가 있었다.

엣지 컴퓨팅은 이런 문제를 해결하며 실시간성과 효율성을 동시에 해결한다.

왜 5G와 엣지는 함께 가야 할까?

앞에서 이야기 한 것과 같이 5G는 이론적으로 1ms 이하의 초저지연, 기가비트급 전송속도, 1㎢당 100만 개 이상의 단말 수용을 목표로 한다. 그러나, 이 모든 걸 제대로 실현하려면 데이터가 중앙이 아닌 로컬에서 처리될 수 있어야 한다. 즉, 엣지 컴퓨팅 없이는 5G의 약속도 절반의 구현에 불과할 수 있다.

[2] Edge Computing 사례

 

 

모바일 애플리케이션에서의 활용 사례

클라우드 게임 스트리밍

  • 지연이 중요한 FPS 게임, 레이싱 게임은 5G만으로도 부족할 수 있다.
  • 사용자 근처의 엣지 서버에서 게임 데이터를 렌더링하고 스트리밍하면,
    • 조작 반응 속도(입력 ↔ 반응)가 현격히 줄어든다.
    • 실제로 Xbox Cloud Gaming, NVIDIA GeForce Now 등은 엣지 인프라를 적극 활용 중.

자율주행차와 스마트 시티

  • 차량의 위치, 속도, 도로 상황 등의 데이터가 엣지에서 처리됨으로써,
    • 교통 신호와 차량 간 실시간 협업이 가능해짐.
  • 스마트 시티에서는 CCTV, 센서 데이터를 엣지에서 실시간 분석하여 교통 제어, 범죄 예방 등에도 활용.

VR과 Edge Computing

  • 클라우드까지 왕복하는 방식은 보통 50~100ms 이상의 지연이 발생할 수 있으나, VR은 일반적으로 20ms 이하의 지연 시간을 유지해야 자연스럽고 어지럽지 않은 경험을 제공할 수 있다.
  • 8K 또는 16K 영상이 필요한 VR 콘텐츠는 데이터 용량이 매우 크다. 이걸 모바일 기기가 직접 처리하거나 원거리 클라우드에서 처리하면 부하가 크고, 지연이 생길 수 있다. 엣지에서 영상 렌더링을 처리하면, 최소한의 전송으로 최적의 영상 품질 제공이 가능하다.
  • VR 헤드셋이나 스마트폰의 연산 능력은 제한적이다. GPU 렌더링, 딥러닝 기반 공간 인식 등을 엣지에서 처리하면, 기기 발열과 배터리 소모도 크게 줄일 수 있다.

앱 개발자가 고려해야 할 점

  • 엣지 환경에 맞춘 API 및 서비스 설계가 필요하다. 예: Google Cloud’s Anthos, AWS Wavelength, Azure Edge Zones[3]
  • 네트워크 지연, 연결 품질이 변수가 될 수 있기 때문에, fallback 전략(엣지 → 클라우드 대체)이 있어야 한다.
  • 데이터 처리 위치를 사용자의 동의에 따라 동적으로 제어하는 기능도 점점 중요해진다.

정리

모바일 네트워크는 단순한 "통신 기술"이 아니라, 사용자 경험과 모바일 생태계를 뒤흔드는 근본 인프라다. 5G의 등장은 단지 더 빠른 유튜브 로딩을 의미하는 게 아니다. 그것은 개발자에게는 전혀 다른 설계 철학, 사용자에게는 새로운 서비스의 경험을 뜻한다. 특히, 엣지 컴퓨팅은 5G와 결합하여 단순히 속도를 높이는 것이 아니라, 네트워크를 유연하게, 그리고 스마트하게 만든다. 이제 중요한 것은 어디에서 데이터를 처리하느냐다. 사용자와 가까운 엣지에서 처리함으로써 얻는 이점은 앞으로의 모바일 앱과 서비스에 있어서 경쟁력을 좌우할 핵심 요소가 될 것이다.

참고 자료

[1] High Performance Browser Networking: Ch 07 모바일 네트워크, https://technical-leader.tistory.com/171

[2] Edge Computing Helps 5G Networks: A Practical Guide to Optimising Campus Network Architecture, https://community.fs.com/article/edge-computing-helps-5g-networks-a-practical-guide-to-optimising-campus-network-architecture.html

[3] AWS Wavelength 소개, https://aws.amazon.com/wavelength/

 

 

+ Recent posts