[CockroachDB] SQL Layer

SQL Layer

Cockroach DB 아키텍처의 SQL 계층은 개발자에게 SQL API를 노출하고 고수준의 SQL 문을 기본 KV Store에 대한 낮은 수준의 읽기 및 쓰기 요청으로 변환해 트랜잭션 계층으로 전달한다.

  • SQL API, 사용자 인터페이스를 형성
  • Parser, SQL 텍스트를 추상 구문 트리(AST)로 변환
  • Cost-based optimizer, AST를 최적화된 논리적 쿼리 계획으로 변환
  • Physical Planner, 논리적 쿼리 플랜을 클러스터의 하나 이상의 노드에서 실행할 수 있도록 지원
  • SQL execution engine, KV Store에 대한 읽기 및 쓰기 요청을 수행

Overview

Cockroach DB 클러스터의 각 노드는 대칭적으로 작동하기 때문에 개발자는 어느 노드에서나 요청을 보낼 수 있다. 요청을 수신하는 노드는 요청을 처리하고 클라이언트에 응답하는 게이트웨이 노드 역할을 한다.

클러스터에 대한 요청은 SQL 문으로 도착하지만, 데이터는 궁극적으로 스토리지 계층에서 KV 쌍으로 쓰여지고 읽혀진다. 이를 처리하기 위해 SQL 계층은 SQL문을 KV operation plan으로 변환한 뒤 트랜잭션 계층으로 전달한다.

Interactions with other layers

  • SQL API를 통해 외부로부터 요청 수신
  • SQL 문을 저레벨 KV operation으로 변환해 트랜잭션 계층에 요청으로 전달

Components

Relational structure

Cockroach DB는 저당된 데이터를 마치 행과 열로 구성된 관계형 구조처럼 데이터를 노출한다. 행과 열의 집합은 다시 테이블로 구성된다. 그 뒤 테이블 모음은 데이터베이스로 구성된다. Cockroach DB는 제약 조건과 같은 일반적 관계형 기능을 제공한다. 이런 기능 덕에 애플리케이션 개발자는 데이터베이스가 애플리케이션 데이터의 일관된 구조화를 보장한다는 것을 신뢰할 수 있으며 데이터 유효성 검사를 애플리케이션 로직에 별도로 구축할 필요가 없다.

SQL API

Cockroach DB는 관계형 구조를 나타내기 위해 대부분의 ANSI SQL 표준을 구현한다. 중요한 점은 개발자가 SQL API를 통해 다른 데이터베이스를 사용할 때와 같이 ACID 시맨틱 트랜잭션에 액세스할 수 있다는 점이다.

PostgreSQL wire protocol

SQL 쿼리는 PstgreSQL 와이어 프로토콜을 통해 클러스터에 도달한다. 이 프로토콜은 많은 PostgreSQL 호환 드라이버와 ORM을 지원해 애플리케이션을 클러스터에 간편하게 연결할 수 있다.

SQL parser, planner, executor

Cockroach DB 클러스터의 노드가 클라이언트로부터 SQL 요청을 받으면, 해당 문을 파싱하고 최적화된 논리적 쿼리 계획을 생성한 뒤 물리적 쿼리 계획으로 변환한다. 마지막으로 이 계획을 실행한다.

Parsing

SQL 쿼리는 지원되는 구문을 설명하는 yacc 파일에 대해 구문 분석되며 각 쿼리의 SQL 버전은 AST로 변환된다.

Logical planning

  1. AST는 고수준의 논리적 쿼리 계획으로 변환된다. 이 변환 과적에서 Cockroach DB는 다음과 같은 작업을 포함하는 의미론적 분석도 수행한다.
    • 쿼리가 SQL 언어에서 유효한 문인지 확인한다.
    • 테이블이나 변수의 이름과 같은 명칭을 해당 값으로 해석한다.
    • Constant folding, 0.6 + 0.41.0으로 대체하는 등 불필요한 중간 계산을 제거한다.
    • 쿼리에 하나 이상의 하위 쿼리가 포함된 경우와 같이 중간 결과에 사용할 데이터 유형을 확정한다.
  2. 논리적 계획은 항상 유효한 일련의 변환을 사용해 단순화한다. 예를 들어 a BETWEEN b AND ca >= b AND a <= c로 변환된다.
  3. 논리적 계획은 쿼리를 실행하는 여러 가지 가능한 방법을 평가하고 비용이 가장 적게 드는 실행 계획을 선택하는 검색 알고리즘을 사용해 최적화된다.

