2025년 7월 8일 화요일

효율적인 동시성 솔루션을 위한 Active Object 패턴

Active Object UML


소프트웨어 개발에서 동시성(Concurrency) 문제는 항상 개발자를 괴롭히는 까다로운 과제입니다. 여러 스레드가 공유 자원에 동시에 접근할 때 발생하는 데이터 경쟁(Data Race), 교착 상태(Deadlock), 또는 일관성 없는 상태(Inconsistent State)와 같은 문제들은 시스템의 안정성과 성능을 심각하게 저해할 수 있습니다. 이러한 문제를 해결하기 위해 락(Lock), 뮤텍스(Mutex), 세마포어(Semaphore) 등과 같은 저수준의 동기화 메커니즘을 사용하지만, 이는 코드를 복잡하게 만들고 오류 발생 가능성을 높이며 디버깅을 어렵게 만듭니다.

오랜 시간 동안 수많은 개발자들이 동시성 문제에 대한 효과적이고 재사용 가능한 해결책을 모색해 왔습니다. 이러한 노력의 결실 중 하나가 바로 디자인 패턴입니다. 특히 POSA2(Pattern-Oriented Software Architecture, Volume 2: Patterns for Concurrent and Networked Objects)에 소개된 Active Object 패턴은 동시성 문제를 해결하는 강력한 기법 중 하나로 주목받습니다.

Active Object 패턴의 핵심은 메소드 호출 시점과 실제 실행 시점을 분리(Decoupling)하는 것입니다. 클라이언트는 비동기적으로 메소드를 호출하고 즉시 반환받으며, 실제 작업은 별도의 스레드에서 순차적으로 처리됩니다. 이를 통해 클라이언트의 블록킹을 최소화하고, 공유 상태에 대한 접근을 단일 스레드 내에서 관리하여 동기화의 복잡성을 크게 줄일 수 있습니다.

이 글에서는 Active Object 패턴의 개념부터 시작하여, 패턴을 구성하는 5가지 핵심 요소인 프록시(Proxy), 서번트(Servant), 액티베이션 큐(Activation Queue), 스케줄러(Scheduler), 그리고 퓨처(Future)에 대해 자세히 살펴보겠습니다. 각 요소의 역할과 상호작용 방식, 패턴의 동작 흐름, 동시성 문제 해결에 기여하는 방식, 그리고 실제 시스템 설계 및 구현에서의 장단점과 활용 사례까지 심도 있게 다루어, 이 패턴이 어떻게 효율적인 동시성 솔루션을 제공하는지 알아보겠습니다.





Active Object Pattern의 개념과 필요성

Active Object 패턴은 객체의 메소드 호출(Invocation)과 해당 메소드의 실제 실행(Execution)을 분리하는 구조화된 동시성 패턴입니다. 전통적인 동기 메소드 호출에서는 클라이언트 스레드가 메소드를 호출하고, 해당 메소드가 완료될 때까지 호출된 객체의 스레드(또는 동일 스레드)에서 블록 상태로 대기합니다. 이 방식은 공유 자원에 대한 동시 접근 시 복잡한 락킹 메커니즘을 요구하며, 클라이언트 스레드의 응답성을 저해할 수 있습니다.

반면 Active Object 패턴은 클라이언트 스레드가 마치 일반 객체의 메소드를 호출하는 것처럼 보이지만, 실제로는 해당 호출 정보를 캡슐화한 "메소드 요청(Method Request)" 객체를 생성하고 이를 큐에 추가한 후 즉시 반환하는 방식입니다. 이 요청은 Active Object의 핵심 부분인 "서번트(Servant)"를 소유한 별도의 스레드(보통 "Active Object 스레드"라고 부름)에 의해 나중에 처리됩니다. 서번트 스레드는 큐에서 요청을 하나씩 꺼내어 순차적으로 실행합니다.

이러한 분리 구조는 여러 가지 이점을 가져옵니다. 첫째, 클라이언트 스레드는 메소드 호출에 대한 실제 작업 완료를 기다리지 않으므로 UI 스레드와 같은 응답성이 중요한 스레드가 블록되는 것을 방지할 수 있습니다. 둘째, 서번트 객체는 자신의 스레드 내에서만 상태 변경이 일어나도록 설계하면, 복잡한 스레드 간 동기화 문제(데이터 경쟁 등)를 서번트 내부의 단일 스레드 처리 로직으로 단순화할 수 있습니다. 공유 상태에 대한 접근이 서번트 스레드에 의해 순차적으로 관리되기 때문입니다.

Active Object 패턴은 다음과 같은 상황에서 유용하게 사용될 수 있습니다.

  • 응답성 개선: 사용자 인터페이스(UI) 애플리케이션에서 시간이 오래 걸리는 작업을 백그라운드 스레드로 분리하여 UI 스레드가 멈추지 않도록 해야 할 때.
  • 비동기 작업 처리: 외부 시스템과의 통신, 파일 I/O 등 비동기적으로 처리해야 하는 작업을 효과적으로 관리해야 할 때.
  • 공유 상태 관리 단순화: 여러 스레드에서 동시에 접근하려는 공유 객체의 상태 변경 로직을 단일 스레드에서 처리하여 동기화 복잡성을 줄이고자 할 때.
  • 처리량 향상: 요청을 큐에 쌓아두고, 서번트 스레드가(혹은 스레드 풀이) 가용한 자원을 활용하여 최대한 효율적으로 처리하도록 구성할 때.

결론적으로, Active Object 패턴은 동시성 프로그래밍의 복잡성을 관리하고 시스템의 응답성 및 처리량을 개선하는 데 강력한 도구로 활용됩니다.




Active Object Pattern의 핵심 구성 요소 (5 Key Components)

Active Object 패턴은 일반적으로 다섯 가지 주요 구성 요소로 이루어집니다. 이 요소들이 유기적으로 상호작용하여 호출과 실행의 분리, 비동기 처리, 그리고 동시성 관리를 가능하게 합니다.

프록시 (Proxy)

프록시(Proxy)는 클라이언트가 상호작용하는 유일한 인터페이스입니다. 클라이언트는 마치 일반적인 객체의 메소드를 호출하듯이 프록시의 메소드를 호출합니다. 하지만 프록시는 실제 비즈니스 로직을 수행하는 서번트(Servant) 객체에 대한 직접적인 참조를 가지고 있지 않거나, 가지고 있더라도 직접 메소드를 호출하지 않습니다. 대신, 프록시는 클라이언트의 메소드 호출을 가로채서 해당 호출에 필요한 정보(메소드 식별자, 인자 값 등)를 캡슐화한 "메소드 요청(Method Request)" 객체를 생성합니다.

프록시의 메소드는 일반적으로 즉시 반환됩니다. 만약 호출된 메소드가 결과를 반환해야 한다면, 프록시는 결과 값 대신 "퓨처(Future)" 객체를 반환합니다. 클라이언트는 나중에 이 퓨처 객체를 통해 실제 결과를 얻을 수 있습니다. 프록시는 메소드 요청을 생성한 후, 이를 액티베이션 큐(Activation Queue)에 추가하는 역할을 수행합니다. 이 과정은 스레드 안전하게 이루어져야 하며, 여러 클라이언트 스레드가 동시에 프록시 메소드를 호출할 수 있도록 설계됩니다.

프록시는 Active Object 패턴에서 비동기 호출의 진입점 역할을 하며, 클라이언트에게는 익숙한 동기 호출 인터페이스를 제공하면서도 실제 작업은 비동기적으로 이루어지도록 추상화하는 핵심 요소입니다.

서번트 (Servant)

서번트(Servant)는 클라이언트의 요청에 의해 실제 비즈니스 로직을 수행하는 객체입니다. 서번트는 Active Object 패턴에서 관리하고자 하는 상태를 포함하며, 클라이언트가 호출하려 했던 메소드의 실제 구현을 담고 있습니다. 서번트는 일반적으로 패턴 내부에 존재하는 별도의 스레드, 즉 Active Object 스레드 내에서 실행됩니다.

Active Object 패턴의 가장 큰 장점 중 하나는 서번트 객체가 대부분의 경우 단일 스레드(서번트 스레드)에 의해 접근된다는 가정 하에 설계될 수 있다는 것입니다. 이는 서번트 내부에서 발생하는 상태 변경에 대해 복잡한 멀티스레드 동기화(예: 여러 스레드가 서번트의 내부 상태에 동시에 접근하여 락을 걸거나 해제하는 과정)를 최소화하거나 제거할 수 있음을 의미합니다. 서번트는 스케줄러(Scheduler)에 의해 액티베이션 큐에서 꺼내진 메소드 요청을 전달받아 해당 요청에 해당하는 자신의 메소드를 실행합니다. 서번트는 요청 처리 중 발생한 결과나 예외를 해당 요청과 연결된 퓨처(Future) 객체에 저장하는 역할도 수행합니다.

서번트는 Active Object의 핵심 작업 단위를 나타내며, 패턴을 통해 보호받고 관리되는 실제 객체입니다.

액티베이션 큐 (Activation Queue)

액티베이션 큐(Activation Queue)는 프록시(Proxy)에 의해 생성된 메소드 요청(Method Request) 객체들을 저장하는 버퍼입니다. 클라이언트 스레드가 프록시 메소드를 호출할 때마다 새로운 메소드 요청 객체가 생성되어 이 큐의 끝에 추가됩니다. 액티베이션 큐는 여러 클라이언트 스레드로부터 동시에 요청을 받을 수 있으므로, 반드시 스레드 안전하게 구현되어야 합니다. 일반적으로 FIFO(First-In, First-Out) 방식의 큐를 사용하지만, 우선순위 큐 등 다른 형태의 큐를 사용하여 요청 처리 순서를 제어할 수도 있습니다.

액티베이션 큐는 클라이언트 스레드와 서번트 스레드 사이의 비동기적인 통신 채널 역할을 합니다. 클라이언트 스레드는 큐에 요청을 넣는 속도에 제약을 받지 않고 빠르게 작업을 이어갈 수 있으며, 서번트 스레드는 큐에서 요청이 빌 때까지 대기하거나, 요청이 도착하면 하나씩 순차적으로 처리합니다. 이는 시스템의 버퍼링 메커니즘을 제공하여, 일시적인 부하 증가에도 유연하게 대처할 수 있게 합니다. 다만, 서번트의 처리 속도보다 요청 생성 속도가 지속적으로 빠르면 큐가 무한히 커져 메모리 부족 현상을 유발할 수도 있습니다.

액티베이션 큐는 Active Object 패턴에서 요청의 순차적인 처리와 비동기성을 구현하는 데 필수적인 요소입니다.

스케줄러 (Scheduler)

스케줄러(Scheduler)는 액티베이션 큐(Activation Queue)에 쌓인 메소드 요청(Method Request) 객체들을 관리하고 실행 순서를 결정하는 역할을 합니다. 스케줄러는 일반적으로 서번트(Servant) 객체가 실행되는 동일한 Active Object 스레드 내에서 동작합니다. 스케줄러의 주요 임무는 다음과 같습니다.

  1. 큐 감시: 액티베이션 큐에 새로운 요청이 도착했는지 지속적으로 확인합니다.
  2. 요청 선택: 큐에서 다음에 처리할 메소드 요청을 선택합니다 (FIFO 또는 우선순위 기반).
  3. 요청 전달: 선택된 메소드 요청을 서번트 객체의 해당 메소드로 디스패치(Dispatch)하여 실행을 시작합니다.
  4. 실행 관리: 서번트 메소드의 실행이 완료될 때까지 대기하거나, 비동기적인 방식으로 실행 결과를 처리합니다.

스케줄러는 Active Object 스레드의 이벤트 루프(Event Loop)와 유사한 역할을 수행하기도 합니다. 큐에서 요청을 꺼내 처리하는 과정이 반복적으로 일어나는 루프 구조를 가질 수 있습니다. 스케줄러의 구현 방식에 따라 다양한 스케줄링 정책을 적용할 수 있으며, 이는 시스템의 응답성 및 처리량에 영향을 미칩니다. 예를 들어, 작업 시간이 매우 긴 요청이 큐에 있다면, FIFO 스케줄링의 경우 뒤따라오는 짧은 요청들이 모두 대기해야 하는 문제가 발생할 수 있으며, 이를 해결하기 위해 우선순위 스케줄링이나 작업 분할 등의 고급 기법을 고려할 수 있습니다.

스케줄러는 Active Object 패턴에서 요청의 실행 순서를 제어하고 서번트 스레드의 작업을 관리하는 핵심 제어 로직입니다.

퓨처 (Future)

퓨처(Future) 또는 Promise는 비동기적으로 호출된 메소드의 결과 값이나 발생할 수 있는 예외를 나타내는 플레이스홀더(Placeholder) 객체입니다. 클라이언트 스레드가 프록시(Proxy)를 통해 결과 값이 필요한 메소드를 호출하면, 프록시는 실제 작업이 완료되기 전에 즉시 이 퓨처 객체를 클라이언트에게 반환합니다. 클라이언트는 퓨처 객체를 받아 다른 작업을 계속 진행할 수 있습니다.

나중에 클라이언트가 비동기 작업의 결과가 필요할 때, 퓨처 객체의 get() 또는 유사한 메소드를 호출합니다. 만약 서번트(Servant) 스레드에서 해당 작업이 아직 완료되지 않았다면, 클라이언트 스레드는 퓨처 객체에서 결과가 준비될 때까지 블록 상태로 대기합니다. 작업이 완료되고 결과가 퓨처에 저장되면, 대기하던 클라이언트 스레드는 깨어나 결과를 받아 작업을 이어갈 수 있습니다.

퓨처는 클라이언트 스레드와 서번트 스레드 간에 결과 값을 비동기적으로 안전하게 전달하는 메커니즘을 제공합니다. 이를 통해 클라이언트는 메소드 호출 시점에 블록되지 않고, 결과가 필요할 때에만 선택적으로 블록되거나 또는 결과를 비동기적으로 받을 수 있는 콜백(Callback) 메커니즘과 함께 사용할 수도 있습니다. 퓨처는 Active Object 패턴에서 비동기 호출의 결과 처리를 가능하게 하는 중요한 구성 요소입니다.




Active Object Pattern의 동작 방식 (Workflow)

