작성자: Tia, Techub News
블록체인은 분산 설계로 인해 효율성이 희생되므로 실행 속도를 높이는 것은 항상 시급히 해결해야 할 문제 중 하나였습니다. 블록체인의 "실행 계층"은 모든 거래를 처리하고 이를 체인에 추가하는 중요한 부분입니다. 처리 능력을 가속화하기 위해 실행 계층을 개선하는 것이 핵심 전략 중 하나가 되었으며, 병렬 실행은 이러한 측면에서 중요한 돌파구입니다.
기존 블록체인은 일반적으로 직렬 방식을 사용하여 트랜잭션을 하나씩 처리하는데, 이로 인해 트랜잭션 속도가 크게 제한되고 특히 트랜잭션 집약적인 네트워크에서 혼잡이 발생할 수 있습니다. 그러나 병렬 실행을 통해 여러 트랜잭션을 동시에 처리할 수 있으므로 실행 효율성이 크게 향상되고 체인상의 압력이 줄어듭니다.
병렬성이 무엇인지 더 잘 이해하기 위해 실행부터 시작하여 PBS 이후 병합 모드의 이더리움을 예로 들어 실행이 무엇인지 설명하고 전체 트랜잭션 수명 주기에서 실행 위치를 보여줍니다.
거래 실행의 특정 측면
트랜잭션은 메모리 풀에 들어가 필터링 및 정렬됩니다. 이는 트랜잭션 필터링 및 정렬을 완료하기 위한 Mempool, Searcher 및 Builder의 상호 작용을 포함하여 트랜잭션이 제출된 후의 전처리 단계입니다.
빌더는 블록을 구축합니다(실행하지는 않음): 빌더는 수익성 있는 거래를 블록으로 배열하여 거래의 패키징 및 순서 지정을 완료합니다.
제안자가 블록을 확인하고 제출합니다. 블록 구성이 완료된 후 빌더는 블록 제안서를 제안자에게 보냅니다. 제안자는 블록의 구조와 거래 내용을 확인한 후 공식적으로 블록을 네트워크에 제출하여 실행을 시작합니다.
트랜잭션 실행: 블록이 제출된 후 노드는 블록의 트랜잭션을 하나씩 실행합니다. 모든 거래가 스마트 계약 호출, 계정 잔액 변경 또는 상태 변경을 트리거하므로 이는 상태 업데이트의 중요한 단계입니다.
증인 증인(Witness Witness): 검증인은 블록의 실행 결과와 상태 루트를 목격하고 이를 최종 확인으로 사용합니다. 이는 실행 계층에서 블록의 신뢰성과 유효성을 보장하고 불일치를 방지합니다.
상태 동기화: 각 노드는 블록의 실행 결과(예: 계정 잔액, 계약 상태 업데이트 등)를 자체 로컬 상태로 동기화합니다. 각 트랜잭션을 실행한 후 노드는 사용할 새로운 상태 루트를 계산하고 저장합니다. 다음 블록을 초기 상태로 사용합니다.
물론 이는 블록 내 트랜잭션의 상태 동기화일 뿐입니다. 최신 온체인 상태를 유지하기 위해 노드는 일반적으로 블록별로 데이터를 동기화하고 지속적으로 블록과 상태를 확인합니다. 그러나 POS 메커니즘에서 최종성을 달성하려면 집계자는 각 슬롯의 증인 서명을 완전한 서명으로 집계하여 다음 슬롯의 제안자에게 전달해야 하며 검증자는 Epoch 이후에 다음 단계를 거쳐야 합니다. 에포크 내 모든 블록의 상태는 투표 수에 따라 확인되어 임시 합의 상태 체크포인트를 형성합니다. 블록과 거래는 두 번의 연속 Epoch가 대부분의 검증인으로부터 증인 지원을 받은 후에만 최종화됩니다.
트랜잭션의 전체 Life Cycle 관점에서 보면, 제안자가 빌더가 보낸 블록의 구조와 트랜잭션 내용을 확인한 후에 실행이 이루어집니다. 실제 실행 과정에서는 거래를 하나씩 처리하고 해당 계정이나 계약 상태를 업데이트해야 합니다. 모든 트랜잭션이 실행된 후 제안자는 현재 블록에 있는 모든 트랜잭션의 실행 결과와 최종 전역 상태를 요약한 새로운 상태 루트(머클 루트)를 계산합니다. 평신도의 관점에서 완전한 블록 실행 프로세스에는 이더리움을 이전 상태에서 다음 상태로 변경하는 과정, 각 트랜잭션의 실행부터 메르켈 루트 계산까지 완료해야 하는 일련의 계산이 포함됩니다.
순차적 실행
병렬 실행의 반대는 순차 실행으로, 이는 현재 블록체인의 보다 일반적인 실행 방법입니다. 일반적으로 트랜잭션은 순서대로 단계별로 실행됩니다. 거래가 실행되면 이더리움은 계정 상태 및 관련 정보(예: 잔액, 계약 저장 데이터)를 계정 상태 트리에 업데이트하고 새로운 계정 상태 해시가 생성됩니다. 모든 계정 상태 트리가 업데이트되면 상태 Merkle 루트라고 하는 상태 트리 루트 노드의 해시가 형성됩니다. 상태 Merkle 루트, 트랜잭션 Merkle 루트 및 영수증 Merkle 루트를 완료한 후 블록 헤더가 해시되어 블록의 블록 해시를 생성합니다.
그 중에서도 트랜잭션 실행 순서가 중요합니다. 머클 트리는 해시 값의 이진 트리이므로 서로 다른 순서로 형성된 머클 루트 값이 달라집니다.
병렬 실행
병렬 실행 환경에서 노드는 블록의 트랜잭션을 병렬로 처리하려고 시도합니다. 트랜잭션을 하나씩 순차적으로 실행하는 대신 트랜잭션이 동시에 실행될 수 있도록 서로 다른 "실행 경로"에 할당됩니다. 병렬 실행을 통해 시스템은 블록 단위의 트랜잭션을 보다 효율적으로 처리하고 처리량을 늘릴 수 있습니다.
모든 트랜잭션의 실행이 완료된 후 노드는 실행 결과(즉, 트랜잭션의 영향을 받는 상태 업데이트)를 요약하여 새로운 블록 상태를 구성합니다. 이 상태는 블록체인에 추가되며 체인의 최신 전역 상태를 나타냅니다.
상태 충돌
병렬성은 서로 다른 경로에서 트랜잭션을 동시에 처리하기 때문에 병렬성에서 가장 큰 어려움은 상태 충돌입니다. 즉, 여러 트랜잭션이 동일한 기간 내에 블록체인의 동일한 데이터(상태) 부분을 읽거나 쓰는 상황이 있을 수 있습니다. 이 상황을 제대로 처리하지 않으면 실행 결과가 불확실해집니다. 상태 업데이트 순서가 다르기 때문에 최종 계산 결과도 달라집니다. 예를 들어,
동일한 계정의 잔액을 업데이트하려고 하는 두 개의 트랜잭션, 트랜잭션 A와 트랜잭션 B가 있다고 가정합니다.
거래 A: 계정 잔액을 10 늘립니다.
거래 B: 계정 잔액을 20 늘립니다.
계정의 초기 잔액은 100입니다.
순차적으로 실행하면 실행 순서의 결과가 결정됩니다.
1. 먼저 트랜잭션 A를 실행한 다음 트랜잭션 B를 실행합니다.
먼저 계좌잔액이 10씩 증가하여 110이 됩니다.
20을 더 추가하면 130이 됩니다.
2. 먼저 트랜잭션 B를 실행한 다음 트랜잭션 A를 실행합니다.
먼저 계좌 잔액이 20씩 늘어나 120이 됩니다.
10을 더 추가하면 130이 됩니다.
두 시퀀스 모두에서 시스템이 트랜잭션 실행의 순차적 일관성을 보장하므로 최종 잔액은 130입니다.
그러나 병렬 실행 환경에서는 트랜잭션 A와 트랜잭션 B가 동시에 초기 잔액 100을 읽고 자체 작업을 수행할 수 있습니다.
1. 트랜잭션 A는 잔액이 100이라고 읽고, 계산 후 업데이트된 잔액은 110입니다.
2. 트랜잭션 B도 잔액이 100이라고 읽고 계산 후 잔액을 120으로 업데이트합니다.
이 경우 트랜잭션이 동시에 실행되었기 때문에 최종 잔액은 130이 아닌 120으로만 업데이트되었습니다. 트랜잭션 A와 트랜잭션 B의 작업이 상대방의 결과를 "덮어써서" 상태 충돌이 발생했기 때문입니다. .
이러한 유형의 상태 충돌 문제를 일반적으로 "데이터 덮어쓰기"라고 합니다. 즉, 트랜잭션이 동시에 동일한 데이터를 수정하려고 하면 서로의 계산 결과를 덮어쓰게 되어 잘못된 최종 상태가 발생할 수 있습니다. 상태 충돌로 인해 발생할 수 있는 또 다른 문제는 실행 순서를 보장할 수 없다는 점입니다. 여러 트랜잭션이 서로 다른 기간에 작업을 완료하므로 실행 순서도 달라집니다. 순서가 다르면 계산 결과가 달라져 결과가 불확실해질 수 있습니다.
이러한 불확실성을 피하기 위해 블록체인 병렬 실행 시스템은 일반적으로 일부 충돌 감지 및 롤백 메커니즘을 도입하거나 트랜잭션에 대한 종속성 분석을 미리 수행하여 최종 상태의 일관성에 영향을 주지 않고 병렬로 실행되도록 합니다.
낙관적 병렬성 대 결정적 병렬성
가능한 상태 갈등을 처리하는 방법에는 결정론적 병렬성과 낙관적 병렬성의 두 가지 방법이 있습니다. 두 모드 모두 효율성과 설계 복잡성 측면에서 절충점이 있습니다.
결정적 병렬 처리에서는 상태 액세스가 미리 선언되어야 하며, 유효성 검사기나 시퀀서는 트랜잭션이 주문될 때 선언된 상태 액세스를 확인합니다. 여러 트랜잭션이 동일한 상태에 쓰려고 시도하는 경우 이러한 트랜잭션은 동시 실행을 피하기 위해 충돌로 표시됩니다. 다양한 체인은 상태 액세스를 미리 선언하는 다양한 방법을 구현하지만 일반적으로 다음 방법을 포함합니다.
계약 사양에 따른 제약: 개발자는 스마트 계약에서 상태 액세스 범위를 직접 지정합니다. 예를 들어 ERC-20 토큰 전송을 위해서는 발신자와 수신자의 잔액 필드에 대한 액세스가 필요합니다.
트랜잭션 구조 데이터를 통한 선언: 트랜잭션에 특수 필드를 추가하여 상태 액세스를 표시합니다.
컴파일러 분석: 고급 언어 컴파일러는 계약 코드를 정적으로 분석하고 상태 액세스 세트를 자동으로 생성할 수 있습니다.
프레임워크를 통한 필수 선언: 일부 프레임워크에서는 개발자가 함수 호출 시 액세스해야 하는 상태를 명시적으로 지정해야 합니다.
낙관적 병렬 처리는 먼저 트랜잭션을 낙관적으로 처리하고 충돌이 발생할 때까지 기다린 다음 영향을 받는 트랜잭션을 순서대로 다시 실행합니다. 충돌을 최대한 피하기 위해 낙관적 병렬 설계의 핵심은 과거 데이터, 정적 분석 등을 통해 상태를 빠르게 예측하고 가정하는 것입니다. 즉, 검증이 불완전한 경우 시스템은 특정 작업이나 상태 업데이트가 유효한 것으로 가정하고 성능 및 처리량 향상을 위해 모든 검증 프로세스를 기다리지 않으려고 합니다.
낙관적 병렬성은 상태에 대한 빠른 예측과 가정을 통해 충돌을 최대한 피할 수 있지만, 특히 계약 실행이나 크로스체인 거래와 관련하여 피할 수 없는 문제가 여전히 있습니다. 충돌이 자주 발생하면 재실행이 발생할 수 있습니다. 시스템 성능이 저하되고 컴퓨팅 리소스 소비가 늘어납니다.
결정적 병렬성은 트랜잭션 전에 상태 종속성을 확인하여 낙관적 병렬성에서 발생할 수 있는 충돌을 방지합니다. 그러나 트랜잭션 제출 전에 상태 종속성을 정확하게 선언해야 하므로 개발자의 요구 사항이 높아져 구현이 복잡해집니다.
EVM 병렬 딜레마
상태 갈등을 처리하는 결정론적이고 낙관적인 방법이 있을 뿐만 아니라 병렬성을 달성하는 구체적인 프로세스에서 체인 데이터베이스 아키텍처의 관점에서 고려해야 합니다. 병렬 처리에서 상태 충돌 문제는 Merkle 트리 아키텍처의 EVM에서 특히 어렵습니다. Merkle 트리는 계층적 해시 구조입니다. 각 트랜잭션이 특정 상태 데이터를 수정한 후에는 Merkle 트리의 루트 해시 값도 업데이트되어야 합니다. 이 업데이트 프로세스는 리프 노드부터 루트 노드까지 반복적으로 계산되며 계층별로 계산됩니다. 해시는 되돌릴 수 없기 때문에, 즉 하위 레이어의 데이터 변경이 완료된 후에만 상위 레이어를 계산할 수 있습니다. 이 기능으로 인해 병렬 업데이트가 어렵습니다.
두 개의 트랜잭션이 병렬로 실행되고 동일한 상태(예: 계정 잔액)에 액세스하면 머클 트리 노드에서 충돌이 발생합니다. 이러한 충돌을 해결하려면 일반적으로 여러 분기에서 일관된 루트 해시 값을 보장하기 위한 추가 트랜잭션 관리 메커니즘이 필요합니다. 이는 병렬화와 상태 일관성 간의 균형이 필요하기 때문에 EVM에 구현하기가 쉽지 않습니다.
비 EVM 병렬 솔루션
솔라나
이더리움의 글로벌 상태 트리와 달리 솔라나는 계정 모델을 채택합니다. 각 계정은 독립적인 저장공간으로 원장에 저장되어 경로 충돌을 방지합니다.
Solana는 결정론적으로 병렬입니다. 솔라나에서 각 거래는 접근할 계정과 필요한 접근 권한(읽기 전용 또는 읽기-쓰기)에 대한 명확한 설명과 함께 제출되어야 합니다. 이 설계를 통해 블록체인 노드는 트랜잭션이 실행되기 전에 각 트랜잭션이 액세스해야 하는 리소스를 미리 분석할 수 있습니다. 트랜잭션이 실행되기 전에 모든 계정 종속성이 명확해지기 때문에 노드는 어떤 트랜잭션이 동일한 계정에 액세스할지, 어떤 트랜잭션이 안전하게 병렬로 실행될 수 있는지 결정할 수 있으므로 지능적인 스케줄링을 달성하고 충돌을 피하여 병렬 스케줄링의 기초를 달성할 수 있습니다. .
각 트랜잭션은 실행 전에 액세스해야 하는 계정과 권한을 선언하므로 솔라나는 트랜잭션 간에 계정 종속성이 있는지 확인할 수 있습니다(Sealevel 모델). 트랜잭션 간에 공유된 읽기 및 쓰기 계정이 없는 경우 시스템은 병렬 실행을 위해 이를 다른 프로세서에 할당할 수 있습니다.
앱토스
Aptos의 병렬 실행 설계는 Ethereum과 매우 다릅니다. 이는 주로 계정 모델과 상태 저장에 반영된 아키텍처와 메커니즘에서 몇 가지 주요 혁신을 이루었습니다.
Ethereum에서는 트랜잭션을 실행할 때 전역 상태 트리(MPT)를 자주 업데이트해야 합니다. 모든 계정과 계약의 상태는 공유 상태 트리에 저장되며 모든 트랜잭션은 이 상태 트리의 일부에 액세스하고 업데이트해야 합니다. Aptos는 계정을 독립적인 상태 단위로 나누며, 각 개체는 서로 영향을 주지 않고 독립적으로 존재할 수 있습니다. 객체 간에 공통 트리 경로가 없고 잠금 경쟁도 없으며 완전한 병렬 처리도 없습니다.
Aptos의 기본 데이터 구조는 Jellyfish Merkle Tree입니다. 각 객체의 상태는 궁극적으로 JMT에 독립적인 키-값 쌍으로 저장됩니다. 이더리움의 MPT와 달리 Jellyfish Merkle Tree는 완전한 바이너리 트리 구조 형태로 노드의 저장 경로와 쿼리 경로를 단순화하여 검증 시간을 대폭 단축합니다. 또한, 각 계정의 트리 내 위치는 고정되어 있으며, 트리 내 노드는 독립적으로 저장되어 있어 여러 계정에 대한 업데이트 및 검색이 동시에 수행될 수 있습니다.
Aptos는 모든 계정 종속성을 사전에 선언할 필요가 없다는 점에서 낙관적으로 유사합니다. 이를 위해 Aptos는 미리 설정된 트랜잭션 순서를 사용하여 종속성을 추정하고 이를 통해 중단 횟수를 줄이는 Block-STM을 사용합니다.
병렬 EVM
EVM이 아닌 병렬 처리와 비교하여 병렬 EVM은 상태 종속성, 충돌 감지, 가스 관리 및 롤백 메커니즘과 같은 문제를 처리할 때 더 큰 기술적 어려움에 직면합니다. 이를 더 잘 이해하기 위해 일부 병렬 EVM 프로젝트(예: Sui, Monad, Canto)가 이러한 문제를 어떻게 해결하는지 참조할 수 있습니다.
수이
Aptos와 마찬가지로 Sui도 개체 모델을 사용하여 상태를 처리하고 각 개체(예: 계정, 스마트 계약 상태)를 독립적인 리소스로 사용하며 이러한 개체는 고유한 개체 식별자로 구별됩니다. 트랜잭션에 서로 다른 개체가 포함된 경우 이러한 트랜잭션은 직접적인 충돌 없이 서로 다른 상태에서 작동하므로 병렬로 처리될 수 있습니다.
Sui는 객체 모델을 사용하여 상태를 관리하지만 EVM과 호환되기 위해 Sui의 아키텍처는 추가 적응 계층 또는 추상화 메커니즘을 통해 객체 모델과 EVM의 계정 모델을 연결합니다.
Sui에서는 트랜잭션 간에 충돌이 없다는 가정 하에 낙관적 병렬성 전략을 사용하여 트랜잭션을 예약합니다. 충돌이 발생하면 시스템은 롤백 메커니즘을 사용하여 상태를 복원합니다.
Sui는 상태 종속성 문제를 효과적으로 방지하기 위해 객체 모델과 상태 격리 기술을 사용합니다. 각 개체는 독립적인 리소스로 작동하며 다양한 트랜잭션을 병렬로 실행할 수 있으므로 처리량과 효율성이 향상됩니다. 그러나 이 접근 방식의 단점은 개체 모델의 복잡성과 롤백 메커니즘의 오버헤드입니다. 트랜잭션 간에 충돌이 발생하면 상태의 일부를 롤백해야 하므로 시스템에 부담이 증가하고 병렬 처리의 효율성에 영향을 미칠 수 있습니다. EVM이 아닌 병렬 시스템(예: Solana)과 비교할 때 Sui는 효율적인 병렬 처리를 유지하기 위해 더 많은 컴퓨팅 및 스토리지 리소스가 필요합니다.
모나드
Sui와 마찬가지로 Monad도 낙관적 병렬 처리를 사용합니다. 그러나 Monad의 낙관적 병렬 처리는 특정 트랜잭션이 실행되기 전에 종속성이 있는 일부 트랜잭션을 예측합니다. 예측은 주로 Monad의 정적 코드 분석기를 통해 완료됩니다. 예측을 위해서는 상태에 대한 접근이 필요하며, 상태가 이더리움 데이터베이스에 저장되는 방식으로 인해 상태에 대한 접근이 매우 어려워집니다. 병렬 상태 읽기 프로세스를 보다 효율적으로 만들기 위해 Monad도 데이터베이스를 재구성했습니다.
모나드 상태 트리는 파티션으로 나누어지며, 각 파티션은 자체 상태 하위 트리를 유지합니다. 업데이트할 때 전체 상태 트리를 다시 빌드하지 않고 관련 조각만 수정하면 됩니다. 상태 인덱스 테이블을 통해 파티션의 상태를 빠르게 찾고 파티션 간의 상호 작용을 줄입니다.
블록체인 | TPS | 노드 하드웨어 요구 사항 | 가상 머신 | 특징 |
솔라나 | 65,000+ | 12코어 CPU, 256GB RAM, 검증인 노드용 500GB SSD, 아카이브 노드용 1TB SSD, 운영 체제용으로 500GB를 예약하는 것이 좋습니다. | 솔라나VM | PoH(Proof of History)를 사용한 높은 동시성 지원, 매우 높은 처리량, 더 가벼운 합의 메커니즘 |
앱토스 | 160,000+ | 32코어 CPU, 64GB+ RAM, 3.0TB SSD 스토리지 | VM 이동 | Move 언어를 기반으로 병렬 실행을 지원하고 높은 처리량과 낮은 대기 시간을 최적화합니다. |
수이 | 120,000+ | 8코어 CPU, 128GB RAM, 4TB NVMe SSD 스토리지 | SuiVM | 병렬 실행, 리소스 기반 모델은 짧은 대기 시간과 높은 처리량을 지원합니다. |
모나드 | 10000+ | 16코어 CPU, 32GB RAM, 2TB NVMe SSD 스토리지 | EVM(호환성 레이어) | 높은 병렬 성능, 비동기 실행 및 더 가벼운 EVM 가상 머신 설계를 통한 최적화된 처리량 |
이더리움(병합 후) | 30-100+ | 4코어 CPU, 32GB RAM, 4TB SSD 스토리지 | EVM | 병합 후 지분 증명으로 변환된 기존 스마트 계약 플랫폼은 여전히 제한된 병렬성을 지원합니다. |
요약
병렬화의 핵심은 다중 경로 실행을 통해 실행 계층의 실행 효율성을 향상시키는 것입니다. 다중 경로 실행을 달성하려면 체인이 일련의 충돌 감지 및 롤백 메커니즘을 수행하여 충돌 없이 병렬화되도록 해야 합니다. 최종 상태의 일관성에 영향을 미칩니다. 데이터베이스를 어느 정도 실행하고 개선합니다.
물론 실행 계층 효율성의 향상은 병렬성에만 국한되지 않으며 데이터베이스의 트랜잭션에 필요한 읽기 및 쓰기 작업을 줄임으로써 실행 링크의 최적화도 완료할 수 있습니다. 전체 체인 속도의 향상에는 더 넓은 범위가 포함되며 합의 계층의 효율성 향상도 포함됩니다.
각 기술에는 고유한 특정 제약 조건이 있습니다. 병렬화는 효율성을 향상시키는 한 가지 방법일 뿐입니다. 이 기술을 사용할지 여부에 대한 최종 결정에서도 개발자 친화적인지, 분산화를 희생하지 않고 완료할 수 있는지 등을 고려해야 합니다. 기술 스택이 많을수록 좋습니다. 적어도 이더리움의 경우 병렬성은 그다지 매력적이지 않습니다. 효율성 향상이라는 관점에서 보면 병렬성을 추가하는 것은 이더리움의 최적의 솔루션이 아닙니다. 현재 Rollup 중심의 Ethereum 로드맵을 고려해보세요.