콘텐츠로 이동하기
데이터베이스

PHP 애플리케이션을 Cloud Spanner로 마이그레이션 하기

2022년 2월 21일
Google Cloud Korea Team

PHP는 웹사이트의 최대 78%가 사용하고 있어 개발자에게 인기 있는 언어입니다. PHP를 사용하는 애플리케이션에서 Google Cloud Spanner의 안정성과 확장성을 활용하고 싶다면 이 게시물이 도움이 될 것입니다.

여기서는 기존 PHP 애플리케이션을 Spanner로 마이그레이션하는 일반적인 단계를 소개합니다. 예시를 통해 Spanner PHP 클라이언트 라이브러리를 사용하도록 전자상거래 플랫폼, Magento의 Catalog Module을 수정해 보겠습니다. Spanner PHP 라이브러리를 사용하는 모든 PHP 애플리케이션에서 이번 예시의 원칙을 적용할 수 있습니다.

학습 목표

  • PHP 클라이언트 라이브러리를 사용해 Spanner 데이터베이스에 대한 연결을 구성하고 세션 풀을 관리합니다.
  • 트랜잭션을 사용해 구성된 데이터베이스에서 안정적으로 CRUD 작업을 실행합니다.
  • Magento 애플리케이션 구현과 관련된 유용한 스니펫과 쿼리에 익숙해집니다.
  • 애플리케이션에서 일반적인 CRUD 작업과 스니펫을 사용하는 방법을 알아봅니다.

시작하기 전에

  • magento-spanner-port를 활용하는 Magento Codelab을 진행하세요. 해당 Codelab에서는 실제 통합을 시작하는 데 필요한 단계를 설명하는 반면 이 게시물은 내부에서 일어나는 상황을 자세히 설명합니다.
  • 이 문서는 Spanner로 Magento를 사용하는 방법의 여러 가지 예시를 보여줍니다. 예시는 magento-spanner-port의 SpannerAdapter AbstractDb 구현에서 가져왔으며 이 파일들은 Magento Spanner 어댑터 구현의 기본 파일입니다.

설정

인증

아래의 코드 스니펫을 실행하기 전에 먼저 인증 가이드를 따라 애플리케이션을 인증해야 합니다.

세션 풀 및 연결 만들기

인증 후 애플리케이션과 Spanner 간의 연결을 지원하는 세션 풀을 만듭니다. 세션을 만드는 작업은 비용이 많이 들기 때문에 재사용할 수 있는 영구적인 세션 풀을 만드는 것이 좋습니다.

Spanner 클라이언트 라이브러리는 캐시된 세션 풀을 사용하여 이러한 문제를 완화해 주는 편리한 방법을 제공합니다. 캐시된 세션 풀 사용에 대한 자세한 내용과 권장사항은 여기에서 확인할 수 있습니다.

SysVCacheItemPool을 사용하면 여러 프로세스에서 캐시된 세션을 공유할 수 있습니다. CreateSessionPool() 메서드는 세션 풀 객체를 만들고 반환합니다. 각 세션은 단일 데이터베이스와 연결되며 한 세션에서 한 번에 하나의 트랜잭션만 실행할 수 있습니다. minSessions 변수를 사용하면 예상되는 최소 동시 실행 세션 수를 설정할 수 있습니다. 풀을 만들면 minSessions로 풀이 초기화됩니다. minSessions 이상의 여러 개 세션이 필요한 경우 maxSessions에 도달할 때까지 새 세션이 만들어 집니다.

코드 블록:

로드 중...

ftok와 관련한 PHP의 프로젝트 식별자에 대한 자세한 내용은 PHP 문서의 해당 섹션(여기)을 참조하세요.

Spanner 데이터베이스에 연결

생성된 세션 풀을 사용해 아래와 같이 connect() 메서드로 Spanner에 연결할 수 있습니다. 연결 객체를 사용하면 Spanner에서 CRUD 작업을 수행할 수 있습니다.

코드 블록:

로드 중...

세션 풀 준비

코드 블록:

로드 중...

세션 연결 유지

코드 블록:

로드 중...

세션은 10분 내로 만료되기 때문에 maintain() 메서드는 세션들을 리프레쉬 합니다. 경우에 따라서는 리프레쉬 호출을 보다 균등하게 분산하기 위해 만료 기한이 10분 이상인 세션들도 리프레쉬할 수 있습니다. minSessions 만큼의 세션만 유지 되며 초과 세션은 그대로 만료됩니다.

참고: 최대 동시 실행 횟수가 늘어날 수 있도록 일부 추가 버퍼를 사용하여 최대 동시 실행을 처리할 만큼 maxSessions를 충분히 높은 값으로 설정해야 합니다. 프로세스가 종료되지 않도록 프로세스의 메모리 사용량에 유의하세요. 또한 요청을 처리할 때 새 세션을 만들어야 하는 경우 발생하는 지연 시간 비용을 방지하기 위해 minSessionsmaxSessions와 동일하게 설정하는 것을 권장합니다. 이를 통해 가장 높은 지연 시간이 최소화됩니다.

