Tag: Architecture

  • [Engineering Insight] Async & Generators: Re-examining Core Principles After 18 Years

    While diving deep into a new tech stack recently, I found myself re-evaluating the fundamental nature of Asynchrony (Async) and Generators—tools we often use without second thought. From the perspective of an engineer who has managed systems for 18 years, here is a breakdown of how different languages like Python, Flutter, and Java approach these problems.


    1. Memory Innovation: List vs. Generator

    Think of the ‘Infinite Scroll’ feature we see in modern apps. Loading tens of thousands of data points at once would crash most systems.

    • List: Allocates memory for all data points immediately.
    • Generator: Stores only the logic (rule) to produce the next data point.

    In Python, Generators utilize Lazy Evaluation to drastically reduce memory footprints. This is the secret to maintaining system stability with only a few MBs of memory, even when processing 10GB log files.


    2. The Essence of Async: ‘Dynamic Callbacks’ and Suspension

    Many treat async/await as mere syntax sugar, but its essence lies in Function Suspension and Dynamic Callback Registration.

    The moment we encounter await, the function packages its current state (local variables, stack pointer, etc.) and hands it over to the Event Loop. The CPU then moves on to other tasks.

    • Traditional Event-Driven: Requires manual registration and management of callbacks, making state management notoriously difficult.
    • Modern Async (Python/Flutter): Because the function itself “carries” its state while suspended, developers can reap all the benefits of asynchrony while writing clean, linear code.

    3. Architectural Differences Across Languages

    Python & Flutter (Dart)

    These two are like siblings. Both rely on a single-threaded Event Loop and use explicit async/await keywords. The goal is either to keep the UI thread from freezing (Flutter) or to efficiently utilize I/O wait times (Python).

    Java (Project Loom)

    Java took a different path. Instead of introducing new syntax, it introduced ‘Virtual Threads.’ Developers write traditional synchronous-style code, and the JVM automatically intercepts blocking calls to switch between millions of lightweight threads.


    4. A Senior Engineer’s Lens: Why Design Matters

    Imagine implementing a login flow in an async environment. If the process must wait for the login to complete, that specific function suspends. However, the system as a whole never stops.

    This is where an engineer’s true caliber is tested:

    • Dependency Design: What should we await (sequential), and what should we gather (parallel)?
    • Resource Management: How do we isolate CPU-bound ‘blocking operations’ within an async loop to prevent bottlenecks?

    Closing Thoughts: Strengthening the Foundation

    18 years of experience is a powerful weapon. However, the ‘gaps’ we feel when facing new tools are only filled by understanding the underlying design philosophy. When you grasp why yield suspends a function and why await is essentially a dynamic callback, you can finally architect systems with confidence—even on a blank canvas.

    It’s not just about writing code that works; it’s about writing code with clear rationale. I believe this is the key to protecting our families’ security and our longevity as engineers in the era of 100-year lifespans.


    [Appendix: Technical Baseline Quiz]

    Q1. What is the Time Complexity (Big-O)?

    def f(n):
        s = 0
        for i in range(n):
            for j in range(i):
                s += 1
        return s

    Answer: This is a nested loop. The inner loop runs $0, 1, 2, \dots, n-1$ times. The total execution count is $\frac{n(n-1)}{2}$. Keeping only the highest-order term, it is $O(n^2)$.

    Q2. Explain the difference between a and b:

    a = [i*i for i in range(1000000)]
    b = (i*i for i in range(1000000))

    Answer: a is a List, allocating memory for 1M results immediately. b is a Generator, which calculates values one by one only when requested (Lazy Evaluation), making it highly memory-efficient for large datasets.

    Q3. What is the problem with this code?

    def append_item(item, lst=[]):
        lst.append(item)
        return lst

    Answer: Mutable Default Arguments. In Python, the default list [] is created only once at the time of function definition. Multiple calls will share the same list object, leading to unintended side effects (e.g., calling it twice results in [1, 1]).

  • [Engineering Insight] 비동기와 제너레이터: 18년 차 엔지니어가 다시 짚어본 본질

    최근 새로운 기술 스택을 깊게 파고들며, 우리가 무심코 사용하던 **비동기(Async)**와 **제너레이터(Generator)**의 본질에 대해 다시 고민해 보았습니다. 18년 동안 시스템을 다뤄온 엔지니어의 시각으로, Python, Flutter, Java 등 각기 다른 언어들이 이 문제를 어떻게 풀어내고 있는지 정리해 봅니다.

    Q1.
    다음 코드의 시간복잡도(Big-O)는?

    def f(n):
        s = 0
        for i in range(n):
            for j in range(i):
                s += 1
        return s

    1. 메모리의 혁신: 리스트(List)와 제너레이터(Generator)

    우리가 흔히 쓰는 ‘무한 스크롤’ 기능을 떠올려 봅시다. 수만 개의 데이터를 한꺼번에 로드하면 메모리는 버티지 못합니다.

    • List: 모든 데이터를 즉시 메모리에 할당합니다.
    • Generator: “다음에 줄 데이터는 이거야”라는 규칙만 저장합니다.

    파이썬의 제너레이터는 **지연 평가(Lazy Evaluation)**를 통해 메모리 점유율을 비약적으로 낮춥니다. 10GB의 로그를 처리할 때도 단 몇 MB의 메모리만으로 시스템을 안정적으로 유지할 수 있는 비결입니다.


    2. 비동기(Async)의 본질: ‘동적 콜백’과 일시 정지

    많은 이들이 async/await를 단순한 문법으로 치부하지만, 그 본질은 **’함수 실행의 일시 정지(Suspend)’**와 **’동적 콜백 등록’**에 있습니다.

    우리가 await를 만나는 순간, 함수는 현재의 상태(로컬 변수, 스택 포인트 등)를 그대로 패키징하여 이벤트 루프에 던집니다. 그리고 CPU는 다른 일을 하러 떠나죠.

    • 전통적 이벤트 드리븐: 콜백을 수동으로 등록하고 관리해야 하므로 상태 관리가 어렵습니다.
    • Modern Async (Python/Flutter): 함수 자체가 ‘상태’를 품고 멈추기 때문에, 개발자는 동기적인 코드 흐름 안에서 비동기적 이득을 모두 취할 수 있습니다.

    Q2.

    Generator vs List 차이 설명 + 아래 코드 결과 차이 설명

    a = [i*i for i in range(1000000)]
    b = (i*i for i in range(1000000))

    3. 언어별 아키텍처의 차이

    Python & Flutter (Dart)

    두 언어는 형제와 같습니다. 싱글 스레드 기반의 이벤트 루프를 사용하며, async/await라는 명시적 키워드를 통해 비동기 구간을 설정합니다. 사용자 경험(UX)을 위해 UI 스레드를 멈추지 않거나(Flutter), I/O 대기 시간을 효율적으로 활용하는 것(Python)이 목표입니다.

    Java (Project Loom)

    자바는 조금 다른 길을 택했습니다. 새로운 문법을 도입하는 대신 **’가상 스레드(Virtual Thread)’**를 도입했습니다. 개발자는 기존의 동기 코드를 짜던 방식 그대로 코딩하지만, 내부적으로 JVM이 수백만 개의 경량 스레드를 가로채 비동기적으로 스위칭합니다.

    Q3.

    다음 코드의 문제점은?

    def append_item(item, lst=[]):
        lst.append(item)
        return lst

    4. 시니어 엔지니어의 시선: 왜 설계를 고민해야 하는가?

    비동기 환경에서 로그인을 구현한다고 가정해 봅시다. 로그인이 완료되어야 다음 단계로 갈 수 있다면, 그 함수 내부는 대기 상태가 됩니다. 하지만 시스템 전체는 멈추지 않습니다.

    여기서 엔지니어의 역량이 드러납니다.

    1. 의존성 설계: 무엇을 기다리고(await), 무엇을 병렬로 던질(gather) 것인가?
    2. 리소스 관리: 비동기 루프 안에서 CPU를 점유하는 ‘블로킹 연산’을 어떻게 격리할 것인가?

    마치며: 성긴 그물을 촘촘하게

    18년의 경험은 강력한 무기입니다. 다만 새로운 도구를 만났을 때 생기는 ‘공백’은 그 도구의 설계 철학을 이해함으로써 채워집니다. yield가 왜 함수를 멈추는지, await가 왜 동적 콜백인지 그 원리를 파악할 때, 비로소 백지 위에서도 자신 있게 아키텍처를 그려나갈 수 있습니다.

    단순히 동작하는 코드를 넘어, 이유가 명확한 코드를 짜는 것. 그것이 우리가 가족의 안전과 엔지니어로서의 수명을 100세까지 지켜나갈 방법이라 믿습니다.


    🔵 테스트 문제 해설 (머릿속 시뮬레이션)

    Q1. 시간복잡도 (Big-O)

    이 코드는 이중 루프입니다.

    • 첫 번째 루프는 $n$번 돕니다.
    • 두 번째 루프는 $0, 1, 2, \dots, n-1$번 돕니다.
    • 전체 실행 횟수는 $0 + 1 + 2 + \dots + (n-1) = \frac{n(n-1)}{2}$입니다.
    • 최고차항만 남기면 **$O(n^2)$**이 됩니다.

    Q2. Generator vs List

    • List (a): 실행 즉시 100만 개의 결과를 메모리에 할당합니다. 속도는 빠를 수 있지만 메모리 점유율이 높습니다.
    • Generator (b): ‘계산할 방법’만 저장하고, 값이 필요할 때마다 하나씩 생성합니다 (Lazy Evaluation). 메모리를 거의 차지하지 않아 대용량 데이터 처리에 필수적입니다.

    Q3. Mutable Default Arguments (가변 기본 인자)

    • 문제점: 파이썬에서 기본 인자(lst=[])는 함수가 정의되는 시점에 딱 한 번 생성됩니다.
    • 함수를 호출할 때마다 새로운 리스트가 생기는 게 아니라, 동일한 리스트 객체를 계속 공유하게 됩니다. 그래서 append_item(1)을 두 번 호출하면 [1, 1]이 반환되는 의도치 않은 결과가 발생합니다.