Active Object 패턴의 동작 방식은 위에서 설명한 다섯 가지 구성 요소의 유기적인 상호작용으로 이루어집니다. 일반적인 메소드 호출부터 결과 반환까지의 흐름은 다음과 같습니다.

  1. 클라이언트의 메소드 호출: 클라이언트 스레드는 Active Object가 제공하는 서비스가 필요할 때, 해당 서비스의 프록시(Proxy) 객체가 노출하는 인터페이스의 메소드를 호출합니다.

    // 개념적인 코드 예시
    ProxyInterface activeObjectProxy = new ActiveObjectProxyImpl();
    Future<ResultType> futureResult = activeObjectProxy.performAsyncOperation(arg1, arg2);
    // 클라이언트 스레드는 여기서 블록되지 않고 다음 작업을 수행할 수 있습니다.
    
  2. 프록시의 메소드 요청 생성: 프록시는 클라이언트의 메소드 호출(메소드 이름, 인자 등)을 가로채서 이를 캡슐화한 메소드 요청(Method Request) 객체를 생성합니다. 이 요청 객체는 필요한 모든 정보와 함께 결과가 저장될 퓨처(Future) 객체에 대한 참조를 포함할 수 있습니다.

    // 개념적인 메소드 요청 객체 구조
    class MethodRequest {
        MethodId methodToCall;
        Object[] arguments;
        Future<?> resultFuture; // 결과를 저장할 Future 객체
    
        // ... 생성자 및 게터/세터 ...
    }
    
  3. 요청의 액티베이션 큐 추가: 프록시는 생성된 메소드 요청 객체를 스레드 안전한 액티베이션 큐(Activation Queue)의 끝에 추가합니다. 이 시점에서 프록시는 클라이언트에게 즉시 반환하며, 만약 결과가 필요한 호출이었다면 해당 메소드 요청과 연결된 퓨처 객체를 함께 반환합니다.

    // 개념적인 프록시 동작
    public Future<ResultType> performAsyncOperation(Object arg1, Object arg2) {
        Future<ResultType> future = new Future<>(); // Future 생성
        MethodRequest request = new MethodRequest(MethodId.PERFORM_ASYNC_OP, new Object[]{arg1, arg2}, future);
        activationQueue.enqueue(request); // 큐에 요청 추가
        return future; // Future 객체 즉시 반환
    }
    
  4. 스케줄러의 요청 선택 및 디스패치: Active Object 스레드 내에서 실행되는 스케줄러(Scheduler)는 액티베이션 큐를 감시합니다. 큐에 요청이 존재하면, 스케줄러는 정의된 정책(일반적으로 FIFO)에 따라 다음 요청을 큐에서 꺼냅니다. 스케줄러는 꺼낸 메소드 요청 객체에 담긴 정보를 바탕으로 서번트(Servant) 객체의 해당 메소드를 호출합니다.

    // 개념적인 스케줄러 동작 (Active Object 스레드 내)
    while (isRunning) {
        MethodRequest request = activationQueue.dequeue(); // 큐에서 요청 가져오기 (블록킹 가능)
        if (request != null) {
            // 서번트 객체의 해당 메소드를 리플렉션 등을 통해 호출
            Object result = servant.executeMethod(request.getMethodId(), request.getArguments());
            // 결과가 있다면 Future에 저장
            if (request.getResultFuture() != null) {
                request.getResultFuture().setResult(result);
            }
        }
    }
    
  5. 서번트의 메소드 실행: 서번트는 스케줄러에 의해 호출된 실제 비즈니스 로직을 수행합니다. 이 실행은 서번트가 속한 Active Object 스레드 내에서 이루어집니다. 따라서 서번트 내부에서는 단일 스레드 문맥에서 안전하게 상태를 변경하거나 작업을 수행할 수 있습니다.

  6. 결과 또는 예외 처리: 서번트 메소드 실행이 완료되면, 결과 값이나 발생한 예외가 해당 메소드 요청과 연결된 퓨처 객체에 저장됩니다. 퓨처 객체는 이 시점에서 "완료(Completed)" 상태가 됩니다.

    // 개념적인 서번트 메소드 내부
    public Object executeMethod(MethodId methodId, Object[] args) {
        if (methodId == MethodId.PERFORM_ASYNC_OP) {
            // 실제 작업 수행
            Object result = doActualWork(args[0], args[1]);
            return result; // 결과 반환
        }
        // ... 다른 메소드 처리 ...
        return null;
    }
    
    // Future 클래스 (간략화)
    class Future<T> {
        private T result;
        private Exception exception;
        private boolean isDone = false;
    
        public synchronized void setResult(T result) {
            this.result = result;
            this.isDone = true;
            notifyAll(); // 대기 중인 스레드에게 알림
        }
    
        public synchronized T get() throws Exception {
            while (!isDone) {
                wait(); // 결과가 준비될 때까지 대기
            }
            if (exception != null) throw exception;
            return result;
        }
    }
    
  7. 클라이언트의 결과 획득: 클라이언트 스레드는 이전에 프록시로부터 받은 퓨처 객체의 get() 메소드를 호출하여 작업 결과를 얻습니다. 작업이 이미 완료되었다면 즉시 결과를 반환받고, 아직 완료되지 않았다면 결과가 준비될 때까지 블록 상태로 대기합니다.

이러한 일련의 과정을 통해 Active Object 패턴은 메소드 호출과 실행을 분리하고, 비동기적인 처리를 가능하게 하며, 서번트 스레드 내에서의 순차적인 실행을 통해 동시성 관리를 단순화합니다.




동시성 문제 해결에 기여하는 방식

Active Object 패턴이 동시성 문제를 해결하는 핵심 기제는 바로 "메소드 호출 시점"과 "실제 실행 시점"을 분리하고, 실제 실행을 단일 Active Object 스레드 내에서 순차적으로 처리하도록 유도하는 것입니다. 이는 다음과 같은 방식으로 동시성 문제를 완화하거나 해결합니다.

  1. 공유 상태에 대한 동시 접근 방지: Active Object 패턴에서 서번트(Servant) 객체는 일반적으로 하나의 전담 스레드(Active Object 스레드) 내에서만 실행됩니다. 여러 클라이언트 스레드가 동시에 Active Object에 작업을 요청하더라도, 이 요청들은 액티베이션 큐(Activation Queue)에 순차적으로 쌓이고, 서번트 스레드는 이 큐에서 요청을 하나씩 꺼내어 처리합니다. 즉, 서번트 객체의 상태는 언제나 한 번에 하나의 메소드에 의해서만 접근 및 수정됩니다. 이는 여러 스레드가 동시에 공유 객체의 상태를 변경하려 할 때 발생하는 복잡한 데이터 경쟁 문제를 자연스럽게 방지합니다. 개발자는 서번트 내부 로직에 대해 멀티스레드 동기화를 크게 신경 쓰지 않아도 됩니다 (단, 서번트가 다른 Active Object를 호출하거나 외부 공유 자원에 접근하는 경우는 예외).

  2. 락킹(Locking) 복잡성 감소: 전통적인 동기화 방식에서는 공유 자원에 접근하는 모든 코드 경로에 대해 적절한 락(Lock) 메커니즘을 적용해야 합니다. 이는 락의 범위, 데드락 가능성, 성능 저하 등 복잡성을 야기합니다. Active Object 패턴에서는 공유 상태가 서번트 스레드 내에 국한되므로, 서번트 객체 자체에 대한 광범위한 락킹이 필요 없어집니다. 동기화는 주로 액티베이션 큐에 요청을 추가하는 과정에서만 필요하며, 이는 표준적인 스레드 안전 큐 구현체(예: ConcurrentLinkedQueue, BlockingQueue 등)를 사용함으로써 손쉽게 해결할 수 있습니다.

  3. 데드락 가능성 감소: 데드락은 둘 이상의 스레드가 서로가 가진 락을 기다리면서 발생하는 문제입니다. Active Object 패턴은 서번트 스레드가 큐에서 요청을 순차적으로 처리하는 단방향 흐름을 가지므로, 서번트 스레드 내부에서 발생하는 데드락 가능성은 줄어듭니다 (단, Active Object 간의 상호 호출 체인이 복잡해지면 데드락이 발생할 수도 있으니 주의해야 합니다). 클라이언트 스레드와 Active Object 스레드 간의 상호작용은 대부분 큐잉 및 퓨처를 통한 결과 대기 방식으로 이루어져, 전통적인 락 기반 동기화에서 흔히 발생하는 데드락 시나리오를 회피합니다.

  4. 비동기 처리를 통한 블록킹 최소화: 클라이언트 스레드는 프록시를 호출하고 즉시 반환받으므로, 작업이 완료될 때까지 블록되지 않습니다. 이는 특히 사용자 인터페이스 스레드와 같이 응답성이 중요한 스레드에서 매우 유용합니다. 복잡하거나 시간이 오래 걸리는 작업도 UI 스레드를 멈추지 않고 백그라운드에서 안전하게 실행할 수 있습니다. 결과가 필요할 때는 퓨처를 통해 선택적으로 대기할 수 있습니다.

이처럼 Active Object 패턴은 호출과 실행의 분리, 큐 기반의 순차 처리, 그리고 퓨처를 통한 비동기 결과 전달 메커니즘을 통해 동시성 문제를 구조적으로 접근하고 해결합니다. 이는 저수준 동기화 메커니즘을 직접 다루는 것보다 추상화 수준을 높여 개발자의 부담을 줄이고, 더 견고하고 관리하기 쉬운 동시성 코드를 작성할 수 있게 합니다.




Active Object Pattern의 장점 (Advantages)

Active Object 패턴은 동시성 프로그래밍에 여러 가지 중요한 장점을 제공합니다.

  1. 향상된 응답성 (Improved Responsiveness): 클라이언트 스레드는 프록시를 호출한 후 작업 완료를 기다리지 않고 즉시 반환받으므로, 블록킹 없이 다른 작업을 계속 진행할 수 있습니다. 이는 특히 GUI 애플리케이션이나 네트워크 서비스와 같이 동시 다발적인 요청을 처리하고 사용자 인터페이스의 반응성을 유지해야 하는 시스템에서 큰 장점입니다.
  2. 단순화된 동시성 관리 (Simplified Concurrency Management): 서번트(Servant) 객체가 전용 스레드 내에서 순차적으로 요청을 처리하기 때문에, 서번트 객체 자체의 내부 상태 관리를 위한 복잡한 스레드 동기화 코드를 상당 부분 줄이거나 없앨 수 있습니다. 공유 상태에 대한 접근은 큐를 통한 단일 스레드 흐름으로 제한됩니다. 이는 개발자가 멀티스레드 환경에서 발생하기 쉬운 데이터 경쟁 등의 오류를 방지하는 데 도움이 됩니다.
  3. 호출과 실행의 분리 (Decoupling of Invocation and Execution): 클라이언트는 메소드를 호출하는 시점과 실제 메소드가 실행되는 시점에 대해 알 필요가 없습니다. 또한, 메소드 호출을 프록시를 통해 추상화함으로써, 실제 실행이 이루어지는 서번트 스레드의 세부 사항(스케줄링 정책, 스레드 풀 사용 여부 등)으로부터 클라이언트 코드가 분리됩니다. 이는 시스템의 유연성과 확장성을 높입니다.
  4. 버퍼링 효과 및 처리량 조절 (Buffering and Throughput Control): 액티베이션 큐(Activation Queue)는 클라이언트 요청을 일시적으로 저장하는 버퍼 역할을 합니다. 클라이언트 요청이 폭주하더라도 서번트가 처리할 수 있는 속도에 맞춰 큐에서 요청을 꺼내 처리함으로써 시스템의 안정성을 유지할 수 있습니다. 또한, 스케줄러 구현을 통해 요청 처리 순서나 동시 실행 수준(예: 서번트 스레드 풀 사용 시)을 조절하여 시스템의 전체 처리량을 최적화할 수 있습니다.
  5. 캡슐화 및 모듈성 향상 (Improved Encapsulation and Modularity): Active Object는 특정 책임(상태 관리 및 비즈니스 로직 실행)을 서번트 객체와 Active Object 스레드 내에 효과적으로 캡슐화합니다. 클라이언트는 프록시 인터페이스만 알면 되므로, 시스템의 모듈성이 향상되고 각 컴포넌트의 독립성이 강화됩니다.

Active Object 패턴은 이러한 장점들을 통해 복잡한 동시성 시나리오를 보다 안전하고 효율적으로 관리할 수 있는 강력한 프레임워크를 제공합니다.




Active Object Pattern의 단점 (Disadvantages)

Active Object 패턴은 여러 장점에도 불구하고, 사용 시 고려해야 할 몇 가지 단점도 존재합니다.

  1. 복잡성 증가 (Increased Complexity): Active Object 패턴은 프록시, 서번트, 액티베이션 큐, 스케줄러, 퓨처 등 여러 구성 요소로 이루어집니다. 이러한 요소들을 직접 구현하거나 설정해야 하므로, 간단한 동기 작업에 비해 전체 시스템의 설계 및 구현 복잡성이 증가합니다. 패턴의 각 요소가 올바르게 상호작용하도록 구성하는 데 추가적인 노력이 필요합니다.
  2. 성능 오버헤드 (Performance Overhead): 메소드 호출이 즉시 서번트에서 실행되는 대신, 메소드 요청 객체를 생성하고 큐에 추가하고 스케줄러가 이를 꺼내 서번트로 디스패치하는 일련의 과정은 동기 호출에 비해 추가적인 오버헤드를 발생시킵니다. 작업 자체가 매우 짧고 빈번하게 호출되는 경우, 이러한 오버헤드가 작업 실행 시간보다 커서 전체 성능에 오히려 부정적인 영향을 미칠 수 있습니다.
  3. 디버깅의 어려움 (Debugging Challenges): 호출 시점과 실행 시점이 분리되고 비동기적으로 동작하므로, 실행 흐름을 추적하고 문제를 진단하는 것이 동기 코드에 비해 훨씬 어렵습니다. 스레드 간의 상호작용, 큐의 상태, 퓨처 객체의 상태 변화 등을 동시에 고려해야 하므로 디버깅 도구의 지원이 중요해집니다.
  4. 큐의 잠재적 병목 현상 및 메모리 문제 (Potential Queue Bottleneck and Memory Issues): 클라이언트 요청 생성 속도가 서번트의 처리 속도보다 지속적으로 빠른 경우, 액티베이션 큐에 요청이 과도하게 쌓일 수 있습니다. 이는 큐의 크기가 무한정 커져 시스템 메모리를 소모하거나, 큐에 오래 대기하는 요청들로 인해 전체 시스템의 응답 시간이 늘어나는 병목 현상을 유발할 수 있습니다. 큐의 크기 제한 및 요청 거부 정책 등을 고려해야 할 수 있습니다.
  5. 비동기 결과 처리의 복잡성 (Complexity of Asynchronous Result Handling): 퓨처(Future) 객체를 통해 결과를 받는 방식은 클라이언트 코드를 비동기적으로 작성하게 만듭니다. 결과가 필요한 시점에서 퓨처를 블록킹하여 기다리거나, 콜백 메커니즘을 사용하거나, 최근 언어의 async/await 구문을 활용해야 하는데, 이는 순차적인 동기 코드에 비해 흐름 제어가 복잡해질 수 있습니다.

이러한 단점들 때문에 Active Object 패턴은 모든 상황에 만능 해결책이 될 수는 없습니다. 패턴의 장점이 단점을 상쇄할 수 있는 복잡한 동시성 시나리오에서 신중하게 선택하여 적용해야 합니다.




주요 활용 사례 (Typical Use Cases)

Active Object 패턴은 특히 호출과 실행의 분리, 비동기 처리, 그리고 단일 스레드 내에서의 안전한 상태 관리가 필요한 다양한 시스템에서 효과적으로 활용될 수 있습니다.

  1. 사용자 인터페이스 (User Interfaces): GUI 애플리케이션에서 시간이 오래 걸리는 작업(예: 파일 로딩, 네트워크 통신, 복잡한 계산)을 메인 UI 스레드에서 직접 수행하면 인터페이스가 멈추고 사용자 경험이 저하됩니다. Active Object 패턴을 사용하면 이러한 작업들을 프록시 호출을 통해 백그라운드 Active Object 스레드로 위임하고, UI 스레드는 즉시 반환받아 반응성을 유지할 수 있습니다. 작업 완료 후 결과는 퓨처를 통해 받아 UI에 업데이트합니다.
  2. 이벤트 처리 시스템 (Event Handling Systems): 다양한 소스에서 발생하는 이벤트를 비동기적으로 수신하고 처리해야 하는 시스템에 적용할 수 있습니다. 이벤트 리스너는 이벤트를 수신하면 Active Object의 프록시를 통해 처리 요청을 액티베이션 큐에 추가하고 즉시 반환합니다. 이벤트 처리는 별도의 스레드에서 순차적으로 또는 제어된 동시성으로 이루어지므로 이벤트 처리 로직의 복잡성을 줄이고 시스템의 안정성을 높일 수 있습니다.
  3. 로깅 서비스 (Logging Services): 애플리케이션에서 발생하는 로그 메시지를 파일이나 네트워크로 기록하는 작업은 디스크 I/O나 네트워크 지연으로 인해 블록킹을 유발할 수 있습니다. 로깅 기능을 Active Object로 구현하면, 로그 메시지 생성은 프록시를 통해 비동기적으로 이루어지고, 실제 파일 쓰기나 네트워크 전송은 Active Object 스레드에서 순차적으로 또는 배치(Batch) 처리될 수 있습니다. 이는 애플리케이션의 주요 스레드가 로깅 작업 때문에 지연되는 것을 방지합니다.
  4. 백그라운드 작업 처리 (Background Task Processing): 웹 서버나 대규모 엔터프라이즈 시스템에서 사용자 요청에 대한 응답 시간을 빠르게 유지하기 위해 시간이 오래 걸리는 작업(예: 이메일 발송, 이미지 변환, 보고서 생성)을 백그라운드로 분리해야 할 때 Active Object 패턴을 사용할 수 있습니다. 사용자 요청 처리 스레드는 Active Object 프록시를 호출하여 작업을 위임하고 즉시 응답을 반환하며, 실제 작업은 백그라운드 스레드에서 처리됩니다.
  5. 명령 패턴(Command Pattern)과의 결합: Active Object 패턴에서 메소드 요청(Method Request) 객체는 사실상 명령 패턴의 커맨드(Command) 객체와 유사한 역할을 합니다. 메소드 호출을 객체로 캡슐화하여 큐에 넣고 나중에 실행하는 방식이므로, 명령 패턴과 자연스럽게 결합하여 비동기적인 명령 실행 시스템을 구축할 수 있습니다.
  6. 메시지 큐 시스템의 내부 구현: Active Object 패턴의 큐잉 메커니즘은 메시지 큐 시스템의 기본적인 동작 방식과 유사합니다. 클라이언트(메시지 생산자)는 큐에 메시지를 넣고, 별도의 소비자 스레드가 큐에서 메시지를 꺼내 처리하는 구조는 Active Object의 프록시-큐-스케줄러/서번트 구조와 맞닿아 있습니다. 복잡한 메시지 처리 로직을 구현할 때 Active Object 패턴을 활용할 수 있습니다.