Spanner에서 트랜잭션 실행

연결을 설정하고 세션 풀을 만들었다면 Spanner 데이터베이스에서 Insert, Read, Update, Delete 작업을 시도할 준비가 된 것입니다.

아래의 스니펫은 트랜잭션을 사용해 CRUD 작업을 실행하는 것을 보여줍니다. 포함된 쿼리가 성공한 경우에만 데이터베이스에 커밋됩니다.

Insert

insert() 메서드에서 runTransaction을 사용해 실행해야 할 일련의 명령을 만듭니다.

로드 중...

insertBatch를 사용해 여러 행의 Insert를 시도합니다. 모든 Insert가 성공적이면 트랜잭션이 데이터베이스에 커밋됩니다.

업데이트

Insert와 마찬가지로 기존 데이터 배치의 업데이트 역시 update() 메서드에서 updateBatch를 사용하면 됩니다.

코드 블록:

로드 중...

업데이트가 실패하는 경우 트랜잭션을 롤백 할 수 있습니다

updateBatch에서는 Mutation API를 사용하지만 DML 문을 executeUpdate와 함께 사용해 업데이트를 수행할 수도 있습니다. 앞서 언급한 Insert 역시 마찬가지입니다. Mutation과 DML의 적절한 사용 시기에 대한 가이드라인은 DML과 Mutation 비교를 참조하세요. Spanner는 단일 트랜잭션에서 수행할 수 있는 Mutation 수를 제한한다는 사실에 유의하세요. Mutation 수 계산 방법에 관해 자세히 알아보려면 문서를 검토하세요. 트랜잭션의 Mutation 수와 트랜잭션이 한도에 도달하지 않도록 방지하는 방법을 알아보려면 커밋 통계 문서를 참조하세요.


삭제

delete() 메서드는 트랜잭션을 사용하는 행을 삭제하여 부분적으로 실패한 쿼리로부터 보호해 줍니다.

코드 블록:

로드 중...

위 코드 스니펫에서 $where에는 WHERE 절 조건이 포함되며 $table은 행을 삭제할 테이블의 이름입니다. Spanner에서는 전체 테이블이 우발적으로 삭제되지 않도록 WHERE 절을 사용해야 합니다.

데이터베이스 읽기

읽기 쿼리는 명시적인 롤백이나 커밋 호출이 필요 없기 때문에 데이터베이스 읽기는 앞서 언급한 트랜잭션 쿼리보다 간단합니다. 아래에 fetchAll() 메서드 예시가 나와 있습니다.

코드 블록:

로드 중...

여기서 $sql은 데이터베이스를 통해 결과를 쿼리하는 데 사용되는 SQL 문자열입니다.


Spanner 데이터베이스에서 SQL 쿼리 실행

쿼리는 클래스의 query() 메서드에서 Spanner 연결에 대한 execute() 함수를 사용해 수행됩니다.

코드 블록:

로드 중...

연결 종료

closeConnection() 메서드는 Spanner 연결의 리소스를 해제합니다. 애플리케이션이 모든 논리적 연산을 성공적으로 완료된 후에 연결을 종료해야 합니다. 그렇지 않으면 이전 호출자에 의해 세션이 영구적으로 잡혀 있게 되고, 결국에는 세션 풀이 소진되어 더 이상 요청이 실행되지 않습니다.

코드 블록:

로드 중...

유용한 쿼리

지금까지 기본적인 CRUD 작업의 예시를 설명했습니다. Spanner를 사용한 Magento 전자상거래 구현에서 아이디어를 얻은 흥미로운 작업을 몇 가지 더 소개합니다.

iterator를 배열로 변환

쿼리에서 결과의 iterator 객체를 애플리케이션에서 사용하는 배열로 복사합니다. 대규모 결과 집합은 상당히 많은 메모리 양을 사용하므로 Spanner 쿼리의 결과를 제한하는 것이 좋습니다.

코드 블록:

로드 중...

UUID 생성

데이터가 Spanner 서버에 분산되는 방식으로 인해 데이터에 액세스할 때 시퀀셜 기본 키의 경우 핫스팟을 생성할 수 있기 때문에 Spanner는 자동으로 증가되는 필드를 지원하지 않으며 UUID의 사용을 권장합니다. 자세한 내용은 기본 키 선택을 참조하세요. generateUUID() 메서드를 사용하면 임의의 UUID를 생성할 수 있습니다.

코드 블록:

로드 중...

SQL 쿼리 삭제

