동시성

동시성은 소프트웨어 개발에서 가장 어려운 측면 중 하나입니다. 여러 프로세스나 스레드가 동일한 데이터를 조작할 때 동시성 문제가 발생하기 마련입니다. 동시성을 다루는 것은 어렵습니다. 왜냐하면 문제가 발생할 수 있는 모든 시나리오를 생각해내는 것이 어렵기 때문입니다. 무엇을 하든 간에 항상 놓치는 부분이 있습니다. 게다가 동시성 문제는 테스트하기도 어렵습니다. 자동화된 테스트가 소프트웨어 개발의 기초가 되어야 한다고 생각하지만, 동시성 문제에서 그러한 테스트가 필요한 보장을 제공하기는 어렵습니다.

동시성 문제

동시성 문제는 여러 프로세스나 스레드가 동시에 데이터를 처리할 때 발생합니다. 동시성 문제를 방지하기 위한 시스템들이 존재하지만, 이들 시스템은 새로운 문제를 일으킬 수 있습니다. 예를 들어, 두 사람이 동시에 동일한 데이터를 작업하려고 할 때 발생하는 문제는 대표적으로 업데이트 손실(lost update)과 비일관성 읽기(inconsistent read)입니다.

  • 업데이트 손실: 두 사용자가 같은 데이터를 수정하고자 할 때, 한 사용자가 다른 사용자의 업데이트를 덮어써서 이전 사용자의 변경 사항이 손실되는 경우를 말합니다.

  • 비일관성 읽기: 두 개의 관련된 데이터 조각을 동시에 읽었지만, 이들이 서로 일치하지 않아서 잘못된 결과를 초래할 때 발생합니다.

이러한 문제는 동시성 제어 시스템이 해결하려는 주요 문제이며, 동시성 제어에서 가장 기본적인 도전 과제입니다.

실행 컨텍스트

시스템에서 처리 작업이 발생할 때, 이 작업은 특정 컨텍스트 내에서 발생합니다. 실행 컨텍스트는 요청(request)과 세션(session)으로 구분될 수 있습니다.

  • 요청(request): 클라이언트로부터의 단일 호출로, 서버는 이 호출에 응답합니다.

  • 세션(session): 클라이언트와 서버 간의 장기적인 상호작용으로, 여러 요청이 포함될 수 있습니다.

이 두 컨텍스트는 서버에서 프로세스와 스레드로 처리됩니다. 프로세스는 보통 독립적인 실행 단위이고, 스레드는 경량 실행 단위로 여러 스레드가 하나의 프로세스 내에서 작동할 수 있습니다. 스레드는 리소스 활용이 효율적이지만, 메모리 공유로 인한 동시성 문제가 발생할 수 있습니다.

격리와 불변성

동시성 문제를 해결하는 데 중요한 두 가지 개념은 **격리(isolation)**와 **불변성(immutability)**입니다.

  • 격리: 데이터를 분할하여 한 번에 하나의 활동적인 에이전트만 접근하도록 합니다. 예를 들어, 운영 체제의 메모리에서 프로세스는 메모리의 특정 부분에만 접근할 수 있습니다.

  • 불변성: 데이터가 변경될 수 없도록 하는 것입니다. 일부 데이터가 불변 데이터로 식별되면, 동시성 문제 없이 공유할 수 있습니다.

낙관적 동시성 제어와 비관적 동시성 제어

동시성 제어에는 **낙관적 동시성 제어(Optimistic Concurrency Control)**와 비관적 동시성 제어(Pessimistic Concurrency Control) 두 가지 방법이 있습니다.

  • 낙관적 동시성 제어: 데이터가 수정될 때까지 동시성 문제가 발생하지 않을 것으로 가정하고, 충돌이 발생할 때 이를 처리합니다. 이 방법은 많은 동시성을 제공하지만, 충돌이 발생했을 때 처리가 복잡할 수 있습니다.

  • 비관적 동시성 제어: 충돌을 방지하기 위해 데이터에 대한 잠금을 걸어, 다른 사용자가 데이터를 동시에 수정할 수 없도록 합니다. 이 방법은 동시성이 제한되지만 충돌을 예방할 수 있습니다.

교착 상태(Deadlocks)

비관적 동시성 제어에서 자주 발생하는 문제는 **교착 상태(deadlock)**입니다. 예를 들어, 두 사용자가 서로 다른 리소스를 잠그고, 이 리소스를 획득하려고 할 때 교착 상태가 발생할 수 있습니다. 이 문제는 소프트웨어에서 자동으로 감지할 수 있으며, 이를 처리하기 위한 다양한 기법이 존재합니다. 예를 들어, 시간 초과(timeout)를 설정하여 시간이 초과되면 잠금을 해제하는 방법이 있습니다.

트랜잭션(Transaction)

트랜잭션은 동시성을 제어하는 주요 도구입니다. 트랜잭션은 원자성(Atomicity), 일관성(Consistency), 고립성(Isolation), 내구성(Durability)이라는 ACID 속성을 가지고 있습니다. 트랜잭션은 시스템의 자원을 보호하고, 데이터의 무결성을 유지하기 위해 사용됩니다. 하지만 긴 트랜잭션은 시스템의 성능을 저하시킬 수 있습니다.

비즈니스 트랜잭션과 시스템 트랜잭션

비즈니스 트랜잭션은 사용자 관점에서 여러 시스템 트랜잭션으로 구성될 수 있습니다. 예를 들어, 사용자가 웹 사이트에서 여러 페이지를 통해 작업을 수행하고, 마지막에 "저장" 버튼을 눌러 모든 변경 사항을 커밋하는 경우가 있습니다. 이 경우, 모든 시스템 트랜잭션이 단일 비즈니스 트랜잭션의 일부로 취급되어야 합니다. 그러나 비즈니스 트랜잭션이 긴 시스템 트랜잭션으로 구성되면 성능 문제가 발생할 수 있으므로, 이를 적절히 관리하는 것이 중요합니다.

오프라인 동시성 제어 패턴

비즈니스 트랜잭션이 여러 시스템 트랜잭션에 걸쳐 이루어질 때, **오프라인 동시성 제어(Offline Concurrency Control)**가 필요합니다. 여기에는 낙관적 오프라인 잠금(Optimistic Offline Lock), 비관적 오프라인 잠금(Pessimistic Offline Lock), 그리고 잠금을 관리하기 위한 Coarse-Grained Lock과 Implicit Lock 패턴이 포함됩니다.

애플리케이션 서버에서의 동시성

애플리케이션 서버에서의 동시성은 다중 스레드 처리와 관련이 있습니다. 프로세스 단위로 요청을 처리할 것인지, 스레드 단위로 처리할 것인지 결정해야 합니다. 프로세스 당 요청 방식은 안정적이지만 비효율적일 수 있으며, 스레드 당 요청 방식은 효율적이지만 동시성 문제가 발생할 수 있습니다. 개발자는 동시성 문제를 피하기 위해 가능한 한 스레드 간 메모리 공유를 줄이고, 각 요청에 대해 새로운 객체를 생성하는 것이 좋습니다.


동시성은 소프트웨어 개발에서 매우 중요한 주제이며, 이를 올바르게 처리하지 않으면 시스템의 안정성과 성능에 큰 영향을 미칠 수 있습니다. 이 장에서 다룬 개념들을 이해하고, 적절한 패턴을 적용하는 것이 중요합니다.

Last updated