이 외에도 비동기 I/O 처리, 쓰레드 풀 관리 등 다양한 동시성 요구사항이 있는 곳에서 Active Object 패턴의 변형이나 핵심 아이디어가 응용될 수 있습니다. 중요한 것은 패턴의 장단점을 충분히 이해하고 해결하고자 하는 문제의 특성에 가장 적합한지 판단하여 적용하는 것입니다.




구현 전략 및 실무적 고려사항

Active Object 패턴을 실제 시스템에 적용할 때는 몇 가지 구현 전략과 실무적 고려사항이 필요합니다.

  1. 구성 요소 구현:

    • 프록시: 클라이언트 인터페이스를 그대로 노출하면서 내부적으로 메소드 요청 객체를 생성하고 큐에 추가하는 로직을 구현해야 합니다. 동적 프록시(Dynamic Proxy) 기능을 지원하는 언어(예: Java)에서는 리플렉션(Reflection)을 활용하여 프록시 코드를 자동 생성하거나 간결하게 만들 수 있습니다.
    • 메소드 요청: 각 메소드 호출에 필요한 정보를 담을 클래스를 정의해야 합니다. 메소드 식별자(메소드 이름, ID 또는 Enum), 인자 값 배열, 그리고 결과 저장을 위한 퓨처 객체 등이 포함됩니다.
    • 액티베이션 큐: 스레드 안전한 큐 구현체를 사용해야 합니다. Java의 java.util.concurrent 패키지에 있는 BlockingQueue 구현체(예: ArrayBlockingQueue, LinkedBlockingQueue)는 생산자-소비자 모델에 적합하여 Active Object의 액티베이션 큐로 사용하기에 매우 편리합니다. 큐가 비어 있을 때 스케줄러 스레드를 블록시키고, 새로운 요소가 추가되면 깨우는 기능을 제공합니다.
    • 스케줄러 및 서번트 스레드: 서번트 객체를 소유하고 큐에서 요청을 꺼내 처리하는 별도의 스레드를 생성하고 관리해야 합니다. 간단하게는 하나의 스레드를 생성하여 무한 루프 내에서 큐를 폴링하거나 블록킹 큐에서 대기하는 방식을 사용할 수 있습니다. 더 복잡한 시나리오에서는 스레드 풀(Thread Pool)을 사용하여 여러 서번트 스레드가 요청을 병렬로 처리하도록 확장할 수도 있지만, 이 경우 서번트 내부의 동시성 관리에 대한 고려가 다시 필요해질 수 있습니다 (예: 각 서번트 스레드가 독립적인 상태를 가지거나, 공유 상태에 대한 동기화 메커니즘을 추가).
    • 퓨처: 비동기 작업의 결과 또는 예외를 저장하고 클라이언트가 이를 기다릴 수 있도록 하는 퓨처 클래스를 구현해야 합니다. Java의 Future 인터페이스와 CompletableFuture 클래스는 이러한 용도로 사용될 수 있는 좋은 예시입니다. C#의 Taskasync/await도 유사한 개념을 제공합니다.
  2. 스레드 관리: Active Object를 위한 전용 스레드를 몇 개 생성하고 관리할지 결정해야 합니다. 작업의 특성(CPU 바운드 vs I/O 바운드), 예상되는 부하, 시스템 자원 등을 고려하여 스레드 수를 조절해야 합니다. ExecutorService와 같은 표준 스레드 풀 관리 기능을 활용하는 것이 좋습니다.

  3. 예외 처리: 비동기적으로 실행되는 서번트 메소드에서 발생한 예외를 어떻게 처리할지 설계해야 합니다. 일반적으로 예외는 해당 메소드 요청과 연결된 퓨처 객체에 저장되어 클라이언트가 get() 메소드를 호출할 때 전달되도록 합니다. 퓨처를 통해 예외를 명시적으로 처리하지 않으면 예외가 누락되거나 시스템 전체에 영향을 미칠 수 있습니다.

  4. 종료 메커니즘: 애플리케이션 종료 시 Active Object의 스레드와 큐를 어떻게 안전하게 정리할지 고려해야 합니다. 큐에 남아 있는 요청들을 어떻게 처리할지(모두 처리할지, 폐기할지 등), 스레드를 어떻게 정상적으로 종료시킬지 등의 로직이 필요합니다. 스케줄러 루프를 제어하는 플래그를 사용하거나, 인터럽트 메커니즘을 활용할 수 있습니다.

  5. 성능 튜닝: 구현 후에는 예상되는 부하 시나리오 하에서 성능을 테스트하고, 필요한 경우 액티베이션 큐의 크기, 스레드 풀의 크기, 스케줄링 정책 등을 튜닝하여 최적의 성능을 확보해야 합니다.

  6. 로깅 및 모니터링: 비동기 시스템은 문제 발생 시 디버깅이 어렵기 때문에, 각 구성 요소의 상태(예: 큐의 크기, 처리된 요청 수, 평균 대기 시간 등)를 로깅하고 모니터링하는 시스템을 구축하는 것이 매우 중요합니다.

Active Object 패턴은 강력하지만, 직접 구현하는 것은 상당한 노력을 요구할 수 있습니다. 따라서 이미 이 패턴의 아이디어를 기반으로 구현된 라이브러리나 프레임워크(예: Actor 모델 기반 라이브러리 Akka 등)의 사용을 고려하거나, 언어 자체에서 제공하는 비동기 처리 기능(async/await, Coroutine 등)이 더 적합할 수 있습니다. 패턴을 적용하기 전에 문제의 본질과 시스템 요구사항을 면밀히 분석하는 것이 중요합니다.




지금까지 우리는 POSA2에 소개된 Active Object 패턴에 대해 깊이 있게 살펴보았습니다. 이 패턴의 핵심은 메소드 호출과 실행을 분리하고, 비동기적인 큐잉 메커니즘과 전용 스레드를 통해 동시성 문제를 효과적으로 관리하는 것입니다. 프록시, 서번트, 액티베이션 큐, 스케줄러, 그리고 퓨처라는 다섯 가지 핵심 구성 요소가 유기적으로 결합하여 이러한 기능을 수행합니다.

Active Object 패턴은 클라이언트 스레드의 응답성을 향상시키고, 공유 상태에 대한 동시 접근 문제를 단순화하며, 복잡한 락킹 로직의 필요성을 줄여준다는 점에서 강력한 이점을 가집니다. 특히 UI 애플리케이션, 이벤트 처리, 로깅, 백그라운드 작업 등 비동기 처리 및 호출/실행 분리가 중요한 시나리오에서 유용하게 활용될 수 있습니다.

하지만 패턴의 적용에는 복잡성 증가, 성능 오버헤드, 디버깅의 어려움, 큐 관리 문제 등 단점도 존재합니다. 따라서 Active Object 패턴은 모든 동시성 문제의 만능 해결책이 될 수는 없습니다. 해결하려는 문제의 특성, 시스템의 요구사항, 그리고 개발 팀의 역량을 종합적으로 고려하여 패턴의 적용 여부를 신중하게 결정해야 합니다.

실무적인 관점에서, Active Object 패턴의 아이디어는 현대적인 비동기 프로그래밍 패러다임과 많은 부분 맞닿아 있습니다. Java의 ExecutorServiceFuture/CompletableFuture, C#'s async/await, Python의 asyncio, 그리고 Actor 모델 기반 프레임워크(Akka 등)는 Active Object 패턴이 제시하는 문제 해결 방식을 다양한 형태로 구현하거나 추상화하여 제공합니다. 이러한 언어 기능이나 라이브러리를 이해하고 활용하는 것은 Active Object 패턴의 원리를 실용적으로 적용하는 효과적인 방법입니다.

Active Object 패턴은 동시성 설계에 대한 귀중한 통찰을 제공합니다. 비록 패턴의 모든 구성 요소를 문자 그대로 구현하지 않더라도, 호출/실행 분리, 큐 기반 통신, 단일 스레드 상태 관리라는 핵심 아이디어는 복잡한 동시성 시스템을 설계하고 이해하는 데 큰 도움이 될 것입니다. 동시성 문제를 마주했을 때, Active Object 패턴의 기본 원리를 떠올리며 시스템의 어떤 부분을 비동기적으로 처리하고, 어떤 상태를 어떻게 안전하게 관리할 것인지 고민하는 것은 보다 견고하고 효율적인 소프트웨어를 만드는 밑거름이 될 것입니다.

동시성 프로그래밍의 여정은 쉽지 않지만, Active Object와 같은 잘 정립된 디자인 패턴들을 이해하고 적용한다면 훨씬 체계적이고 효율적으로 문제를 해결해 나갈 수 있습니다. 이 글이 Active Object 패턴에 대한 깊이 있는 이해를 돕고, 여러분의 실무 개발에 유용한 참고 자료가 되기를 바랍니다.

2025년 7월 7일 월요일

Active Object 패턴으로 견고한 게임 서버 로직 구축

 

동시성 문제와 비동기 처리를 위한 Active Object 패턴의 필요성

현대의 소프트웨어 시스템, 특히 고성능과 응답성이 필수적인 게임 서버 개발에서는 동시성(Concurrency) 관리가 핵심 과제 중 하나입니다. 여러 플레이어가 동시에 게임 세계와 상호작용하고, 서버는 이들의 요청을 지연 없이 처리하며, 게임 상태를 일관되게 유지해야 합니다. 이러한 환경에서 전통적인 스레드 기반 동시성 제어 방식(예: 락(Lock)을 사용한 공유 자원 접근)은 복잡성을 증대시키고, 데드락(Deadlock)이나 레이스 컨디션(Race Condition)과 같은 심각한 문제를 유발하기 쉽습니다. 또한, 많은 양의 동시 요청을 처리할 때 성능 병목 현상이 발생하거나, 특정 작업이 전체 시스템을 블록(Block)시키는 상황을 초래하기도 합니다.

이러한 문제들을 해결하기 위해, 메서드 호출과 실제 실행을 분리하여 객체의 상태를 안전하게 관리하고 비동기 처리를 효과적으로 수행할 수 있는 디자인 패턴들이 주목받고 있습니다. 그중 하나가 바로 POSA2(Pattern-Oriented Software Architecture, Volume 2)에 소개된 Active Object 패턴입니다. Active Object 패턴은 각 객체가 마치 자체적인 제어 스레드를 가진 것처럼 동작하게 함으로써, 외부에서의 메서드 호출을 비동기적으로 처리하고 객체의 내부 상태 변경을 단일 스레드에서 순차적으로 수행하도록 유도합니다. 이는 객체의 상태 일관성을 보장하면서도 호출자는 해당 작업의 완료를 기다릴 필요 없이 다른 작업을 계속할 수 있게 해줍니다.

특히 C# 환경에서 게임 서버 로직을 개발할 때, Active Object 패턴은 플레이어 캐릭터, NPC, 게임 아이템과 같은 핵심 게임 객체들의 상태를 안전하게 관리하고 동시에 발생하는 다양한 이벤트와 상호작용을 효율적으로 처리하는 데 매우 유용합니다. C#의 강력한 비동기 프로그래밍 모델인 asyncawait, 그리고 Task 병렬 라이브러리(TPL)는 Active Object 패턴을 구현하기 위한 훌륭한 기반을 제공합니다.

본 글에서는 POSA2에 정의된 Active Object 패턴의 핵심 개념과 구성 요소를 깊이 있게 살펴보고, C# 환경에서 이 패턴을 게임 서버 로직 개발에 어떻게 적용할 수 있는지 실무적인 관점에서 논할 것입니다. 패턴의 기본 구조부터 시작하여, 게임 서버에서의 구체적인 적용 시나리오, C# 기반의 구현 전략, 비동기 처리 및 결과 관리 방법, 패턴의 장단점 분석, 그리고 실제 시스템 설계 시 고려해야 할 사항들까지 상세하게 다룰 것입니다. 이 글을 통해 독자 여러분은 Active Object 패턴을 이해하고 C# 게임 서버 개발에 효과적으로 활용할 수 있는 실질적인 지식과 통찰을 얻게 될 것입니다.


POSA2 Active Object 패턴의 깊이 있는 이해와 C# 적용

Active Object 패턴의 기본 구조와 구성 요소

Active Object 패턴의 핵심 아이디어는 특정 객체(Servant)가 자신만의 스레드에서 동작하며, 외부에서 해당 객체의 메서드를 호출할 때 즉시 실행되는 것이 아니라, 요청(Request) 형태로 큐(Queue)에 쌓이고 Servant의 스레드가 이 큐의 요청을 순차적으로 처리하는 것입니다. 이를 통해 Servant 객체의 상태는 항상 단일 스레드에 의해서만 변경되므로, 복잡한 락 메커니즘 없이도 상태 일관성을 유지할 수 있습니다.

POSA2에서 정의하는 Active Object 패턴은 다음과 같은 주요 구성 요소로 이루어집니다.

  1. Proxy (프록시):

    • 클라이언트(호출자)가 Active Object의 메서드를 호출할 때 사용하는 인터페이스 또는 객체입니다.
    • Proxy는 실제 작업을 수행하는 Servant 객체를 직접 호출하는 대신, 메서드 호출 정보를 담은 요청(Request) 객체를 생성합니다.
    • 생성된 요청 객체를 Activation List(요청 큐)에 추가하고, 호출자에게는 작업 완료 여부를 확인할 수 있는 Future(미래 객체)를 즉시 반환합니다.
    • Proxy는 Servant의 퍼블릭 인터페이스 역할을 하지만, 실제 비즈니스 로직은 수행하지 않습니다.
  2. Servant (서번트):

    • Active Object 패턴의 핵심 비즈니스 로직을 포함하는 실제 객체입니다.
    • Servant는 자신에게 할당된 별도의 스레드 위에서 동작합니다.
    • Activation List에서 요청을 꺼내와 해당 요청에 해당하는 메서드를 실행하고, 결과를 생성합니다.
    • Servant는 자신의 상태를 관리하며, 이 상태는 오직 Servant 스레드에 의해서만 접근되고 변경됩니다.
  3. Scheduler (스케줄러):

    • Activation List에 쌓인 요청들을 관리하고, Servant가 실행할 수 있도록 요청을 선택하고 디스패치(Dispatch)하는 역할을 합니다.
    • 대부분의 경우, Scheduler는 단순히 Activation List에서 요청을 하나씩 꺼내와 Servant의 해당 메서드를 호출하는 방식으로 동작합니다.
    • 요청 처리 순서를 제어하거나, 특정 요청에 우선순위를 부여하는 등의 로직을 포함할 수도 있습니다.
  4. Activation List (활성화 목록 또는 요청 큐):

    • Proxy를 통해 들어온 모든 요청 객체가 저장되는 큐 또는 목록입니다.
    • Servant 스레드는 이 Activation List에서 다음 처리할 요청을 가져옵니다.
    • 동시성 환경에서 여러 Proxy가 동시에 요청을 추가할 수 있으므로, 스레드 안전(Thread-safe)한 자료구조가 사용되어야 합니다. C#에서는 ConcurrentQueue<T>BlockingCollection<T> 등이 적합합니다.
  5. Future (미래 객체 또는 결과 핸들):

    • Proxy가 메서드 호출 시 즉시 반환하는 객체입니다.
    • 이 객체를 통해 호출자는 비동기적으로 수행된 작업의 결과를 나중에 조회하거나, 작업 완료 시 알림을 받을 수 있습니다.
    • 작업이 완료될 때까지 대기(wait)하거나, 비동기적으로 콜백(callback)을 등록하는 등의 기능을 제공할 수 있습니다. C#의 Task 또는 Task<T>가 이 Future의 역할을 완벽하게 수행합니다.

이 구성 요소들이 상호작용하여 Active Object 패턴의 워크플로우를 형성합니다. 클라이언트는 Proxy의 메서드를 호출하고 Future를 즉시 받습니다. Proxy는 요청을 생성하여 Activation List에 넣습니다. Scheduler는 Activation List에서 요청을 꺼내 Servant에게 전달하고 실행시킵니다. Servant는 작업을 완료한 후 Future를 통해 결과를 호출자에게 전달합니다. Servant는 별도의 스레드에서 Activation List의 요청을 처리하는 루프를 계속 실행합니다.