Spanner는 엄격한 유형의 쿼리 형식을 사용하기 때문에 다음 sanitizeSQL() 메서드를 사용하면 값이 Spanner 데이터 유형과 일치하도록 보장할 수 있습니다. 이 함수는 정수 데이터 유형의 정리를 처리하는 예시일 뿐입니다. 자체 애플리케이션에서 다른 정리를 처리하도록 확장할 수 있습니다.

코드 블록:

로드 중...

쿼리의 컬럼에 유형별 변환 추가

앞서 언급했듯이 Spanner는 읽고 쓰는 데이터의 타입에 엄격합니다. Spanner에서 암시적 변환을 수행하지만 기본적인 암시적 변환에 실패할 경우 쿼리가 실패하기도 합니다. 따라서 안전을 기하기 위해 아래와 같은 스니펫을 사용하여 컬럼에 적용할 변환을 명시적으로 지정할 수 있습니다. cast() 함수에 대한 자세한 내용은 여기에서 확인할 수 있습니다.

코드 블록:

로드 중...

적용하기

앞서 작업한 Magento Codelab에서 발췌한 몇 가지 설명을 토대로 학습 내용을 실제로 적용하는 방법에 대해 알아보겠습니다.


데이터베이스에서 항목 카탈로그 가져오기

다음과 같이 데이터베이스의 모든 항목을 간단하게 가져올 수 있습니다.

코드 블록:

로드 중...

앞서 '데이터베이스 읽기' 섹션에서 설명했듯이 $select는 데이터베이스에서 조회할 SQL 쿼리를 참조합니다.

Spanner가 엄격한 타입을 적용한다는 점을 염두에 두고 학습한 addCast() 함수를 사용하면 데이터베이스에 포함된 항목 카탈로그의 전체 읽기 작업을 다음과 같이 구조화 할 수 있습니다.

코드 블록:

로드 중...

위시리스트 또는 장바구니 항목 가져오기

위시리스트를 위해 카탈로그에서 필요한 항목을 가져올 때 항목에 대한 SQL 쿼리를 다시 실행해야 합니다. 이 작업은 다음과 같이 수행할 수 있습니다.

코드 블록: 

로드 중...

또한 Spanner의 엄격한 타입 요구사항에 따라 쿼리를 정리하는 방법도 배웠습니다. 이 작업을 수행하기 위해서는 단순한 getData() 메서드를 아래와 같이 수정해야 합니다.

코드 블록:

로드 중...

앞서 다룬 스니펫의 sanitizeSql() 함수는 쿼리에 요구되는 엄격한 타입의 형식 지정을 처리해 줍니다.


조건을 사용해 데이터 가져오기

쿼리는 조회할 필드의 유형을 알아야 합니다. load() 함수를 사용한 아래의 구현을 고려해 보세요. _getLoadSelect() 메서드는 필드 유형의 문자열과 숫자 값이 동일하게 문자열 등으로 처리되는 단순한 MySQL 쿼리를 반환합니다.

코드 블록:

로드 중...

Spanner의 유형 요구사항을 충족하기 위해 다음과 같이 _getLoadSelect() 함수를 getLoadSelectForSpanner()로 바꿉니다.

코드 블록:

로드 중...

getLoadSelectForSpanner()의 구현은 다음과 같습니다.

코드 블록:

로드 중...

항목의 객체 저장 및 업데이트 

Spanner가 기본 키의 증분 숫자 대신 랜덤 UUID를 사용하도록 권장한다는 사실을 고려하여 generateUuid()의 사용을 보여줍니다.

아래와 같은 Spanner 외 구현도 고려해 보세요.

코드 블록:

로드 중...

다음과 같이 대체할 수 있습니다.

코드 블록:

로드 중...

updateObjectInSpanner() saveNewObjectInSpanner()는 앞서 설명한 generateUuid() 스니펫을 사용합니다.


객체 저장:

코드 블록:

로드 중...

기존 객체 업데이트:

코드 블록:

로드 중...

참고로 위에 나온 updateObjectInSpanner()prepareDataForSpannerUpdate() 구현은 다음과 같이 표시됩니다.

코드 블록:

로드 중...

객체 삭제

CRUD의 D(삭제)를 완료하기 위해 Magento 맥락에서 Spanner의 객체를 안전하게 삭제하는 예시를 소개합니다.

코드 블록:

로드 중...

Magento 예시에서 id 열은 일반적으로 정수입니다. 다른 애플리케이션의 경우 예상되는 Spanner 데이터 유형과 값이 일치하는지 확인하는 것이 도움이 될 수 있습니다.

정리

데이터베이스와 상호작용할 때의 가장 기본적인 것은 CRUD 작업 입니다. 이번 블로그는 PHP와 Magento의 맥락에서 이러한 Spanner 작업과 주의해야 할 기타 세부사항에 대해 소개했습니다. 이제 공식 문서를 참조하여 Spanner 및 PHP에 대한 학습 여정을 이어 나가기 바랍니다

게시 위치