Physical planning

물리적 계획 단계에서는 range 정보를 기반으로 쿼리 실행에 참여할 노드를 결정한다. 이 단계에서는 데이터가 저장된 위치와 가까운 곳에서 일부 연산을 수행하기 위해 쿼리를 분산할지 여부를 결정한다. 더 자세히 말하면, 물리적 계획 단계에서는 논리 계획에서 생성된 최적화된 논리 계획을 물리적 SQL 연산자의 DAG로 변환시킨다.

분산 계층은 단일 키 공간의 추상화를 제공하기 때문에 SQL 계층은 모든 노드에서 모든 범위에 대해 읽기 및 쓰기 작업을 수행할 수 있다. 따라서 SQL 연산자는 게이트웨이 모드에서 계획하든 분산 모드에서 계획하든 동일하게 작동할 수 있다.

쿼리를 여러 노드에 분산할지 여부는 네트워크를 통해 전송해야 하는 데이터의 양을 추정하는 휴리스틱에 의해 결정된다. 적은 수의 행만 필요한 쿼리는 게이트웨이 노드에서 실행된다. 다른 쿼리는 여러 노드에 분산된다.

예를 들어, 쿼리가 분산되면 물리 계획 단계에서는 논리 계획의 스캔 연산을 스캔으로 읽은 범위를 포함하는 각 노드에 대해 하나씩 여러개의 물리적 TableReader 연산자로 분할한다. 그 뒤 나머지 논리 연산은 tableReader와 동일한 노드에서 스케쥴링 된다. 그 결과 계산이 가능한 한 실제 데이터에 가깝게 수행된다.

Query execution

물리 계획의 컴포넌트는 실행을 위해 하나 이상의 노드로 전송된다. 각 노드에서는 쿼리의 일부를 계산하기 위해 논리 프로세서를 생성한다. 노드 내부 또는 노드 간 논리 프로세서는 데이터의 논리적 흐름을 통해 서로 통신한다. 쿼리의 결합된 결과는 쿼리가 수신된 첫 번째 노드로 다시 전송되어 SQL 클라이언트로 전송된다.

각 프로세서는 쿼리에 의해 조작된 스칼라 값에 인코딩된 형식을 사용한다. 이것은 SQL에서 사용되는 것과는 다른 바이너리 형식이다. 따라서 SQL 쿼리에 나열된 값은 인코딩되어야 하며, 논리 프로세서 간에 통신되고 디스크에서 읽혀진 데이터는 SQL 클라이언트로 다시 전송되기 전에 디코딩 되어야 한다.

Vectorized query execution

이 기능이 활성화된 경우 밀리 계획이 노드로 전송되어 벡터화된 실행 엔진에서 처리된다. 물리 계획을 수신하면 벡터화된 엔진이 디스크에서 테이블 데이터 배치를 읽고 데이터를 행 형식에서 열 형식으로 변환한다. 이런 열 데이터 배치는 메모리에 저장되어 엔진이 실행 중에 빠르게 액세스할 수 있다.

백터화된 엔진은 열 데이터의 유형별 배열을 빠르게 반속하는 특수하게 사전 컴파일된 함수를 사용한다. 함수의 열 출력은 엔진이 각 데이터 열을 처리할 때 메모리에 저장된다. 입력 버퍼의 모든 데이터 열을 처리한 후 엔진은 열 형식의 출력을 다시 행 형식으로 변환한 다음 처리된 행을 SQL 인터페이스로 반환한다. 테이블 데이터 배치가 완전히 처리된 후 엔진은 쿼리가 실행될 때까지 처리를 위해 다음 테이블 데이터배치를 읽는다.

Encoding