게임 서버 로직에서의 Active Object 패턴 적용 시나리오

게임 서버 개발에서 Active Object 패턴은 다양한 상황에서 유용하게 활용될 수 있습니다. 게임 서버의 핵심은 수많은 게임 객체들의 상태를 관리하고, 이들 객체 간의 상호작용 및 외부(플레이어)와의 상호작용을 처리하는 것입니다. 여러 플레이어 또는 시스템 컴포넌트가 동시에 특정 게임 객체(예: 플레이어 캐릭터, 몬스터, 상자 등)의 상태를 변경하려고 할 때 동시성 문제가 발생하며, 이를 안전하게 처리하는 것이 중요합니다.

Active Object 패턴은 다음과 같은 시나리오에서 빛을 발합니다.

  1. 플레이어 캐릭터 객체: 각 플레이어 캐릭터를 Active Object로 모델링할 수 있습니다.

    • 플레이어의 이동, 공격, 아이템 사용, 스킬 발동 등의 모든 액션 요청은 해당 플레이어 캐릭터 Active Object의 Proxy를 통해 들어옵니다.
    • 요청들은 캐릭터 객체의 Activation List에 쌓이고, 캐릭터 객체의 전용 스레드(또는 스레드 풀의 특정 스레드)에서 순차적으로 처리됩니다.
    • 예를 들어, 동시에 아이템 획득과 스킬 사용 요청이 들어와도, 캐릭터의 Servant 스레드는 이들을 순서대로 처리하여 인벤토리 상태나 스킬 쿨다운 상태의 일관성을 보장합니다.
    • 이 방식은 특정 플레이어 캐릭터의 상태 접근을 단일 스레드로 집중시켜 락 경합을 최소화하고 상태 관리를 단순화합니다.
  2. 게임 월드/지역 객체: 게임 월드 전체 또는 특정 지역(Region)을 Active Object로 만들 수 있습니다.

    • 해당 지역 내의 모든 객체 생성/삭제, 물리 연산 결과 반영, 주기적인 환경 업데이트 등의 작업 요청을 지역 Active Object가 처리합니다.
    • 여러 플레이어의 활동이나 다른 시스템 컴포넌트의 변화로 인해 지역 상태 변경 요청이 들어올 때, 이를 비동기적으로 큐에 넣어 처리하여 지역 상태의 일관성을 유지합니다.
  3. 인벤토리 또는 장비 관리 객체: 플레이어의 인벤토리나 장비 상태를 관리하는 객체를 Active Object로 만듭니다.

    • 아이템 획득/사용/버리기, 장비 착용/해제 등의 작업은 인벤토리 Active Object의 Proxy를 통해 요청됩니다.
    • 동시에 여러 아이템 관련 작업이 발생해도, 인벤토리 Active Object의 Servant 스레드에서 순차적으로 처리되므로, 인벤토리 슬롯 상태나 아이템 수량의 불일치 문제를 방지할 수 있습니다.
  4. NPC 또는 몬스터 AI 객체: 복잡한 AI 로직을 가진 NPC나 몬스터를 Active Object로 모델링할 수 있습니다.

    • 주기적인 상태 업데이트, 플레이어와의 상호작용 처리, 길 찾기 연산 등의 작업 요청을 해당 AI Active Object가 처리합니다.
    • 특히 여러 플레이어가 한 몬스터를 공격하거나, 몬스터가 여러 타겟을 고려해야 할 때, Active Object는 AI 상태 갱신의 동시성 문제를 안전하게 관리할 수 있습니다.
  5. 지속 데이터(Persistence Data) 저장/로딩: 플레이어 데이터나 게임 월드 상태를 데이터베이스에 저장하거나 로딩하는 작업을 처리하는 객체를 Active Object로 만듭니다.

    • 저장 또는 로딩 요청을 비동기적으로 큐에 넣어 처리함으로써, 메인 게임 스레드가 파일 I/O나 네트워크 지연으로 인해 블록되는 것을 방지합니다.
    • 이 패턴을 통해 동시에 여러 플레이어의 저장 요청이 들어와도 순차적으로 안정적으로 처리할 수 있습니다.

이처럼 Active Object 패턴은 게임 서버 내의 상태를 가지는(Stateful) 핵심 객체들을 모델링하는 데 매우 적합합니다. 각 Active Object는 마치 독립적인 액터(Actor)처럼 동작하며, 메시지(요청)를 받아 자신만의 영역에서 안전하게 처리함으로써 시스템 전체의 복잡성을 낮추고 안정성을 높입니다. 이는 특히 대규모 멀티플레이어 게임 서버에서 수많은 객체의 상태를 효율적이고 안전하게 관리하는 데 중요한 역할을 합니다.

C#에서의 Active Object 패턴 구현 전략

C#은 async/await 구문과 Task 병렬 라이브러리(TPL)를 통해 비동기 및 병렬 프로그래밍을 강력하게 지원합니다. 이러한 기능들은 Active Object 패턴을 C#에서 매우 자연스럽고 효율적으로 구현할 수 있도록 해줍니다.

C#에서의 Active Object 패턴 구현 전략은 다음과 같습니다.

  1. Servant (실제 로직 객체):

    • Servant는 일반적인 C# 클래스로 구현됩니다. 이 클래스는 Active Object로 만들고자 하는 핵심 로직(예: PlayerCharacter, Inventory)을 포함합니다.
    • 주의할 점은 이 클래스의 메서드들은 Servant 스레드 위에서 실행될 것이라는 점입니다. 따라서 이들 메서드는 동기적으로 구현될 수 있으며, Servant 스레드 내에서는 락 없이도 자신의 상태를 안전하게 변경할 수 있습니다. 물론 외부 객체나 공유 리소스에 접근할 때는 여전히 동시성 제어가 필요할 수 있습니다.
  2. Activation List (요청 큐) 및 Scheduler (스케줄러 루프):

    • Activation List는 스레드 안전한 큐를 사용합니다. C#에서는 System.Collections.Concurrent.ConcurrentQueue<T>가 적합합니다. 여러 스레드(Proxy)에서 동시에 Enqueue하고, 단일 스레드(Servant)에서 Dequeue하기 좋습니다.
    • 요청 객체 T는 메서드 호출에 필요한 정보를 담고 있어야 합니다. 최소한 실행할 메서드를 식별하는 정보와 인자(arguments)를 포함합니다. 중요한 것은 비동기 작업의 결과를 호출자에게 전달하기 위한 메커니즘입니다. C#의 TaskCompletionSource<T>를 요청 객체 내부에 포함시키는 것이 일반적입니다.
    • Scheduler의 역할은 Servant 객체 내부에 구현되는 무한 루프 형태의 비동기 작업(Task)으로 구현될 수 있습니다. 이 Task는 Activation List에서 요청을 계속 Dequeue하고, 해당 요청에 따라 Servant의 적절한 메서드를 호출합니다.
    public class Servant<TRequest> // TRequest는 요청 타입을 정의
    {
        private readonly ConcurrentQueue<TRequest> _requestQueue = new ConcurrentQueue<TRequest>();
        private Task _processingTask;
        private CancellationTokenSource _cancellationTokenSource;
        private readonly IServantTarget _servantTarget; // 실제 로직을 가진 객체
    
        public Servant(IServantTarget servantTarget)
        {
            _servantTarget = servantTarget;
        }
    
        public void Start()
        {
            if (_processingTask != null && !_processingTask.IsCompleted) return;
    
            _cancellationTokenSource = new CancellationTokenSource();
            _processingTask = Task.Run(() => ProcessQueueAsync(_cancellationTokenSource.Token));
        }
    
        public async Task StopAsync()
        {
            _cancellationTokenSource?.Cancel();
            if (_processingTask != null)
            {
                await _processingTask;
            }
        }
    
        public void EnqueueRequest(TRequest request)
        {
            _requestQueue.Enqueue(request);
        }
    
        private async Task ProcessQueueAsync(CancellationToken cancellationToken)
        {
            // 이 루프는 Servant 스레드(또는 Task가 할당받은 스레드)에서 실행됩니다.
            while (!cancellationToken.IsCancellationRequested)
            {
                if (_requestQueue.TryDequeue(out TRequest request))
                {
                    // 여기서 request를 해석하고 ServantTarget의 메서드를 호출합니다.
                    // request 객체는 실행할 메서드 정보와 인자, TaskCompletionSource를 가집니다.
                    await ProcessSingleRequestAsync(request); // 요청 처리 로직
                }
                else
                {
                    // 큐가 비어있으면 잠시 대기하여 CPU 부하를 줄입니다.
                    await Task.Delay(1, cancellationToken);
                }
            }
        }
    
        private async Task ProcessSingleRequestAsync(TRequest request)
        {
             // TRequest 내부의 정보를 사용하여 ServantTarget의 메서드를 호출하고 결과를 TaskCompletionSource에 설정
             // 예: request.ExecuteOn(_servantTarget);
             // 이 부분은 요청 타입 TRequest와 ServantTarget 인터페이스에 따라 달라집니다.
             // request 객체는 TaskCompletionSource를 포함하고, ServantTarget 메서드 호출 결과를 TCS에 SetResult/SetException 합니다.
             // 실제 구현에서는 리플렉션, delegate, 또는 Command 패턴 등을 사용하여 메서드 호출을 추상화할 수 있습니다.
             // 예: request.InvokeMethod(servantTarget, request.CompletionSource);
        }
    }
    

    위 코드 스니펫은 Servant 객체 내부에 Activation List(_requestQueue)와 Scheduler 루프(ProcessQueueAsync)를 포함하는 기본적인 구조를 보여줍니다. ProcessQueueAsync Task가 바로 Servant 스레드의 역할을 합니다.

  3. Proxy (프록시):

    • Proxy는 클라이언트가 사용하는 공용 인터페이스 또는 클래스입니다.
    • Proxy의 메서드들은 비동기 (async) 메서드로 선언됩니다.
    • 각 메서드는 호출 시 요청 객체를 생성하고, 이 요청 객체를 Servant의 Activation List에 Enqueue합니다.
    • 요청 객체에 포함된 TaskCompletionSource<T>Task 속성을 즉시 반환합니다.
    public interface IMyActiveObject
    {
        Task<int> PerformOperationAsync(string data);
        Task ProcessActionAsync(int id);
    }
    
    public class MyActiveObjectProxy : IMyActiveObject
    {
        private readonly Servant<MyRequest> _servant; // 내부적으로 Servant 인스턴스를 가짐
    
        public MyActiveObjectProxy(Servant<MyRequest> servant)
        {
            _servant = servant;
        }
    
        public Task<int> PerformOperationAsync(string data)
        {
            var tcs = new TaskCompletionSource<int>();
            var request = new MyRequest
            {
                RequestType = RequestType.PerformOperation,
                Args = new object[] { data },
                CompletionSource = tcs // 결과를 받을 TCS를 요청에 담아서 보냄
            };
            _servant.EnqueueRequest(request);
            return tcs.Task; // TCS의 Task를 즉시 반환
        }
    
        public Task ProcessActionAsync(int id)
        {
            var tcs = new TaskCompletionSource<object>(); // void 메서드의 경우 object 또는 non-generic TaskCompletionSource 사용 가능
            var request = new MyRequest
            {
                RequestType = RequestType.ProcessAction,
                Args = new object[] { id },
                CompletionSource = tcs
            };
            _servant.EnqueueRequest(request);
            return tcs.Task; // Task를 즉시 반환
        }
    }
    
    // 요청 객체 정의 (예시)
    public class MyRequest
    {
        public RequestType RequestType { get; set; } // 실행할 메서드 식별자
        public object[] Args { get; set; } // 메서드 인자
        public object CompletionSource { get; set; } // TaskCompletionSource<T> 또는 TaskCompletionSource
    }
    
    public enum RequestType { PerformOperation, ProcessAction }
    
    // ServantTarget 인터페이스 (실제 로직)
    public interface IServantTarget
    {
        int PerformOperation(string data);
        void ProcessAction(int id);
    }
    

    위 예시에서 MyRequest 객체는 RequestType으로 어떤 메서드를 호출할지 식별하고, Args로 인자를 전달하며, CompletionSource를 통해 작업 결과를 받아올 TaskCompletionSource를 전달합니다. Servant의 ProcessSingleRequestAsync 메서드는 이 MyRequest 객체를 받아와 RequestType에 따라 _servantTarget의 적절한 메서드를 호출하고, 그 결과를 CompletionSourceSetResult 또는 SetException 해주는 로직으로 구현됩니다.

  4. Future (미래 객체):

    • C#에서는 System.Threading.Tasks.TaskSystem.Threading.Tasks.Task<TResult>가 Future의 역할을 완벽하게 수행합니다.
    • Proxy 메서드는 Task 또는 Task<T>를 반환하며, 호출자는 await 키워드를 사용하여 작업 완료를 비동기적으로 대기하거나, Task.ContinueWith 등을 사용하여 작업 완료 시 콜백을 등록할 수 있습니다.
    • TaskCompletionSource<T>는 프로그래머가 직접 Task의 완료 상태, 결과, 예외를 제어할 수 있게 해주는 클래스로, Active Object 패턴에서 Servant가 작업 완료 후 Proxy가 반환한 Task를 완료 상태로 만드는 데 사용됩니다.

C#의 async/awaitTask는 Active Object 패턴의 핵심인 '비동기 메서드 호출'과 '미래 결과 관리'를 매우 직관적이고 효과적으로 구현할 수 있도록 지원합니다. Proxy 메서드는 async 키워드를 사용하여 비동기 호출의 시작점을 표시하고, 즉시 Task를 반환합니다. Servant 스레드는 작업을 완료한 후 TaskCompletionSource를 통해 해당 Task를 완료 상태로 만듭니다. 호출자 스레드는 await를 통해 해당 Task가 완료될 때까지 논리적으로 대기하지만, 실제 스레드는 블록되지 않고 다른 작업을 수행할 수 있습니다.

비동기 처리와 결과 관리: C# async/await 및 Task 활용

C#의 비동기 모델은 Active Object 패턴 구현의 핵심적인 부분을 간소화하고 명확하게 만듭니다.

1. 비동기 메서드 호출 및 Task 반환: Proxy의 각 메서드는 async Task 또는 async Task<TResult> 시그니처를 가집니다. 이는 해당 메서드가 비동기적으로 실행될 수 있으며, 작업 완료 시 Task 객체를 반환함을 나타냅니다.

// Proxy 메서드 예시
public async Task<Item> GetInventoryItemAsync(int itemId)
{
    // 요청 생성 및 큐 삽입 로직...
    var tcs = new TaskCompletionSource<Item>();
    var request = new GetItemRequest { ItemId = itemId, CompletionSource = tcs };
    _servant.EnqueueRequest(request);

    // 여기서 await를 사용하여 Servant 스레드에서의 작업이 완료되기를 비동기적으로 대기
    // 이 await는 호출자 스레드를 블록하지 않습니다.
    return await tcs.Task;
}

호출자는 await activeObject.GetInventoryItemAsync(101); 와 같이 호출하면, GetInventoryItemAsync 메서드는 즉시 Task<Item> 객체를 반환하고 호출자 스레드는 다른 작업을 수행합니다. 나중에 Servant 스레드가 요청 처리를 완료하고 tcs.SetResult(item)를 호출하면, await 지점 이후의 코드가 실행됩니다.

2. Servant 스레드에서의 작업 수행: Servant 내부의 스케줄러 루프는 큐에서 요청을 꺼내와 처리합니다. 이 처리는 Servant 스레드 위에서 동기적으로 수행되거나, Servant 스레드가 다시 다른 비동기 작업을 await할 수도 있습니다. 하지만 핵심 비즈니스 로직(객체 상태 변경 등)은 가급적 Servant 스레드에서 직접 수행하는 것이 상태 일관성 관리에 유리합니다.

// Servant의 요청 처리 로직 (예시)
private async Task ProcessSingleRequestAsync(MyRequest request)
{
    try
    {
        switch (request.RequestType)
        {
            case RequestType.PerformOperation:
                var data = (string)request.Args[0];
                // ServantTarget의 메서드를 Servant 스레드에서 실행
                var result = _servantTarget.PerformOperation(data);
                // 결과 설정
                ((TaskCompletionSource<int>)request.CompletionSource).SetResult(result);
                break;
            case RequestType.ProcessAction:
                var id = (int)request.Args[0];
                // ServantTarget의 메서드를 Servant 스레드에서 실행
                _servantTarget.ProcessAction(id);
                // 완료 설정 (void 메서드)
                ((TaskCompletionSource<object>)request.CompletionSource).SetResult(null); // 또는 SetResult()
                break;
            // 다른 요청 타입 처리...
        }
    }
    catch (Exception ex)
    {
        // 예외 발생 시 TCS에 예외 설정
        if (request.CompletionSource is TaskCompletionSource tcs)
            tcs.SetException(ex);
        else if (request.CompletionSource is TaskCompletionSource<int> tcsInt)
            tcsInt.SetException(ex);
        // ... 다른 TResult 타입에 대한 처리
        // 실제 구현에서는 Request 객체가 제네릭 TCS 타입을 알 수 있도록 설계하는 것이 좋습니다.
        // 예: class MyRequest<TResult> { public TaskCompletionSource<TResult> CompletionSource { get; set; } }
        // 또는 Dictionary<Type, object> CompletionSourceMap으로 관리
    }
}

Servant 스레드는 TaskCompletionSource를 통해 Proxy가 반환했던 Task의 상태를 직접 제어합니다. 작업이 성공하면 SetResult로 결과를 전달하고 Task를 완료(RanToCompletion) 상태로 만듭니다. 예외가 발생하면 SetException으로 예외를 전달하고 Task를 Faulted 상태로 만듭니다. 호출자에서 await 하고 있었다면, 결과나 예외를 받을 수 있게 됩니다.

3. Future (Task)를 통한 결과 조회 및 예외 처리: 호출자는 Proxy가 반환한 Task 객체를 통해 비동기 작업의 결과를 기다리거나 처리할 수 있습니다.

// 호출자 코드
try
{
    Console.WriteLine("Active Object 작업 요청...");
    Task<Item> getItemTask = activeObject.GetInventoryItemAsync(101);
    // await 하지 않고 다른 작업 수행 가능
    Console.WriteLine("다른 작업 수행 중...");

    // 나중에 결과가 필요할 때 대기
    Item item = await getItemTask;
    Console.WriteLine($"아이템 획득 완료: {item.Name}");
}
catch (Exception ex)
{
    Console.WriteLine($"작업 실패: {ex.Message}");
}

await getItemTask는 Servant 스레드에서 tcs.SetResult가 호출될 때까지 비동기적으로 대기합니다. 만약 Servant 스레드에서 예외가 발생하고 tcs.SetException(ex)가 호출되었다면, await 지점에서 해당 예외가 다시 throw되어 호출자 코드에서 catch 블록으로 잡을 수 있습니다.

C#의 async/await는 비동기 코드의 흐름을 동기 코드처럼 보이게 만들어 가독성을 높이고, Task 객체는 비동기 작업의 완료, 결과, 예외 상태를 통합적으로 관리하는 강력한 메커니즘을 제공합니다. 이는 Active Object 패턴의 핵심적인 비동기 호출 및 결과 관리 부분을 구현하는 데 있어 매우 효율적입니다.

워크플로우: 요청 생성부터 결과 반환까지

Active Object 패턴의 전체 워크플로우를 C# 구현 관점에서 단계별로 살펴보겠습니다.

  1. 클라이언트(호출자)의 메서드 호출:

    • 다른 객체(클라이언트)가 Active Object의 기능을 사용하기 위해 Proxy 객체의 비동기 메서드를 호출합니다.
    • 예: Task<Item> itemTask = playerCharacterProxy.GetInventoryItemAsync(itemId);
  2. Proxy에서의 요청 객체 생성 및 TCS 준비:

    • Proxy 메서드는 호출에 필요한 정보(메서드 식별자, 인자)를 담은 요청 객체를 생성합니다.
    • 결과를 돌려받기 위한 TaskCompletionSource<TResult> 인스턴스를 생성합니다.
    • 요청 객체 내부에 이 TaskCompletionSource 인스턴스를 저장합니다.
    • 예: var tcs = new TaskCompletionSource<Item>(); var request = new ItemRequest { ItemId = itemId, CompletionSource = tcs };
  3. 요청 객체를 Activation List (큐)에 Enqueue:

    • Proxy는 생성된 요청 객체를 Servant와 공유하는 스레드 안전한 큐(ConcurrentQueue)에 추가합니다.
    • 예: _servantQueue.Enqueue(request);
    • 이 시점에서 Proxy의 역할은 거의 끝났으며, 호출자에게 Future(Task)를 반환할 준비를 합니다.
  4. Proxy, Future (Task)를 반환:

    • Proxy 메서드는 요청 객체에 저장했던 TaskCompletionSource.Task 속성을 호출자에게 반환합니다.
    • 예: return tcs.Task;
    • 호출자는 즉시 Task 객체를 받으며, 이 Task는 현재 'Pending' 상태입니다. 호출자는 이 Task를 await하여 대기하거나, 다른 작업을 계속 수행할 수 있습니다.
  5. Servant 스레드의 Scheduler 루프 동작:

    • Servant 객체 내부에 있는 전용 스레드(Scheduler 루프)는 Activation List (ConcurrentQueue)를 계속 모니터링합니다.
    • 큐에 새로운 요청이 들어오면, Scheduler는 TryDequeue 등을 사용하여 요청을 꺼냅니다.
    • 예: if (_requestQueue.TryDequeue(out Request request))
  6. Servant 스레드에서 요청 처리:

    • Scheduler는 Dequeue한 요청 객체를 분석하여, Servant 객체의 실제 로직 메서드 중 어떤 것을 어떤 인자와 함께 호출해야 할지 결정합니다.
    • Servant 스레드는 해당 로직 메서드를 동기적으로 실행합니다. 이 메서드 내에서는 Servant 자신의 상태를 안전하게 변경할 수 있습니다.
    • 예: Item acquiredItem = _servantTarget.AcquireItem(request.ItemId);
  7. Servant 스레드에서 결과 설정 (SetResult/SetException):

    • Servant 스레드에서의 로직 실행이 완료되면, 요청 객체에 포함되어 있던 TaskCompletionSource를 사용하여 비동기 작업의 결과를 설정합니다.
    • 작업이 성공했으면 SetResult(결과 값)을 호출합니다.
    • 작업 중 예외가 발생했으면 SetException(예외 인스턴스)를 호출합니다.
    • 예: request.CompletionSource.SetResult(acquiredItem); 또는 request.CompletionSource.SetException(ex);
  8. Future (Task)의 상태 변경 및 호출자에게 결과 전달:

    • TaskCompletionSourceSetResult 또는 SetException이 호출되는 순간, Proxy가 이전에 반환했던 Task 객체의 상태가 'Completed' 또는 'Faulted'로 변경됩니다.
    • 호출자 스레드에서 해당 Task를 await하며 대기하고 있었다면, await 지점 이후의 코드가 실행됩니다. 결과 값은 await 표현식의 결과로 반환되거나, 예외는 await 지점에서 다시 throw됩니다.

이러한 워크플로우를 통해, 클라이언트(호출자)는 Active Object의 메서드를 호출하는 시점에 블록되지 않고 작업을 비동기적으로 요청할 수 있습니다. 실제 복잡한 로직 실행과 객체 상태 변경은 Servant의 전용 스레드에서 순차적으로 안전하게 이루어집니다. 결과는 Task를 통해 비동기적으로 전달되므로, 호출자는 필요할 때만 결과를 대기하거나 콜백으로 처리할 수 있습니다.

Active Object 패턴의 장점 및 단점 분석

모든 디자인 패턴과 마찬가지로, Active Object 패턴 또한 명확한 장점과 고려해야 할 단점을 동시에 가집니다. 게임 서버 개발에 적용할 때 이러한 특성을 잘 이해하는 것이 중요합니다.

장점 (Advantages):

  1. 상태 일관성 및 동시성 관리 용이: Active Object의 가장 큰 장점은 Servant 객체의 상태가 단일 스레드에 의해서만 변경된다는 것입니다. 이는 복잡한 락(Lock) 메커니즘 없이도 객체 내부 상태의 일관성을 강력하게 보장하며, 레이스 컨디션을 효과적으로 방지합니다. 게임 객체(캐릭터, 인벤토리 등)의 상태 관리에 매우 유리합니다.
  2. 호출자와 실행자의 결합도 감소 (Decoupling): Proxy를 통해 호출자와 실제 실행(Servant)이 분리됩니다. 호출자는 결과를 기다릴 필요 없이 즉시 반환되는 Future(Task)를 받으므로, 호출 스레드가 블록되지 않습니다. 이는 시스템의 전반적인 응답성을 향상시킵니다.
  3. 구조적인 비동기 처리: 비동기 처리가 패턴의 구조 자체에 녹아있습니다. 복잡한 콜백 체인이나 수동적인 스레드 관리에 비해 async/awaitTask를 활용하여 비동기 흐름을 더 명확하게 표현할 수 있습니다.
  4. 워크로드 분산 및 격리: 각 Active Object는 자체 스레드에서 동작하므로, 부하가 많은 작업을 특정 Active Object로 분산시킬 수 있습니다. 또한, 하나의 Active Object 내에서 발생한 문제(예: 예외)가 다른 Active Object의 스레드에 직접적인 영향을 미치지 않도록 격리할 수 있습니다.
  5. 확장성: 필요에 따라 Active Object의 인스턴스를 늘리거나, 특정 유형의 Active Object를 위한 스레드 풀을 관리하는 등의 방식으로 확장성을 고려할 수 있습니다.

단점 (Disadvantages):

  1. 복잡성 증가: 패턴을 적용하면 Proxy, Servant, Scheduler, Activation List, Future 등 여러 구성 요소를 도입해야 하므로 시스템의 전체적인 구조가 복잡해집니다. 간단한 객체에 적용하기에는 오버 엔지니어링일 수 있습니다.
  2. 성능 오버헤드: 메서드 호출이 요청 객체 생성, 큐 삽입, 큐 대기, Dequeue, 메서드 디스패치 등 여러 단계를 거치므로, 직접적인 동기 호출에 비해 약간의 오버헤드가 발생합니다. 매우 빈번하고 짧은 작업을 처리하는 데에는 불리할 수 있습니다.
  3. 큐 지연 (Latency): 요청이 큐에서 대기하는 시간만큼 지연이 발생합니다. 실시간 반응성이 매우 중요한 특정 게임 로직(예: 정밀한 충돌 판정)에는 부적합할 수 있습니다. 큐에 요청이 많이 쌓이면 지연 시간이 더욱 길어집니다.
  4. 디버깅의 어려움: 비동기적인 흐름과 스레드 간의 메시지 전달 방식 때문에 디버깅이 복잡해질 수 있습니다. 호출 스택이 스레드를 넘나들기 때문에 문제의 근원지를 추적하는 데 어려움이 있을 수 있습니다.
  5. 큐 포화 가능성: 특정 Active Object에게 처리해야 할 요청이 너무 많으면 Activation List가 포화되어 새로운 요청을 더 이상 받지 못하거나 메모리 문제가 발생할 수 있습니다. 큐 사이즈 모니터링 및 부하 조절 메커니즘이 필요할 수 있습니다.
  6. 결과 반환 메커니즘의 복잡성: 결과를 비동기적으로 돌려주기 위해 TaskCompletionSource를 사용해야 하며, 다양한 반환 타입(Task<T>)이나 예외 처리를 요청 객체 내부에 어떻게 담아서 전달할 것인지에 대한 설계가 필요합니다. 이는 비제네릭 Task와 제네릭 Task<T>를 혼합 사용할 때 더 복잡해질 수 있습니다.

Active Object 패턴은 만능 해결책이 아니며, 시스템의 특정 부분, 특히 상태 관리의 복잡성이 높고 동시적인 접근이 빈번한 객체에 선택적으로 적용하는 것이 효과적입니다. 게임 서버에서는 플레이어 캐릭터, 인벤토리, 특정 지역 객체와 같이 핵심적인 상태를 관리하는 요소에 적용하는 것을 우선적으로 고려해볼 수 있습니다.

실무 적용 시 고려사항 및 유의점

C#을 사용하여 Active Object 패턴을 게임 서버에 도입할 때 고려해야 할 몇 가지 실무적인 사항들이 있습니다.

  1. Activation List (큐) 구현 선택:

    • ConcurrentQueue<T>: 논 블록킹(Non-blocking) 방식의 큐입니다. TryDequeue를 사용하면 큐가 비어있을 때 즉시 false를 반환하므로, Servant 루프에서 큐를 주기적으로 확인하며(Task.Delay 등을 사용) CPU 낭비를 줄여야 합니다. 구현이 비교적 간단합니다.
    • BlockingCollection<T>: GetConsumingEnumerable() 또는 Take() 메서드가 큐에 요소가 있을 때까지 스레드를 블록시키는 기능을 제공합니다. Servant 루프를 구현할 때 BlockingCollection.GetConsumingEnumerable()을 사용하면 큐가 비어있을 때 효율적으로 대기할 수 있습니다. 특정 조건에서만 블록이 해제되는 방식으로 구현하기 편리합니다. 게임 서버에서는 실시간성이 중요하므로, 짧은 대기 시간을 가진 ConcurrentQueue 방식이 더 일반적일 수 있으나, 백그라운드 작업 등에는 BlockingCollection도 고려해볼 만합니다.
  2. 요청 객체 설계:

    • 요청 객체(MyRequest 예시)는 Servant가 어떤 메서드를 실행할지, 어떤 인자를 사용할지, 그리고 결과를 어디로 보낼지를 알아야 합니다.
    • 메서드 식별은 Enum, 문자열, 또는 Delegate 타입을 사용할 수 있습니다. 리플렉션은 성능 오버헤드가 있을 수 있으므로 주의해야 합니다. Action/Func Delegate나 Command 패턴을 사용하여 요청 자체를 실행 가능한 객체로 만드는 것이 더 C#스럽고 효율적일 수 있습니다.
    • TaskCompletionSource를 포함시킬 때, 제네릭 타입(TResult)을 어떻게 관리할지가 중요합니다. 모든 요청 타입을 감싸는 비제네릭 기본 요청 클래스를 만들고 내부에 object CompletionSource를 두거나, 제네릭 기본 요청 클래스 Request<TResult>를 사용하는 방법을 고려할 수 있습니다.
  3. Servant 스레드 관리:

    • 각 Active Object마다 전용 스레드를 생성하는 방식(new Thread(...))은 스레드 생성/관리 오버헤드가 크고 스레드 수가 폭발적으로 증가할 수 있습니다.
    • C#에서는 Task.Run()을 사용하여 ThreadPool의 스레드를 이용하는 것이 일반적입니다. 이 경우, Servant의 스케줄러 루프 (ProcessQueueAsync)는 ThreadPool 스레드 중 하나에서 실행됩니다. await 지점 이후에는 다른 스레드에서 재개될 수도 있지만, 핵심 로직 실행(_servantTarget.PerformOperation)은 해당 Task가 실행되는 스레드(처음 시작된 스레드 또는 await 후 재개된 스레드)에서 이루어집니다. 만약 특정 Servant 객체에 대한 모든 작업이 반드시 단일 스레드에서 이루어져야 한다면, SynchronizationContext나 TaskScheduler를 커스터마이징하여 특정 스레드에 고정시키는 방법을 고려해야 할 수 있습니다. 하지만 대부분의 경우 ThreadPoolasync/await의 조합으로 충분하며, Servant 로직 자체를 스레드 안전하게(즉, 자신의 상태만 변경하며 외부 공유 자원에 접근 시에는 별도의 동기화 사용) 작성하는 것이 현실적입니다.
    • Active Object 수가 많아지면, 각 Active Object마다 Task를 생성하는 대신, 소수의 전용 ThreadPool 스레드를 만들고 이들 스레드가 여러 Active Object의 큐를 처리하도록 멀티플렉싱(Multiplexing)하는 구조도 고려해볼 수 있습니다. 이는 Actor 모델의 디스패처와 유사합니다.
  4. 예외 처리:

    • Servant 스레드에서 요청 처리 중 발생하는 예외는 반드시 적절히 처리되어야 합니다. 잡히지 않은 예외는 해당 스레드를 종료시키거나 예측 불가능한 상태를 초래할 수 있습니다.
    • try-catch 블록으로 Servant 로직을 감싸고, 발생한 예외를 요청 객체의 TaskCompletionSource를 통해 호출자에게 전달해야 합니다 (SetException 호출).
    • 큐 처리 루프 자체에서 발생하는 예외 또한 처리하여 Servant 스레드가 갑자기 종료되지 않도록 해야 합니다.
  5. 종료 처리 (Shutdown):

    • 서버 종료 시 Active Object들이 현재 처리 중인 요청을 완료하거나 안전하게 중단하고, 큐에 남은 요청들을 처리하거나 폐기하는 메커니즘이 필요합니다.
    • CancellationTokenSourceCancellationToken을 사용하여 Servant의 스케줄러 루프를 부드럽게 종료하는 방식을 구현할 수 있습니다. 루프는 토큰 취소 요청을 감지하면 더 이상 큐에서 새 요청을 Dequeue하지 않고, 현재 처리 중인 요청까지만 완료한 후 종료합니다. 큐에 남은 요청들은 어떻게 처리할지 정책을 결정해야 합니다 (폐기, 로깅, 다른 곳으로 전달 등).
  6. 성능 모니터링:

    • 각 Active Object의 Activation List (큐) 길이를 모니터링하는 것은 시스템 부하 상태를 파악하는 데 매우 중요합니다. 큐 길이가 지속적으로 길어진다면, 해당 Active Object가 병목 지점일 수 있습니다.
    • Servant 스레드의 CPU 사용률, 요청 처리 시간 등을 프로파일링하여 성능 문제를 진단해야 합니다.
  7. 테스트 용이성:

    • Active Object 패턴은 각 Servant 로직을 단위 테스트하기 용이하게 만듭니다. ServantTarget 인터페이스나 클래스의 메서드들은 동기적으로 작성되므로 일반적인 방법으로 테스트할 수 있습니다.
    • Proxy와 Servant, 큐 간의 상호작용은 통합 테스트를 통해 검증해야 합니다. Task를 반환하는 Proxy 메서드를 await하여 결과를 확인하는 방식으로 테스트 케이스를 작성할 수 있습니다.
  8. 다른 패턴과의 조합:

    • Active Object 패턴의 요청 객체를 구현할 때 Command 패턴을 활용하면, 실행될 작업을 객체화하여 큐에 담고 처리하는 구조를 더욱 명확하게 만들 수 있습니다.
    • 객체 간의 메시지 전달을 위해 메시지 버스(Message Bus)나 이벤트 기반 아키텍처와 Active Object를 함께 사용할 수 있습니다.