SQL 쿼리는 파싱 가능한 문자열로 작성되지만 DB의 하위 계층은 주로 바이트 단위로 처리한다. 즉, SQL 계층에서 쿼리를 실행할 때 문자열로 표현된 행 데이터를 바이트로 변환하고 하위 계층에서 반환된 바이트를 클라이언트에 다시 전달할 수 있는 SQL 데이터로 변환해야 한다.

인덱싱된 열의 경우, 이 바이트 인코딩이 나타내는 데이터 유형과 동일한 정렬 순서를 유지하는 것도 중요하다. 이는 Cockroach DB가 궁극적으로 데이터를 정렬된 KV map에 저장하는 방식 때문인데, 바이트가 나타내는 데이터와 동일한 순서로 저장하면 KV 데이터를 효율적으로 스캔할 수 있다.

그러나 인덱싱되지 않은 열의 경우 공간을 덜 차지하지만 순서를 유지하지 않는 인코딩(value encoding)을 사용한다.

DistSQL

Cockroach DB는 분산 데이터베이스이므로 일부 쿼리를 위한 분산 SQL 최적화 도구를 개발해 많은 range를 포함하는 쿼리의 속도를 획기적으로 높일 수 있다. 비분산 쿼리에서는 코디네이터 노드가 쿼리와 일치하는 모든 행을 수신한 다음 전체 데이터 세트에 대해 모든 계산을 수행한다.

그러나 DistSQL 호환 쿼리의 경우, 각 노드는 포함된 행에 대해 계산을 수행한 다음 전체 행이 아닌 결과를 집계 노드로 보낸다. 집계 노드는 각 노드의 결과를 집계한 뒤 최종적으로 클라이언트에 단일 응답을 내보낸다.

이렇게 하면 집계 노드로 전송되는 데이터의 양이 크게 줄고, 이미 입증된 병렬 컴퓨팅 개념을 활용해 궁극적으로 복잡한 쿼리를 완료하는 데 걸리는 시간이 단축된다. 또한 이미 데이터를 저장하고 있는 노드에서 데이터를 처리하므로 개별 노드의 저장 공간보다 더 큰 행 집합을 처리할 수 있다.

  • 논리적 계획 : AST / planNode Tree와 유사하게 계산 단계를 통한 추상적(비분산적) 데이터 흐름을 나타냄
  • 물리적 계획 : 개념적으로 논리적 계획 노드를 cockroach DB를 실행시키는 물리 머신에 매핑하는 것이다. 논리적 계획 노드는 클러스터 토폴로지에 따라 복제되고 특화된다. PlanNodes와 마찬가지로 물리 계획의 이런 컴포넌트는 클러스터에서 스케쥴링되고 실행된다.

Schema changes

스키마 변경 중에 테이블이 온라인 상태를 유지(읽기 및 쓰기 서비스 제공)할 수 있는 프로토콜을 사용해 열 또는 보조 인덱스 추가와 같은 스키마 변경을 수행한다. 이 프로토콜을 사용하면 클러스터의 여러 노드가 서로 다른 시간에 새로운 테이블 스키마로 비동기적으로 전환할 수 있다.

스키마 변경 프로토콜은 각 스키마 변경을 원하는 효과를 얻을 수 있는 일련의 점진적 변경으로 분해한다.

예를 들어, 보조 인덱스를 추가하려면 시작 버전과 종료 버전 사이에 두 개의 중간 스키마 버전이 필요하므로 전체 클러스터에서 쓰기 시 인덱스가 업데이트된 후 읽기에 사용할 수 있게 된다. 스키마가 변경되는 동안 데이터베이스가 일관된 상태를 유지하도록 하기 위해 클러스터에서 항상 이 스키마의 연속적 버전이 최대 두 개 까지만 사용된다는 불변성을 적용한다. 이 접근 방식은 해당 논문을 기반으로 한다.

Technical interactions with other layers

SQL and transaction layer

실행된 planNode의 KV operation은 트랜잭션 레이어로 전송된다.

Author: Song Hayoung
Link: https://songhayoung.github.io/2024/01/14/Cockroach/sql-layer/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.