Active Object 패턴은 강력하지만, 도입 결정은 신중해야 합니다. 상태 관리의 복잡성이 높고 동시성 문제가 빈번한 핵심 객체에 우선적으로 적용하고, 단순한 객체에는 과도한 추상화나 오버헤드를 피하는 것이 좋습니다. 또한, 철저한 설계와 테스트, 그리고 지속적인 모니터링이 안정적인 시스템 구축에 필수적입니다.


Active Object 패턴을 통한 견고한 게임 서버 로직 구축과 미래 전망

지금까지 POSA2 Active Object 패턴의 기본 개념, C#에서의 구현 전략, 게임 서버 로직 적용 시나리오, 그리고 실무 적용 시 고려사항들을 상세하게 살펴보았습니다. Active Object 패턴은 비동기 메서드 호출과 객체 상태의 단일 스레드 접근이라는 핵심 원리를 통해 동시성 환경에서 발생하는 복잡성을 효과적으로 관리할 수 있는 강력한 도구임이 분명합니다. 특히 C#의 async/awaitTask 모델과의 시너지는 이 패턴을 더욱 실용적으로 만듭니다.

게임 서버 개발에서 Active Object 패턴은 플레이어 캐릭터, 인벤토리, 특정 게임 지역 등 상태를 가지고 있으며 다양한 스레드나 이벤트 소스로부터 동시적인 접근이 발생할 수 있는 핵심 게임 객체들의 로직을 구현하는 데 매우 적합합니다. 각 객체를 Active Object로 모델링함으로써, 해당 객체의 상태 변경 로직은 전용 스레드에서 순차적으로 안전하게 실행되므로 복잡한 락 구조 없이도 상태 일관성을 보장할 수 있습니다. 이는 개발자가 동시성 문제에 대한 걱정을 덜고 핵심 게임 로직 자체에 더 집중할 수 있게 해줍니다. 또한, 비동기 호출 방식을 통해 서버의 전반적인 응답성을 유지하고 특정 작업이 전체 시스템을 블록하는 상황을 방지할 수 있습니다.

물론 Active Object 패턴 도입에는 복잡성 증가, 약간의 성능 오버헤드, 큐 지연 발생 가능성 등의 단점도 존재합니다. 따라서 모든 객체에 일률적으로 적용하기보다는 시스템의 요구사항과 각 객체의 특성을 면밀히 분석하여 패턴 적용이 가장 효과적일 부분을 신중하게 선택해야 합니다. 특히 극도의 저지연(Low Latency)이 요구되는 실시간 상호작용 로직보다는, 상태 일관성이 중요하고 비동기 처리가 용인되는 로직에 더 적합합니다.

실무 적용을 위한 조언:

  1. 점진적 도입: 모든 것을 한 번에 Active Object로 바꾸기보다, 상태 관리 복잡성이 가장 높거나 동시성 문제 발생 위험이 큰 부분부터 시작하여 점진적으로 패턴을 도입하십시오.
  2. C# 비동기 모델 숙지: async/await, Task, TaskCompletionSource, ConcurrentQueue 등의 C# 기능을 깊이 이해하는 것이 효율적인 Active Object 구현에 필수적입니다.
  3. 명확한 책임 분리: Proxy, Servant, Scheduler 각 구성 요소의 역할을 명확히 분리하여 코드를 작성하고, 요청 객체 (Request) 디자인을 신중하게 하십시오. Command 패턴을 활용하는 것을 추천합니다.
  4. 예외 처리 및 종료 처리: Servant 스레드 내에서의 예외 처리와 서버 종료 시 Active Object들의 안전한 종료 메커니즘을 철저하게 구현해야 합니다.
  5. 모니터링 및 프로파일링: 큐 길이, Servant 스레드의 작업 부하 등을 지속적으로 모니터링하고, 성능 병목이 발견되면 프로파일링을 통해 원인을 분석하십시오.
  6. 대안 패턴 고려: Active Object 패턴이 모든 상황에 최적은 아닙니다. Actor 모델, CSP(Communicating Sequential Processes) 등 다른 동시성 모델이나 패턴과 비교하여 시스템에 가장 적합한 방식을 선택하거나 혼합하여 사용하십시오. 특히 Actor 모델은 Active Object 패턴의 아이디어를 확장하여 독립적인 상태와 행동을 가진 주체(Actor)들이 메시지를 주고받으며 상호작용하는 강력한 프레임워크를 제공합니다. C#에서는 Akka.NET과 같은 라이브러리를 통해 Actor 모델을 구현할 수 있습니다.

Active Object 패턴은 게임 서버와 같이 복잡하고 동시적인 환경에서 상태를 가진 객체들의 안정적인 로직을 구축하는 데 탁월한 구조적 가이드를 제공합니다. C#의 현대적인 비동기 프로그래밍 기능을 활용하면 이 패턴을 더욱 간결하고 효율적으로 구현할 수 있습니다. 이 패턴을 깊이 이해하고 실무에 적용함으로써, 여러분은 더욱 견고하고 확장 가능한 게임 서버 아키텍처를 설계하고 구현하는 역량을 강화할 수 있을 것입니다. 복잡한 동시성 문제를 해결하고 싶은 개발자에게 Active Object 패턴은 반드시 고려해야 할 중요한 디자인 패턴입니다.

POSA2 동시성 패턴


고성능 네트워크 시스템의 숨겨진 힘, 동시성과 패턴

현대의 IT 시스템은 점점 더 복잡해지고 있습니다. 특히 사용자 요청의 폭발적인 증가, 실시간 데이터 처리의 요구, 그리고 분산된 환경에서의 상호작용은 시스템 설계자들에게 엄청난 도전을 안겨주고 있습니다. 단일 스레드로 순차적으로 작업을 처리하는 방식으로는 더 이상 요구사항을 만족시킬 수 없습니다. 여기서 '동시성(Concurrency)'의 중요성이 부각됩니다. 여러 작업을 동시에 수행함으로써 시스템의 처리량(Throughput)을 높이고 응답성(Responsiveness)을 개선할 수 있기 때문입니다.

하지만 동시성은 양날의 검과 같습니다. 단순하게 스레드를 늘리거나 비동기 코드를 사용하는 것만으로는 충분하지 않습니다. 복잡하게 얽힌 공유 자원 문제, 교착 상태(Deadlock), 경쟁 상태(Race Condition), 스레드 관리 오버헤드 등 새로운 문제들이 발생하며 시스템의 안정성과 유지보수성을 해칠 수 있습니다. 특히 네트워크 애플리케이션에서는 수많은 클라이언트 연결을 효율적으로 관리하고, I/O 작업의 지연을 최소화하는 것이 핵심인데, 여기서 동시성 설계의 중요성은 극대화됩니다.

많은 개발자가 동시성 문제를 해결하기 위해 고군분투하지만, 사실 소프트웨어 공학 분야에서는 이미 수십 년간 이러한 문제들에 대한 해결책을 패턴 형태로 정립해 왔습니다. 대표적인 것이 ‘Pattern-Oriented Software Architecture, Volume 2 (POSA2): Patterns for Concurrent and Networked Objects’ 서적에 소개된 다양한 패턴들입니다. 이 책은 동시성과 네트워크 환경에서 객체 지향 시스템을 설계할 때 마주치는 일반적인 문제들을 해결하기 위한 검증된 솔루션을 제공합니다.

본 글에서는 POSA2에 제시된 핵심 동시성 패턴들을 살펴보고, 특히 네트워크 애플리케이션의 성능과 안정성을 개선하는 데 이러한 패턴들이 어떻게 적용될 수 있는지 심층적으로 논의할 것입니다. 단순히 패턴을 나열하는 것을 넘어, 각 패턴의 작동 방식, 장단점, 그리고 실무에서 마주칠 수 있는 10가지 구체적인 예시를 통해 독자 여러분들이 자신의 시스템에 적용할 수 있는 실질적인 통찰을 얻도록 돕겠습니다. 20년 IT 개발 경력을 바탕으로 얻은 실무 경험과 깊이 있는 분석을 통해, 복잡한 동시성 및 네트워크 프로그래밍의 세계를 이해하고 효과적인 솔루션을 구축하는 데 필요한 지식을 제공하고자 합니다.


2024년 11월 19일 화요일

n8n으로 자동화 시작하기: 기능 설명과 셀프 호스팅 설정 가이드


n8n 소개

n8n은 "No-code" 워크플로우 자동화 도구로, 코드 없이 다양한 애플리케이션과 서비스를 연결하여 자동화된 작업을 수행할 수 있는 플랫폼입니다. 데이터 처리, 알림 발송, API 통합 등 반복적인 작업을 자동화하고 효율적인 비즈니스 프로세스를 구축하는 데 매우 유용합니다. n8n은 오픈 소스(무료)로 제공되며, 클라우드 기반 호스팅뿐만 아니라 셀프 호스팅을 통해 개인 서버에서 자유롭게 실행할 수 있어 많은 기업과 개발자들에게 큰 인기를 얻고 있습니다.




한국사람들은 n8n 을 주로 "엔팔엔"으로 발음하고, 외국에서는 "엔에잇엔", "네이튼" 등으로 발음합니다.


n8n의 주요 기능

n8n은 다양한 기능을 제공하여 복잡한 자동화 작업을 손쉽게 처리할 수 있습니다. 주요 기능은 다음과 같습니다:


(1) 워크플로우 디자인

n8n은 사용자 친화적인 UI를 통해 워크플로우를 시각적으로 디자인할 수 있습니다. 사용자는 드래그 앤 드롭 방식으로 다양한 작업을 연결하여 복잡한 자동화 프로세스를 쉽게 만들 수 있습니다. 예를 들어, 구글 시트에서 데이터를 읽어와 이메일로 알림을 발송하거나, API 호출을 통해 데이터베이스에 저장하는 등의 작업을 손쉽게 구현할 수 있습니다.


(2) 다양한 통합 지원

n8n은 200개 이상의 외부 서비스와 통합을 지원합니다. 여기에는 Slack, Google Sheets, GitHub, Twitter, AWS, Dropbox 등 여러 인기 있는 애플리케이션이 포함됩니다. 이를 통해 데이터의 흐름을 원활하게 연결하고 다양한 외부 시스템과 상호작용할 수 있습니다.


(3) 조건부 흐름 제어

n8n은 단순히 작업을 순차적으로 처리하는 것 이상으로, 조건부 흐름 제어가 가능합니다. 예를 들어, 특정 조건이 충족될 때만 작업을 실행하거나, 오류가 발생할 경우 예외 처리를 하도록 설정할 수 있습니다. 이는 복잡한 비즈니스 로직을 자동화하는 데 유용합니다.


(4) API 및 Webhook 통합

n8n은 API 호출 및 Webhook을 쉽게 설정할 수 있어, 다양한 시스템과의 실시간 데이터 연동이 가능합니다. 외부 시스템에서 발생한 이벤트에 따라 자동화된 작업을 실행하는 등 복잡한 작업도 처리할 수 있습니다.


(5) 파일 처리

파일을 다루는 기능도 탁월합니다. CSV, JSON, XML 파일을 처리하고 변환하는 기능을 제공하여, 다양한 형식의 데이터를 자동으로 변환하고 전달하는 데 유용합니다.


셀프 호스팅 설정 방법

n8n을 사용하기 위해서는 클라우드 서비스나 개인 서버에 설치할 수 있습니다. 이 섹션에서는 n8n을 셀프 호스팅하는 방법을 단계별로 안내합니다.


(1) 서버 준비

먼저 n8n을 호스팅할 서버가 필요합니다. n8n은 Linux, macOS, Windows에서 모두 실행 가능하며, Docker를 사용하면 다양한 환경에서 쉽게 설정할 수 있습니다. 여기서는 Docker를 이용한 설치 방법을 설명합니다.


(2) Docker 설치

n8n은 Docker 이미지를 제공하므로 Docker를 설치해야 합니다. Docker가 설치되지 않은 경우, 공식 웹사이트(https://www.docker.com)에서 Docker를 설치할 수 있습니다.

  • Linux: sudo apt install docker.io 명령어를 사용해 설치
  • Windows: Docker Desktop을 다운로드하여 설치
  • macOS: Docker Desktop을 설치

(3) n8n Docker 이미지 실행

Docker가 설치된 후, 다음 명령어를 사용해 n8n을 실행할 수 있습니다. 이 명령어는 n8n을 컨테이너로 실행하는 과정입니다.

bash
docker run -d \
  --name n8n \
  -p 5678:5678 \
  -v ~/.n8n:/root/.n8n \
  n8nio/n8n


이 명령어는 n8n을 백그라운드에서 실행하고, 5678 포트를 사용하여 외부에서 접근할 수 있도록 설정합니다. 또한, 데이터가 영구적으로 저장되도록 ~/.n8n 디렉토리를 마운트합니다.


(4) n8n 웹 인터페이스 접속

n8n이 성공적으로 실행되면 웹 브라우저에서 http://localhost:5678 주소로 접속하여 n8n의 웹 인터페이스에 접근할 수 있습니다. 여기서부터는 드래그 앤 드롭 방식으로 워크플로우를 디자인하고 자동화 작업을 시작할 수 있습니다.


(5) 데이터베이스 설정

n8n은 기본적으로 SQLite를 사용하지만, 대규모 운영을 고려한다면 PostgreSQL이나 MySQL을 사용하는 것이 좋습니다. 데이터베이스 설정은 docker-compose 파일을 수정하여 구성할 수 있습니다. 예를 들어, PostgreSQL을 사용하는 방법은 아래와 같습니다:

yaml
version: '3'
services:
  n8n:
    image: n8nio/n8n
    ports:
      - 5678:5678
    environment:
      - DB_TYPE=postgresdb
      - DB_POSTGRESDB_HOST=postgres
      - DB_POSTGRESDB_PORT=5432
      - DB_POSTGRESDB_DATABASE=n8n
      - DB_POSTGRESDB_USER=n8n
      - DB_POSTGRESDB_PASSWORD=n8n_password
    volumes:
      - ~/.n8n:/root/.n8n
    depends_on:
      - postgres
  postgres:
    image: postgres:13
    environment:
      - POSTGRES_USER=n8n
      - POSTGRES_PASSWORD=n8n_password
      - POSTGRES_DB=n8n
    volumes:
      - ~/.postgres:/var/lib/postgresql/data


위와 같은 설정을 통해 PostgreSQL을 데이터베이스로 사용하여 n8n을 안정적으로 운영할 수 있습니다.


결론

n8n은 코드 없이 복잡한 자동화 워크플로우를 구축할 수 있는 강력한 도구입니다. 다양한 외부 서비스와의 통합과 강력한 기능을 통해 비즈니스 자동화의 효율성을 극대화할 수 있습니다. 셀프 호스팅을 통해 개인 서버에서 실행할 수 있으며, Docker를 사용하면 쉽게 설치하고 관리할 수 있습니다. 이제 n8n을 활용해 반복적인 업무를 자동화하고, 비즈니스 프로세스를 최적화해 보세요.

2018년 3월 12일 월요일

윈도우10 메모리/디스크 사용량 100% 문제

윈도우10 메모리 사용량 99% 이상일때

윈도우 10에서 메모리 사용량이 계속 99% 여서 인터넷 찾아보고, services.msc 에서 필요없는 서비스 모두 사용중지해도 계속 99%이상이었는데, 아래처럼 해보니 괜찮아졌음.

1. Windows Key + R
2. Regedit



간략하게 정리하면 레지스트리 편집기를 열어  'HKEY_LOCAL_MACHINE -> SYSTEM -> ControlSet001 -> Services -> NDU'항목으로 이동해 'Start'의 값을 4로 입력해주시면 문제 해결은 됩니다.

저는 이 글을 쓰면서 확인하니 'Start'의 값이 2인데 메모리 사용량에 문제가 없었네요.


출처: http://pastimelife.com/1473 [잡다한 세상]
'HKEY_LOCAL_MACHINE -> SYSTEM -> ControlSet001 -> Services -> NDU'항목으로 이동해 'Start'의 값을 4로 입력



크롬 사용하면 디스크 100% 될때

윈도우 10에서 크롬만 사용하면 디스크가 100%가 되었는데, 크롬 History 파일 찾아서 삭제하니깐 디스크가 100% 되는 문제 없어짐.

1. C:\Users\*your id*\AppData\Local\Google\Chrome\User Data\Default
2. History 파일 찾아서 삭제





2016년 7월 28일 목요일

A Swift Tour

The Swift Programming Language (Swift 3)

최근 Swift 에 부쩍 관심이 간다. Objective-C 를 대체해서 아이폰 어플을 개발하기 위한 언어인데, 확실히 문법이 Objective-C 보다는 쉽고 Modern 하다. 그런데 또한 생소한 문법들도 있는 것 같다.

아이폰 어플 개발용 언어이기 때문에 Mac 환경에서 개발 가능하지만, 간단한 문법 정도는 IBM 에서 제공하는 REPL 에서 테스트 해볼수 있다.

아래 A Swift Tour 에서 간략한 문법을 확인할 수 있다.
짬짬이 시간 날 때마다 공부해봐야 겠다.

A Swift Tour

https://developer.apple.com/library/prerelease/content/documentation/Swift/Conceptual/Swift_Programming_Language/GuidedTour.html#//apple_ref/doc/uid/TP40014097-CH2-ID1

IBM Swift REPL 환경

https://swiftlang.ng.bluemix.net/#/repl/57988668bf9f1feb4143f284




2015년 9월 24일 목요일

영어 비지니스 이메일 작성하기(2015-09-24)

1. ~에 관해서

in respect of (공식적인 비지니스 레터에는 잘 쓰지 않음)
in reference to ~
with regard to ~
regarding to ~
concerning to ~

I am writing in reference to our recent purchase.
I am writing with regard to our recent purchase.
I am writing regarding/concerning to our recent purchase.

나는 최근 구매품에 대해서 적고자 합니다.


2. 유감이다

regret: 유감스럽게 생각한다. 유감의 뜻으로 사용될 때는 would like to(~하고 싶다)와 같이 사용하면 어색해짐. regret 뒤에는 바로 그 유감스러운 사건/일이 와야함.
죄송하다는 표현은 apologize for 를 사용함.

We regret the delay in shipping.
우리는 선적이 늦어진 것에 유감입니다.

We would like to apologize for the delay in shipping.
우리는 선적이 늦어진 것을 사과드리고 싶습니다.


3. 확신시키다 / 보장하다

assure + 사람: 그사람을 확신시키다. 그사람의 의심을 없앤다는 뜻
ensure + 사물: 그 사실이나 조건에 대해서 보장한다는 뜻. ensure 다음에는 that 절이 주로 옴.

I assure you that our products are of the highest quality.
나는 당신에게 우리 상품이 매우 높은 품질을 가지고 있다고 확언한다.

We have a quality control process to ensure that every item is free from defects.
우리는 모든 물건들이 불량품이 전혀 없는 것을 보장하는 양질의 통제 과정이 있습니다.


4. by / until

미래의 어떤 시점 전까지 일어나야 하는 일에는 전치사 by 를 써야하고, 그 시간까지는 계속 그 일이 발생해야하는 사건에는 until 을 써야 함.

Your order will be shipped by Wednesday at the latest.
주문은 늦어도 수요일까지 선적(출하)될 것입니다.

We will be performing maintenance until the 25th.
우리는 25일까지 유지보수가 이루어 질 것입니다.


5. informations

명사 information 을 셀수없는 명사(불가산 명사)이기 때문에 단수만 써야 함.

I would like to request some information about your services.
서비스에 대해서 정보를 요청드립니다.


6. accept / except

accept 는 받아들이다, 수용하다 라는 뜻
except 는 제외시키다, 배제시키다 라는 뜻

We accept all major credit cards as well as checks and money orders.
저희는 모든 신용카드, 체크, 머니오더를 다 취급합니다.

We offer free shipping to every U.S stat except Alaska and Hawaii.
저희는 알래스카와 하와이를 제외하고는 모든 미국에 무료배송을 제공합니다.


출처: http://raccoonenglish.tistory.com/3255

2015년 6월 30일 화요일

우분투14.04 에서 Django + uWSGI + Nginx 설치하기

AWS EC2 Instance 를 생각지도 못한 일로 새로 설치하고 기존에 셋팅되어 있던 것들이 전부 날라가서 웹서버부터 다시 설치해야 했다. 이참에 다시 공부한다는 생각으로 구글링해서 나름 괜찮은 블로그 포스팅을 찾았다. 영문이라 번역도 해볼겸 기록으로 남긴다.

개요

Django 는 Python 애플리케이션이나 웹사이트를 개발하는데 도움을 주는 강력한 웹 프레임워크이다. Django 는 코드를 자체적으로 테스트하기 위한 간단한 웹서버를 가지고 있는데, 제품출시를 위해 이것보다는 좀 더 보안이 좋고 강력한 웹서버가 필요하다.

본 가이드에서는 우분투 14.04 에 Django 애플리케이션을 지원하는 몇가지 Component 들을 어떻게 설치하고 설정하는지 시연할 것이다. 그리고 Django 애플리케션을 위한 uWSGI 애플리케이션 컨테이너 서버에 대해서 설정하고, uWSGI 와의 역프록시를 위한 Nginx를 설치해서 Django 애플리케이션의 보안과 성능을 높이고자 한다.

선결 조건과 목표

본 가이드를 마치기 위해 새로운 우분투 14.04 서버 인스턴스와 sudo 를 사용할 수 있는 root 가 아닌 계정이 필요하다. 우분투 서버 설정은 이곳 에서 배울수 있다.

두개의 다른 가상환경에 Django 를 설치하고 각각의 설정을 따로 핸들링하도록 할 것이다. 두개의 샘플 프로젝트를 생성함으로써 멀티 프로젝트 환경에 한걸음 다가설 수 있다.

Django 애플리케이션을 설치한 후 uWSGI 애플리케이션 서버를 설치하고 설정할 것이다. 이 서버는 HTTP 를 통한 사용자 리퀘스트를 Django 애플리케이션이 처리할수 있는 Python 으로 변환하는 인터페이스 기능을 한다. 그리고 uWSGI 앞단에 Nginx를 설치해서 고성능의 연결 핸들링 메카니즘과 보안모듈을 손쉽게 탑재할수 있는 이점을 가져간다.

자, 이제 시작해보자.

VirtualEnv 와 VirtualEnvWrapper 설치 및 설정하기

먼저 Django 프로젝트와 필요한 사항들을 각각의 가상환경에 설치한다. 이를 위해서, Python 가상 환경을 생성해주는 virtualenv 를 설치하고 virtualenv 작업을 효율적으로 개선시켜주는 virtualenvwrapper 를 설치하자.

이 두 모듈을 Python 패키지 매니저인 pip 를 통해서 설치한다. pip 는 우분투 레포지토리에서 apt-get 으로 설치할 수 있다.
$ sudo apt-get update
$ sudo apt-get install python-pip
본 가이드에서는 Python 2.x 버전을 사용한다. Python 3.x 버전을 사용하는 사용자는 python3-pip 패키지로 설치할 수 있다. 그리고 pip 대신 pip3 명령어를 사용하면 된다.

이제 pip 가 설치되었으면, virtualenv virtualenvwrapper 를 설치할 수 있게 된다.
$ sudo pip install virtualenv virtualenvwrapper
두개의 모듈이 설치되었으면, 이제 shell 에서 virtualenvwrapper 스크립트 사용을 위한 설정을 해야한다. 가상환경은 간편하게 접근하기 위해서 home 폴더안에 Env 폴더에 위치시킨다. WORKON_HOME 이라는 환경변수를 생성하고, 이것으로 virtualenvwrapper 스크립트를 실행한다.

만약 Python3와 pip3 를 사용한다면, 아래 라인을 shell 초기화 스크립트에 추가해야 한다.
$ echo "export VIRTUALENVWRAPPER_PYTHON=/usr/bin/python3"
어떤 Python 버전을 사용하는 가에 상관없이, 아래 라인을 실행하자.
$ echo "export WORKON_HOME=~/Env" >> ~/.bashrc
$ echo "source /usr/local/bin/virtualenvwrapper.sh" >> ~/.bashrc
이제 설정한 Shell 초기화 스크립트를 실행하면 된다.
$ source ~/.bashrc
그리고나면, 가상환경 정보 저장을 위한 Env 폴더가 home 폴더안에 생성되게 된다.

Django 프로젝트 생성하기

이제 가상환경툴이 생겼다. 이것으로 두개의 가상환경을 생성하고 각각의 가상환경에 Django를 설치하고 프로젝트를 생성해보자.

첫번째 프로젝트 생성하기

virtualenvwrapper 스크립트를 이용해서 간단하게 가상환경을 생성할 수 있다.

첫번째 가상환경을 생성해보자
$ mkvirtualenv firstsite
위 명령어는 가상환경을 생성 후 가상환경에 포함된 Python과 pip 를 설치하고 가상환경을 동작시킨다. 가상환경이 동작되면 현재 새로운 가상환경이 동작하고 있다고 알려주는 프롬프트로 아래와 같이 변경된다. 괄호안의 값은 위에서 설정한 가상환경의 이름으로 나온다.
(firstsite)user@hostname:~$
이제부터 pip 로 설치하는 모든 모듈은 전체시스템이 아니라 가상환경 상에 설치가 되어지고 프로젝트 별로 패키지를 관리할 수 있게 된다.

sudo 없이 pip 를 이용해 가상환경에 Django 를 설지하고, 첫번째 Django 프로젝트를 만들어보자.
(firstsite)user@hostname:~$ pip install django
(firstsite)user@hostname:~$ django-admin.py startproject firstsite
home 폴더 안에 firstsite 라는 하위폴더가 생성된다. 샘플 프로젝트를 설정하기 위해서 firstsite 폴더로 이동하자
(firstsite)user@hostname:~$ cd ~/firstsite

프로젝트에서 사용 할 SQLite 데이터베이스를 초기화 하자. 원할경우 SQLite 대신 다른 데이터베이스를 설정해도 되지만, 본 가이드에서는 언급하지 않겠다.
$ ./manage.py migrate
$ python manage migrate
이제 프로젝트 폴더에 db.sqlite3 라는 데이터베이스 파일을 가지게 될 것이다. 그리고 어드민 계정을 하나 생성하자.
$ ./manage.py createsuperuser
$ python manage createsuperuser
username, email address, password, confirm password 를 입력하게 된다.

다음으로, 프로젝트 설정파일을 오픈하자. (원문에서는 nano를 썼는데, 아무래도 vi 가 손에 익어서 vi 로 대체 하였다.)
$ vi firstsite/settins.py
Nginx 로 Django 사이트를 서비스하려면, 사이트에서 사용할 static 파일들을 저장할 폴더를 설정하는 것이 필요하다. Nginx 가 직접 static 파일들을 제공하도록 하면 성능에도 긍정적인 영향을 주게 된다. Django 에게 static 파일들을 프로젝트 기본폴더 안 static 폴더안에 위치시키라고 알려주기 위해 아래 라인을 설정파일의 맨하단에 추가한다.
STATIC_ROOT = os.path.join(BASE_DIR, "static/")
추가가 끝나면 저장하고 Django 사이트에서 사용할 static 파일들을 해당폴더에 수집하는 작업이 필요하다.
$ ./manage.py collectstatic
$ python manage collectstatic
yes 를 입력하고 확인하면 static 파일들을 수집하고 프로젝트 폴더에 static 폴더가 생성된다.

위 일련의 과정이 마무리되면, 이제 첫번째 프로젝트를 개발서버로 실행할 수 있다.
$ ./manage.py runserver 0.0.0.0:8000
$ python manage runserver 0.0.0.0:8000
개발서버 8000번 포트를 이용해서 시작되는데, 아래와 같이 서버의 도메인이름 혹은 IP 주소뒤에 8000 포트를 붙여서 브라우저에서 열어보자.
http://server_domain_or_IP:8000

It worked! 페이지가 제대로 보이면 성공한 것이다.
http://server_domain_or_IP:8000/admin
URL 맨끝에 /admin 을 붙이면 어드민 페이지 접속할수 있고, 아까 생성했던 어드민 계정으로 로그인 할 수 있다.

이제 실행중인 개발서버를 CTRL-C 로 종료시키고, 두번째 프로젝트를 생성해 보자.

두번째 프로젝트 생성하기

두번째 프로젝트 역시 첫번째와 동일하다. 프로젝트를 한번 생성해봤기 때문에 이번 섹션은 간단하게 축약해서 설명하도록 하겠다.

다시 home 폴더로 돌아와서 두번째 가상환경을 생성하고 Django 를 설치하자.
(firstsite)user@hostname:~/firstsite$ cd ~
(firstsite)user@hostname:~$ mkvirtualenv secondsite
(secondsite)user@hostname:~$ pip install django
(secondsite)user@hostname:~$ django-admin.py startproject secondsite
(secondsite)user@hostname:~$ cd secondsite
(secondsite)user@hostname:~/secondsite$
새로운 가상환경을 생성하면 첫번째 가상화면은 종료되고, 생성한 새로운 가상화면으로 변경된다. 여기서 설치한 Django 는 이전에 설정했던 것과는 완전히 분리된다. 즉, 필요에 따라 독립적으로 관리할 수 있게된다.

첫번째 프로젝트와 마찬가지 방식으로 설정해보자.
$ ./manage.py migrate
$ ./manage.py createsuperuser
$ vi secondsite/settings.py
STATIC_ROOT = os.path.join(BASE_DIR, "static/")
$ ./manage.py collectstatic
$ ./manage.py runserver 0.0.0.0:8080
http://server_domain_or_IP:8080
http://server_domain_or_IP:8080/admin

가상환경 종료하기

가상환경 종료를 위해서는 아래 명령어를 입력하면 된다.
$ deactivate

가상환경 종료 후 설정한 가상환경을 다시 불러서 작업을 하기 위해서는 workon 명령어를 사용하면 된다.
$ workon firstsite
또는,
$ workon secondsite
마찬가지로 종료하기 위해서는 deactivate 명령어를 사용하면 된다.

uWSGI 애플리케이션 서버 설정하기

이제 두개의 Django 프로젝트 설정을 마쳤고, uWSGI 설정을 할 준비가 되었다. uWSGI 는 Django 애플리케이션과 WSGI 라는 표준 인터페이스로 통신하는 애플리케이션 서버이다. 우분투에서 Nginx 와 uWSGI 를 설정하느 더 자세한 사항은 여기에서 확인할 수 있다.

uWSGI 설치하기

위에서 제공한 링크와는 다르게, 본 가이드에서는 uWSGI 를 전체시스템에 설치할 것이다. 이렇게 함으로써, 멀티 Django 프로젝트를 관리할때 충돌을 줄여줄 수 있다. uWSGI 를 설치하기 전에 uWSGI 와 의존성이 있는 Python Development 파일을 먼저 아래와 같이 설치해야 한다.
$ sudo apt-get install python-dev
development 파일들이 설치되면, pip 를 통해서 전체시스템에 uWSGI를 설치하자.
$ sudo pip install uwsgi
설치 후 uWSGI를 간단하게 테스트 해볼수 있다. 아래와 같이 첫번째 프로젝트를 테스트해보자.
$ uwsgi --http :8080 --home /home/user/Env/firstsite --chdir /home/user/firstsite -w firstsite.wsgi
여기에서, 가상환경 ~/Env, 프로젝트폴더 firstsite 그리고 프로젝트폴더에 포함된 wsgi.py 파일을 사용할수 있게 uWSGI 에 알려주었다. 위 테스트에서는 8080 포트를 이용해서 HTTP 로 프로젝트를 서비스했고, 도메인 혹은 IP 에 :8080 을 붙여서 브라우저에서 오픈하면 runserver 와 동일하게 Django 사이트가 동작하는 것을 확인할 수 있다.(그렇지만 /admin 의 static 엘리먼트들은 아직 동작하지 않는다.) CTRL-C로 테스트를 종료할 수 있다.

설정파일 생성하기

커맨드라인에서 uWSGI 를 동작시키는 것은 테스트 할때 유용하다. 그렇지만, 실제 배포환경에서는 특별히 도움이 되지 않는다. 그래서 uWSGI 를 독립된 애플리케이션들을 자동으로 관리해주는 "Emperor mode" 로 동작시킬 것이다.

우선, 설정파일을 저장할 폴더를 생성하자. 전체시스템에 적용되어야 하기 때문에 /etc/uwsgi/sites 폴더를 생성하고 설정파일을 저장할 것이다. 폴더를 생성하고 이동하자.
$ sudo mkdir -p /etc/uwsgi/sites
$ cd /etc/uwsgi/sites
위 폴더에 서비스할 프로젝트의 설정파일을 각각 생성해야 한다. uWSGI 프로세스는 다양한 포맷의 설정파일을 가져올수 있지만, 본 가이드에서는 간단하게 .ini 파일을 사용할 것이다.
$ sudo vi firstsite.ini
[uwsgi]
project = firstsite
base = /home/user

chdir = %(base)/%(project)
home = %(base)/Env/%(project)
module = %(project).wsgi:application

master = true
processes = 5

socket = %(base)/%(project)/%(project).sock
chmod-socket = 664
vacuum = true
설정파일의 시작은 무조건 [uwsgi] 로 시작하여야 하고 모든 설정은 이 섹션아래에 있어야 한다. project 이름과 home 폴더는 재사용하기 위해서 변수 project base 로 만들어준다.

chdir 에 프로젝트 경로, home 에 가상환경의 경로를 설정하고, 프로젝트와 어떻게 상호작용할지를 알려준다(프로젝트폴더에 있는 wsgi.py 에서 "application" 을 임포트한다.)

마스터 프로세스와 5개의 워커를 설정해준다.

다음으로, uWSGI 가 어떻게 네트웤을 연결해야할지를 설정한다. 위 테스트에서는 HTTP 와 네트웤 포트를 사용했지만, Nginx 를 역프록시로 사용할 것이기 때문에 더 좋은 옵션이 있다.

모든 모듈들이 하나의 서버에서 동작하기 때문에 네트웤 포트를 사용하는 대신 더욱 안전하고 성능좋은 유닉스소켓을 사용할 수 있다. 유닉스소켓은 HTTP 를 사용하지 않고, 다른 서버와 통신하기 위해 디자인된  바이너리 프로토콜인 uWSGI의 uwsgi 프로토콜을 사용한다. Nginx 는 기본적으로 uwsgi 프로토콜을 사용할수 있기 때문에 유닉스소켓을 사용하는 것이 가장 좋은 선택이라고 볼수있다.

또한 웹서버가 쓰기권한을 가질수 있도록 소켓의 권한을 변경하여야 한다.

마지막으로 서버가 Stop 되었을때 자동으로 소켓파일이 삭제되도록 vacuum 옵션을 준다.

이것으로 첫번째 프로젝트의 uWSGI 설정이 완료되었다.

설정파일에 변수를 사용하는 것의 장점은 재사용하기가 놀랍도록 간단하다는 것이다. 첫번째 프로젝트의 설정파일을 복사해서 두번째 프로젝트의 설정파일로 만들어 보자.
$ sudo cp /etc/uwsgi/sites/firstsite.ini /etc/uwsgi/sites/secondsite.ini
두번째 프로젝트 설정파일을 열고 project 변수의 이름을 두번째 프로젝트의 이름으로 변경해주면 된다.
$ sudo vi /etc/uwsgi/sites/secondsite.ini
[uwsgi]
project = secondsite
base = /home/user

chdir = %(base)/%(project)
home = %(base)/Env/%(project)
module = %(project).wsgi:application

master = true
processes = 5

socket = %(base)/%(project)/%(project).sock
chmod-socket = 664
vacuum = true
이제 두번째 프로젝트 설정파일도 준비되었다.

uWSGI 를 위한 Upstart 스크립트 생성하기

Django 프로젝트를 서비스하기 위해서 필요한 설정파일이 준비되었다. 그렇지만, 아직 자동화된 프로세스는 아니기 때문에 부팅시에 uWSGI를 자동으로 시작시켜주기위한 Upstart 스크립트를 생성할 것이다.

Upstart 스크립트 파일은 /etc/init 폴더에 생성한다.
$ sudo vi /etc/init/uwsgi.conf
description "uWSGI application server in Emperor mode"

start on runlevel [2345]
stop on runlevel [!2345]

setuid user
setgid www-data

exec /usr/local/bin/uwsgi --emperor /etc/uwsgi/sites
uWSGI 서비스를 위한 설명문을 작성하고 자동으로 동작될때의 런레벨을 알려준다. 여기서는 2,3,4,5의 런레벨로 설정한다.

다음으로 프로세스가 어떤 유저와 그룹으로 동작될지를 설정한다. 유저는 지금까지 파일을 생성한 권한이 있는 사용자로 설정을 하도록 하고 그룹은 www-data 로 설정한다. uWSGI 설정파일에서의 소켓설정은 웹서버가 소켓에 쓰기권한을 가지고 있어야 한다.

마지막으로, 실제 실행 명령어를 입력한다. 설정파일이 있는 폴더를 입력하고 uWSGI를 Emperor 모드로 시작한다. uWSGI 가 해당 파일들을 읽고 각 프로젝트를 서비스하게 된다.

완료되면 저장하고 종료하자. Nginx를 설치하기전까지는 www-data 그룹이 없기 때문에 아직 uWSGI 를 시작하지 않는다.

Nginx 설치하기 및 역프록시 설정하기

uWSGI 설정을 마치고 사용할 준비가 되었다. 이제 Nginx를 설치하고 역프록시로 설정해보자.
$ sudo apt-get install nginx
Nginx 가 설치되면 각 프로젝트에 대한 서버블록 설정파일을 만들수 있다. 첫번째 프로젝트에 대한 서버블록 설정파일부터 만들어보자.
$ sudo vi /etc/nginx/sites-avaliable/firstsite
server {
    listen 80;
    server_name firstsite.com www.firstsite.com;

    location = /favicon.ico { access_log off; log_not_found off; }
    location /static/ {
        root /home/user/firstsite;
    }

    location / {
        include         uwsgi_params;
        uwsgi_pass      unix:/home/user/firstsite/firstsite.sock;
    }
}
첫번째 프로젝트가 접속할 서버 도메인 이름과 포트 숫자를 지정한다. 다음으로, Favicon 이 없어도 오류를 발생시키지 않도록 설정하고, 정적파일의 폴더를 지정한다.
uswgi_pass 를 이용해서 트래픽을 프로젝트 폴더에 있는 firstproject.sock 소켓파일로 전달하도록 지정한다. 또한, include 를 이용해 네트웤 연결 핸들링에 필요한 uwsgi 파라메터를 포함시킨다.

위에서 생성한 설정파일을 복사해서 두번째 프로젝트용 Nginx 설정파일을 만든다.
$ sudo cp /etc/nginx/sites-available/firstsite /etc/nginx/sites-available/secondsite
설정파일을 오픈해서 firstsite 를 secondsite 로 변경하고 server_name 에 사용할 도메인을 입력한다.
$ sudo vi /etc/nginx/sites-avaliable/secondsite
server {
    listen 80;
    server_name secondsite.com www.secondsite.com;

    location = /favicon.ico { access_log off; log_not_found off; }
    location /static/ {
        root /home/user/secondsite;
    }

    location / {
        include         uwsgi_params;
        uwsgi_pass      unix:/home/user/secondsite/secondsite.sock;
    }
}
다음으로, Nginx 의 sites-enabled 폴더에 위 설정파일들의 링크를 생성한다.
(기본적으로 설정파일들을 만들때는 sites-available 폴더에 만들어 놓고, 서비스를 운영할 때 설정파일들을 sites-enabled 폴더로 링크복사해서 사용한다.)
$ sudo ln -s /etc/nginx/sites-available/firstsite /etc/nginx/sites-enabled
$ sudo ln -s /etc/nginx/sites-available/secondsite /etc/nginx/sites-enabled
Nginx 설정파일이 정상적으로 만들어졌는지 체크해보자.
$ sudo service nginx configtest
설정파일이 문제가 없으면 Nginx 를 재시작해서 설정파일을 다시 로딩한다.
$ sudo service nginx restart
아직 uWSGI 를 시작한적이 없기때문에 uWSGI 역시 동작시킨다.
$ sudo service uwsgi start
이제 전에 만들었던 두 프로젝트 모두에 접근할수 있게 된다. 기대한대로 일반 사이트와 어드민 사이트가 제대로 동작할 것이다.

결론

본 가이드에서, 각각의 가상환경을 가진 두개의 Django 프로젝트를 생성하고 설정했다.  또한 각각의 가상환경을 사용하는 독립적인 프로젝트를 서비스하기 위해 uWSGI 도 설정했다. 그리고나서, 클라이언트 연결을 핸들링하고 클라이언트 리퀘스트에 따라 알맞은 프로젝트를 서비스하는 역프록시 역할의 Nginx 도 설정했다.

Django 는 제공되는 수많은 모듈들로 프로젝트와 애플리케이션 생성을 간단하게 만들어주고, 개발자는 특별한 요소에만 집중할수 있도록 해준다. 본 가이드에서 설명하고 있는 일반적인 툴체인들을 지렛대삼아 싱글서버에서 더욱 쉽게 Django 애플리케이션을 서비스 할수 있길 바란다.

AWS EC2 Instance 에서 whereami 설치 후기

어제(2015년 6월 29일) 아마존 무료계정으로 설치했던 서버에서 놀다가 갑자기 무슨 생각이 들었는지 "whereami"를 타이핑 했더니 그런명령어 없다고 apt-get 으로 설치하랬다.
$ whereami
sudo apt-get install whereami

그래서 아무생각없이 whereami 를 설치했는데, 세상에 설치하다가 도중에 멈추더니 EC2 인스턴스가 그대로 죽어버렸다. 급하게 aws 콘솔로 들어가서 확인했는데 Instance Status 가 1/2 checks passed 라고 나오고 Instance Status Checks 가 패스되지 않는다. (putty 로 접속했는데 계속 network error 가 발생한다.)

구글링해보니 reboot을 해보라고 해서 reboot 을 해봤는데 아무 동작을 하지 않는다. 그래서 할수없이 Instance 를 stop 시키고 다시 start 를 해봤다.(Instance 를 stop 시키니깐, public IP가 변경되버려서 도메인을 다시 설정했다.)

아무리해도 도무지 Instance 가 동작하지 않아서 결국 Instance 를 terminate 시키고 새 Instance 를 생성해야 했다.

아직도 원인이 뭔지 왜 Instance 가 멈췄는지 알수가 없다. aws 포럼에도 비슷한 문제로 질문을 한 사용자가 있는데, 제대로 된 답변이 달려있지 않다.

결론,

AWS EC2 Instance(Ubuntu14.04)에서 whereami 를 절대 설치하지 말자!


2015년 6월 18일 목요일

[CSS] 구글블로거 본문글의 pre 태그에 블럭효과 주기

요즘 Test-Driven Development with Python 이란 책 - (한국어판은 "클린 코드를 위한 테스트 주도 개발")을 보면서 Django 공부를 다시 하고 있는데, 영문판 버전의 경우 웹사이트에서 무료로도 볼수가 있다.

웹사이트의 포스팅 중간에 나오는 블럭이 마음에 들어서 CSS로 따라 만들어보고자 한다.

대략 아래와 같은 느낌의 블럭이다.

pre 태그에 블럭효과 주기

CSS 편집

아래 CSS 를 구글블로거의 디자인 > 템플릿디자이너 > 고급 > 맞춤 CSS 추가 부분에 입력하면 된다.



사용법

본문글을 쓸때 HTML 을 선택하고 사용하고 싶은 부분에 pre 태그로 감싸면 된다. 이때 pre 태그의 class="screen" 값을 넣어주면 된다.(class 명은 변경가능)




Fabric 설치 후 DistributionNotFound 오류수정

Python Django 로 웹서비스 개발을 하는 예제를 따라하는 중에 Fabric 을 사용하여 운영서버(혹은 Staging 서버, 개발서버)에 배포(Deploying) 하는 부분이 나왔길래 책에서 설명하는 그대로 따라했는데, 역시나 한방에 되지를 않는다.


책에서 설명하기를 Fabric 은 Pycrypto에 의존성이 있기때문에 pycrypto 를 먼저 설치해야 되는데 윈도우환경에서는 윈도우용 pycypto 를 먼저 설치하고 Fabric 을 설치하라고 했다.


Fabric depends on pycrypto, which is a package that needs compiling. Compiling on Windows is a rather fraught process; it’s often quicker to try and get hold of precompiled binaries put out there by some kindly soul. In this case the excellent Michael Foord[12] has provided some Windows binaries. (Don’t forget to giggle at the mention of absurd US munitions export controls.)
So the instructions, for Windows, are:
  1. Download and install pycrypto from the previous URL.
  2. pip install Fabric.
Another amazing source of precompiled Python packages for Windows is maintained by Christoph Gohlke.


Fabric의 경우 최신버전이라도 아직 Python 3.x 버전은 지원을 하지 않기 때문에 Python 2.7 버전에 설치를 했다.

$ pip2 install fabric

Fabric을 설치 후 잘 동작하는지 확인을 해보았는데, 에러가 발생했다.

$ fab --help
Traceback (most recent call last):
  File "c:\Python27\Scripts\fab-script.py", line 5, in <module>
    from pkg_resources import load_entry_point
  File "c:\Python27\lib\site-packages\pkg_resources\__init__.py", 
line 3084, in <module> 
    @_call_aside
  File "c:\Python27\lib\site-packages\pkg_resources\__init__.py", 
line 3070, in _call_aside 
    f(*args, **kwargs)
  File "c:\Python27\lib\site-packages\pkg_resources\__init__.py", 
line 3097, in _initialize_master_working_set 
    working_set = WorkingSet._build_master()
  File "c:\Python27\lib\site-packages\pkg_resources\__init__.py", 
line 651, in _build_master
    ws.require(__requires__)
  File "c:\Python27\lib\site-packages\pkg_resources\__init__.py", 
line 952, in require
    needed = self.resolve(parse_requirements(requirements))
  File "c:\Python27\lib\site-packages\pkg_resources\__init__.py", 
line 839, in resolve
    raise DistributionNotFound(req, requirers)
pkg_resources.DistributionNotFound: The 'ecdsa>=0.11' 
distribution was not found  and is required by paramiko

Fabric을 실행하는 fab 에서 오류가 발생했다. 어떤오류인지 몰라서 여기저기 구글링을 했는데 pip 를 업그레이드 하면 된다 virtualenv 를 업그레이드 하면 된다고 하는데, 다 해봐도 계속 오류가 발생했다.

역시 해답은 오류 Traceback 안에 있었는데, ecdsa 를 설치(혹은 업그레이드)하니깐 오류가 발생하지 않았다.

$ pip2 install ecdsa
Downloading/unpacking ecdsa
Installing collected packages: ecdsa
Successfully installed ecdsa
Cleaning up...