1. 개요
Spanner는 관계형 및 비관계형 운영 워크로드에 모두 적합한 수평 확장이 가능하고 전 세계적으로 분산된 완전 관리형 데이터베이스 서비스입니다. Spanner는 핵심 기능 외에도 지능형 데이터 기반 애플리케이션을 빌드할 수 있는 강력한 고급 기능을 제공합니다.
이 Codelab에서는 Spanner에 대한 기본적인 이해를 바탕으로 온라인 뱅킹 애플리케이션을 기반으로 고급 통합을 활용하여 데이터 처리 및 분석 기능을 향상하는 방법을 자세히 알아봅니다.
다음 세 가지 주요 고급 기능에 중점을 둘 것입니다.
- Vertex AI 통합: Spanner를 Google Cloud의 AI 플랫폼인 Vertex AI와 원활하게 통합하는 방법을 알아봅니다. Spanner SQL 쿼리 내에서 직접 Vertex AI 모델을 호출하여 강력한 데이터베이스 내 변환 및 예측을 사용 설정하고 은행 애플리케이션이 예산 추적 및 이상 감지와 같은 사용 사례에 맞게 거래를 자동으로 분류하는 방법을 알아봅니다.
- 전체 텍스트 검색: Spanner 내에서 전체 텍스트 검색 기능을 구현하는 방법을 알아봅니다. 텍스트 데이터 색인을 생성하고 운영 데이터에서 키워드 기반 검색을 실행하는 효율적인 쿼리를 작성하여 은행 시스템 내에서 이메일 주소로 고객을 효율적으로 찾는 등 강력한 데이터 탐색을 사용 설정하는 방법을 살펴봅니다.
- BigQuery 통합 쿼리: Spanner의 통합 쿼리 기능을 활용하여 BigQuery에 있는 데이터를 직접 쿼리하는 방법을 살펴봅니다. 이를 통해 Spanner의 실시간 운영 데이터를 BigQuery의 분석 데이터 세트와 결합하여 데이터 중복이나 복잡한 ETL 프로세스 없이 포괄적인 통계와 보고서를 얻을 수 있습니다. 또한 실시간 고객 데이터를 BigQuery의 광범위한 과거 동향과 결합하여 타겟팅 마케팅 캠페인과 같은 은행 애플리케이션의 다양한 사용 사례를 지원할 수 있습니다.
학습할 내용
- Spanner 인스턴스 설정 방법
- 데이터베이스 및 테이블을 만드는 방법
- Spanner 데이터베이스 테이블에 데이터를 로드하는 방법
- Spanner에서 Vertex AI 모델을 호출하는 방법
- 유사 검색 및 전체 텍스트 검색을 사용하여 Spanner 데이터베이스를 쿼리하는 방법
- BigQuery에서 Spanner에 대해 통합 쿼리를 실행하는 방법
- Spanner 인스턴스를 삭제하는 방법
필요한 항목
2. 설정 및 요건
프로젝트 만들기
결제가 사용 설정된 Google Cloud 프로젝트가 이미 있는 경우 Console 왼쪽 상단에서 프로젝트 선택 풀다운 메뉴를 클릭합니다.
프로젝트를 선택한 상태에서 필수 API 사용 설정으로 건너뜁니다.
아직 Google 계정 (Gmail 또는 Google Apps)이 없으면 계정을 만들어야 합니다. Google Cloud Platform Console (console.cloud.google.com)에 로그인하고 새 프로젝트를 만듭니다.
표시된 대화상자에서 '새 프로젝트' 버튼을 클릭하여 새 프로젝트를 만듭니다.
아직 프로젝트가 없으면 첫 번째 프로젝트를 만들기 위해 다음과 비슷한 대화상자가 표시됩니다.
이후의 프로젝트 만들기 대화상자에서 새 프로젝트의 세부정보를 입력할 수 있습니다.
모든 Google Cloud 프로젝트에서 고유한 이름인 프로젝트 ID를 기억해 두세요. 이 ID는 나중에 이 Codelab에서 PROJECT_ID
라고 부릅니다.
그런 다음 Google Cloud 리소스를 사용하고 Spanner API, Vertex AI API, BigQuery API, BigQuery Connection API를 사용 설정하려면 아직 완료하지 않은 경우 Developers Console에서 결제를 사용 설정해야 합니다.
Spanner 가격 책정은 여기를 참고하세요. 다른 리소스와 관련된 기타 비용은 해당 가격 책정 페이지에 설명되어 있습니다.
Google Cloud Platform 신규 사용자는 $300 상당의 무료 체험판을 사용할 수 있습니다.
Google Cloud Shell 설정
이 Codelab에서는 Cloud에서 실행되는 명령줄 환경인 Google Cloud Shell을 사용합니다.
이 Debian 기반 가상 머신에는 필요한 모든 개발 도구가 로드되어 있습니다. 영구적인 5GB 홈 디렉터리를 제공하고 Google Cloud에서 실행되므로 네트워크 성능과 인증이 크게 개선됩니다. 즉, 이 Codelab에 필요한 것은 브라우저뿐입니다.
Cloud Console에서 Cloud Shell을 활성화하려면 Cloud Shell 활성화 를 클릭합니다. 환경을 프로비저닝하고 연결하는 데 몇 분 정도 소요됩니다.
Cloud Shell에 연결되면 사용자 인증이 이미 완료되었고 프로젝트가 내 PROJECT_ID
에 설정되어 있음을 확인할 수 있습니다.
gcloud auth list
예상 출력:
Credentialed Accounts ACTIVE: * ACCOUNT: <myaccount>@<mydomain>.com
gcloud config list project
예상 출력:
[core] project = <PROJECT_ID>
어떤 이유로든 프로젝트가 설정되지 않은 경우 다음 명령어를 실행합니다.
gcloud config set project <PROJECT_ID>
PROJECT_ID
를 찾고 계신가요? 설정 단계에서 사용한 ID를 확인하거나 Cloud Console 대시보드에서 확인하세요.
또한 Cloud Shell은 기본적으로 이후 명령어를 실행할 때 유용할 수 있는 몇 가지 환경 변수를 설정합니다.
echo $GOOGLE_CLOUD_PROJECT
예상 출력:
<PROJECT_ID>
필요한 API 사용 설정
프로젝트에 Spanner, Vertex AI, BigQuery API를 사용 설정합니다.
gcloud services enable spanner.googleapis.com
gcloud services enable aiplatform.googleapis.com
gcloud services enable bigquery.googleapis.com
gcloud services enable bigqueryconnection.googleapis.com
요약
이 단계에서는 프로젝트가 없는 경우 프로젝트를 설정하고, Cloud Shell을 활성화하고, 필요한 API를 사용 설정했습니다.
다음 단계
다음으로 Spanner 인스턴스를 설정합니다.
3. Spanner 인스턴스 설정
Spanner 인스턴스 만들기
이 단계에서는 Codelab을 위한 Spanner 인스턴스를 설정합니다. 이렇게 하려면 Cloud Shell을 열고 다음 명령어를 실행합니다.
export SPANNER_INSTANCE=cloudspanner-onlinebanking
gcloud spanner instances create $SPANNER_INSTANCE \
--config=regional-us-central1 \
--description="Spanner Online Banking" \
--nodes=1 \
--edition=ENTERPRISE \
--default-backup-schedule-type=NONE
예상 출력:
Creating instance...done.
요약
이 단계에서는 Spanner 인스턴스를 만들었습니다.
다음 단계
다음으로 초기 애플리케이션을 준비하고 데이터베이스와 스키마를 만듭니다.
4. 데이터베이스 및 스키마 만들기
초기 애플리케이션 준비
이 단계에서는 코드를 통해 데이터베이스와 스키마를 만듭니다.
먼저 Maven을 사용하여 onlinebanking
라는 Java 애플리케이션을 만듭니다.
mvn -B archetype:generate \
-DarchetypeGroupId=org.apache.maven.archetypes \
-DgroupId=com.google.codelabs \
-DartifactId=onlinebanking \
-DjavaCompilerVersion=1.8 \
-DjunitVersion=4.13.2 \
-DarchetypeVersion=1.5
데이터베이스에 추가할 데이터 파일을 체크아웃하고 복사합니다 (코드 저장소는 여기 참고).
git clone https://github.com/GoogleCloudPlatform/cloud-spanner-samples.git
cp -r ./cloud-spanner-samples/banking/data ./onlinebanking
애플리케이션 폴더로 이동합니다.
cd onlinebanking
Maven pom.xml
파일을 엽니다. Maven BOM을 사용하여 Google Cloud 라이브러리의 버전을 관리하도록 종속 항목 관리 섹션을 추가합니다.
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.google.cloud</groupId>
<artifactId>libraries-bom</artifactId>
<version>26.56.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
편집기와 파일은 다음과 같습니다.
dependencies
섹션에 애플리케이션에서 사용할 라이브러리가 포함되어 있는지 확인합니다.
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-nop</artifactId>
<version>2.0.9</version>
</dependency>
<dependency>
<groupId>com.opencsv</groupId>
<artifactId>opencsv</artifactId>
<version>5.10</version>
</dependency>
<dependency>
<groupId>com.google.cloud</groupId>
<artifactId>google-cloud-spanner</artifactId>
</dependency>
<dependency>
<groupId>com.google.cloud</groupId>
<artifactId>google-cloud-bigquery</artifactId>
</dependency>
<dependency>
<groupId>com.google.cloud</groupId>
<artifactId>google-cloud-bigqueryconnection</artifactId>
</dependency>
</dependencies>
마지막으로 애플리케이션이 실행 가능한 JAR로 패키징되도록 빌드 플러그인을 교체합니다.
<build>
<plugins>
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<version>3.3.1</version>
<executions>
<execution>
<id>copy-resources</id>
<phase>process-resources</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<outputDirectory>${project.build.directory}/${project.artifactId}-resources</outputDirectory>
<resources>
<resource>
<directory>resources</directory>
<filtering>true</filtering>
</resource>
</resources>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>3.8.1</version>
<executions>
<execution>
<id>copy-dependencies</id>
<phase>prepare-package</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<outputDirectory>${project.build.directory}/${project.artifactId}-resources/lib</outputDirectory>
<overWriteReleases>false</overWriteReleases>
<overWriteSnapshots>false</overWriteSnapshots>
<overWriteIfNewer>true</overWriteIfNewer>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.4.2</version>
<configuration>
<finalName>${project.artifactId}</finalName>
<outputDirectory>${project.build.directory}</outputDirectory>
<archive>
<index>false</index>
<manifest>
<mainClass>com.google.codelabs.App</mainClass>
<addClasspath>true</addClasspath>
<classpathPrefix>${project.artifactId}-resources/lib/</classpathPrefix>
</manifest>
</archive>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-failsafe-plugin</artifactId>
<version>3.2.5</version>
<executions>
<execution>
<goals>
<goal>integration-test</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.2.5</version>
<configuration>
<useSystemClassLoader>false</useSystemClassLoader>
</configuration>
</plugin>
</plugins>
</build>
Cloud Shell 편집기의 '파일' 메뉴에서 '저장'을 선택하거나 Ctrl+S
키를 눌러 변경사항을 pom.xml
파일에 저장합니다.
이제 종속 항목이 준비되었으므로 앱에 코드를 추가하여 스키마, 일부 색인 (검색 포함), 원격 엔드포인트에 연결된 AI 모델을 만듭니다. 이 Codelab에서는 이러한 아티팩트를 기반으로 하여 이 클래스에 더 많은 메서드를 추가합니다.
onlinebanking/src/main/java/com/google/codelabs
에서 App.java
를 열고 콘텐츠를 다음 코드로 바꿉니다.
package com.google.codelabs;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ExecutionException;
import com.google.api.gax.longrunning.OperationFuture;
import com.google.cloud.bigquery.BigQuery;
import com.google.cloud.bigquery.BigQueryOptions;
import com.google.cloud.spanner.Database;
import com.google.cloud.spanner.DatabaseAdminClient;
import com.google.cloud.spanner.DatabaseClient;
import com.google.cloud.spanner.DatabaseId;
import com.google.cloud.spanner.Spanner;
import com.google.cloud.spanner.SpannerException;
import com.google.cloud.spanner.SpannerExceptionFactory;
import com.google.cloud.spanner.SpannerOptions;
import com.google.spanner.admin.database.v1.CreateDatabaseMetadata;
public class App {
// Create the Spanner database and schema
public static void create(DatabaseAdminClient dbAdminClient, DatabaseId db,
String location, String model) {
System.out.println("Creating Spanner database...");
List<String> statements = Arrays.asList(
"CREATE TABLE Customers (\n"
+ " CustomerId INT64 NOT NULL,\n"
+ " FirstName STRING(256) NOT NULL,\n"
+ " LastName STRING(256) NOT NULL,\n"
+ " FullName STRING(512) AS (FirstName || ' ' || LastName) STORED,\n"
+ " Email STRING(512) NOT NULL,\n"
+ " EmailTokens TOKENLIST AS\n"
+ " (TOKENIZE_SUBSTRING(Email, ngram_size_min=>2, ngram_size_max=>3,\n"
+ " relative_search_types=>[\"all\"])) HIDDEN,\n"
+ " Address STRING(MAX)\n"
+ ") PRIMARY KEY (CustomerId)",
"CREATE INDEX CustomersByEmail\n"
+ "ON Customers(Email)",
"CREATE SEARCH INDEX CustomersFuzzyEmail\n"
+ "ON Customers(EmailTokens)",
"CREATE TABLE Accounts (\n"
+ " AccountId INT64 NOT NULL,\n"
+ " CustomerId INT64 NOT NULL,\n"
+ " AccountType STRING(256) NOT NULL,\n"
+ " Balance NUMERIC NOT NULL,\n"
+ " OpenDate TIMESTAMP NOT NULL\n"
+ ") PRIMARY KEY (AccountId)",
"CREATE INDEX AccountsByCustomer\n"
+ "ON Accounts (CustomerId)",
"CREATE TABLE TransactionLedger (\n"
+ " TransactionId INT64 NOT NULL,\n"
+ " AccountId INT64 NOT NULL,\n"
+ " TransactionType STRING(256) NOT NULL,\n"
+ " Amount NUMERIC NOT NULL,\n"
+ " Timestamp TIMESTAMP NOT NULL"
+ " OPTIONS(allow_commit_timestamp=true),\n"
+ " Category STRING(256),\n"
+ " Description STRING(MAX),\n"
+ " CategoryTokens TOKENLIST AS (TOKENIZE_FULLTEXT(Category)) HIDDEN,\n"
+ " DescriptionTokens TOKENLIST AS (TOKENIZE_FULLTEXT(Description)) HIDDEN\n"
+ ") PRIMARY KEY (AccountId, TransactionId),\n"
+ "INTERLEAVE IN PARENT Accounts ON DELETE CASCADE",
"CREATE INDEX TransactionLedgerByAccountType\n"
+ "ON TransactionLedger(AccountId, TransactionType)",
"CREATE INDEX TransactionLedgerByCategory\n"
+ "ON TransactionLedger(AccountId, Category)",
"CREATE SEARCH INDEX TransactionLedgerTextSearch\n"
+ "ON TransactionLedger(CategoryTokens, DescriptionTokens)",
"CREATE MODEL TransactionCategoryModel\n"
+ "INPUT (prompt STRING(MAX))\n"
+ "OUTPUT (content STRING(MAX))\n"
+ "REMOTE OPTIONS (\n"
+ " endpoint = '//aiplatform.googleapis.com/projects/" + db.getInstanceId().getProject()
+ "/locations/" + location + "/publishers/google/models/" + model + "',\n"
+ " default_batch_size = 1\n"
+ ")");
OperationFuture<Database, CreateDatabaseMetadata> op = dbAdminClient.createDatabase(
db.getInstanceId().getInstance(),
db.getDatabase(),
statements);
try {
Database dbOperation = op.get();
System.out.println("Created Spanner database [" + dbOperation.getId() + "]");
} catch (ExecutionException e) {
throw (SpannerException) e.getCause();
} catch (InterruptedException e) {
throw SpannerExceptionFactory.propagateInterrupt(e);
}
}
static void printUsageAndExit() {
System.out.println("Online Online Banking Application 1.0.0");
System.out.println("Usage:");
System.out.println(" java -jar target/onlinebanking.jar <command> [command_option(s)]");
System.out.println("");
System.out.println("Examples:");
System.out.println(" java -jar target/onlinebanking.jar create");
System.out.println(" - Create a sample Spanner database and schema in your "
+ "project.\n");
System.exit(1);
}
public static void main(String[] args) {
if (args.length < 1) {
printUsageAndExit();
}
String instanceId = System.getProperty("SPANNER_INSTANCE", System.getenv("SPANNER_INSTANCE"));
String databaseId = System.getProperty("SPANNER_DATABASE", System.getenv("SPANNER_DATABASE"));
String location = System.getenv().getOrDefault("SPANNER_LOCATION", "us-central1");
String model = System.getenv().getOrDefault("SPANNER_MODEL", "gemini-2.0-flash-lite");
if (instanceId == null || databaseId == null) {
System.err.println("Missing one or more required environment variables: SPANNER_INSTANCE or "
+ "SPANNER_DATABASE");
System.exit(1);
}
BigQueryOptions bigqueryOptions = BigQueryOptions.newBuilder().build();
BigQuery bigquery = bigqueryOptions.getService();
SpannerOptions spannerOptions = SpannerOptions.newBuilder().build();
try (Spanner spanner = spannerOptions.getService()) {
String command = args[0];
DatabaseId db = DatabaseId.of(spannerOptions.getProjectId(), instanceId, databaseId);
DatabaseClient dbClient = spanner.getDatabaseClient(db);
DatabaseAdminClient dbAdminClient = spanner.getDatabaseAdminClient();
switch (command) {
case "create":
create(dbAdminClient, db, location, model);
break;
default:
printUsageAndExit();
}
}
}
}
App.java
에 변경사항을 저장합니다.
코드에서 생성하는 다양한 항목을 살펴보고 애플리케이션 JAR을 빌드합니다.
mvn package
예상 출력:
[INFO] Building jar: /home/your_user/onlinebanking/target/onlinebanking.jar [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS
애플리케이션을 실행하여 사용 정보를 확인합니다.
java -jar target/onlinebanking.jar
예상 출력:
Online Banking Application 1.0.0 Usage: java -jar target/onlinebanking.jar <command> [command_option(s)] Examples: java -jar target/onlinebanking.jar create - Create a sample Spanner database and schema in your project.
데이터베이스 및 스키마 만들기
필요한 애플리케이션 환경 변수를 설정합니다.
export SPANNER_INSTANCE=cloudspanner-onlinebanking
export SPANNER_DATABASE=onlinebanking
create
명령어를 실행하여 데이터베이스와 스키마를 만듭니다.
java -jar target/onlinebanking.jar create
예상 출력:
Creating Spanner database... Created Spanner database [<DATABASE_RESOURCE_NAME>]
Spanner에서 스키마 확인
Spanner 콘솔에서 방금 만든 인스턴스와 데이터베이스로 이동합니다.
Accounts
, Customers
, TransactionLedger
등 세 개의 테이블이 모두 표시됩니다.
이 작업은 최적화된 데이터 검색을 위한 보조 색인 및 Vertex AI 모델 참조와 함께 Accounts
, Customers
, TransactionLedger
테이블을 포함한 데이터베이스 스키마를 만듭니다.
TransactionLedger
테이블은 계정 내에서 인터리빙되어 데이터 지역성을 개선하여 계정별 트랜잭션의 쿼리 성능을 향상시킵니다.
이 Codelab에서 사용되는 일반적인 데이터 액세스 패턴(예: 정확한 이메일 및 대략적인 이메일로 고객 조회, 고객별 계정 검색, 거래 데이터를 효율적으로 쿼리 및 검색)을 최적화하기 위해 보조 색인(CustomersByEmail
, CustomersFuzzyEmail
, AccountsByCustomer
, TransactionLedgerByAccountType
, TransactionLedgerByCategory
, TransactionLedgerTextSearch
)이 구현되었습니다.
TransactionCategoryModel
는 Vertex AI를 활용하여 LLM에 대한 직접 SQL 호출을 사용 설정합니다. 이 Codelab에서는 동적 거래 분류에 사용됩니다.
요약
이 단계에서는 Spanner 데이터베이스와 스키마를 만들었습니다.
다음 단계
다음으로 샘플 애플리케이션 데이터를 로드합니다.
5. 데이터 로드
이제 CSV 파일의 샘플 데이터를 데이터베이스에 로드하는 기능을 추가합니다.
App.java
를 열고 가져오기를 대체합니다.
package com.google.codelabs;
import java.io.FileReader;
import java.math.BigDecimal;
import java.time.Duration;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.ExecutionException;
import com.google.api.gax.longrunning.OperationFuture;
import com.google.cloud.Timestamp;
import com.google.cloud.bigquery.BigQuery;
import com.google.cloud.bigquery.BigQueryOptions;
import com.google.cloud.spanner.Database;
import com.google.cloud.spanner.DatabaseAdminClient;
import com.google.cloud.spanner.DatabaseClient;
import com.google.cloud.spanner.DatabaseId;
import com.google.cloud.spanner.Key;
import com.google.cloud.spanner.Spanner;
import com.google.cloud.spanner.SpannerException;
import com.google.cloud.spanner.SpannerExceptionFactory;
import com.google.cloud.spanner.SpannerOptions;
import com.google.cloud.spanner.Statement;
import com.google.cloud.spanner.Struct;
import com.google.spanner.admin.database.v1.CreateDatabaseMetadata;
import com.opencsv.CSVReader;
그런 다음 App
클래스에 삽입 메서드를 추가합니다.
// Insert customers from CSV
public static void insertCustomers(DatabaseClient dbClient) {
System.out.println("Inserting customers...");
dbClient
.readWriteTransaction()
.run(transaction -> {
int count = 0;
List<Statement> statements = new ArrayList<>();
try (CSVReader reader = new CSVReader(new FileReader("data/customers.csv"))) {
reader.skip(1);
String[] line;
while ((line = reader.readNext()) != null) {
Statement statement = Statement.newBuilder(
"INSERT INTO Customers (CustomerId, FirstName, LastName, Email, Address) "
+ "VALUES (@customerId, @firstName, @lastName, @email, @address)")
.bind("customerId").to(Long.parseLong(line[0]))
.bind("firstName").to(line[1])
.bind("lastName").to(line[2])
.bind("email").to(line[3])
.bind("address").to(line[4])
.build();
statements.add(statement);
count++;
}
transaction.batchUpdate(statements);
System.out.println("Inserted " + count + " customers");
return null;
}
});
}
// Insert accounts from CSV
public static void insertAccounts(DatabaseClient dbClient) {
System.out.println("Inserting accounts...");
dbClient
.readWriteTransaction()
.run(transaction -> {
int count = 0;
List<Statement> statements = new ArrayList<>();
try (CSVReader reader = new CSVReader(new FileReader("data/accounts.csv"))) {
reader.skip(1);
String[] line;
while ((line = reader.readNext()) != null) {
Statement statement = Statement.newBuilder(
"INSERT INTO Accounts (AccountId, CustomerId, AccountType, Balance, OpenDate) "
+ "VALUES (@accountId, @customerId, @accountType, @balance, @openDate)")
.bind("accountId").to(Long.parseLong(line[0]))
.bind("customerId").to(Long.parseLong(line[1]))
.bind("accountType").to(line[2])
.bind("balance").to(new BigDecimal(line[3]))
.bind("openDate").to(line[4])
.build();
statements.add(statement);
count++;
}
transaction.batchUpdate(statements);
System.out.println("Inserted " + count + " accounts");
return null;
}
});
}
// Insert transactions from CSV
public static void insertTransactions(DatabaseClient dbClient) {
System.out.println("Inserting transactions...");
dbClient
.readWriteTransaction()
.run(transaction -> {
int count = 0;
List<Statement> statements = new ArrayList<>();
try (CSVReader reader = new CSVReader(new FileReader("data/transactions.csv"))) {
reader.skip(1);
String[] line;
// Specify timestamps that are within last 30 days
Random random = new Random();
Instant startTime = Instant.now().minus(15, ChronoUnit.DAYS);
Instant currentTimestamp = startTime;
Map<Long, BigDecimal> balanceChanges = new HashMap<>();
while ((line = reader.readNext()) != null) {
long accountId = Long.parseLong(line[1]);
String transactionType = line[2];
BigDecimal amount = new BigDecimal(line[3]);
int randomMinutes = random.nextInt(60) + 1;
currentTimestamp = currentTimestamp.plus(Duration.ofMinutes(randomMinutes));
Timestamp timestamp = Timestamp.ofTimeSecondsAndNanos(
currentTimestamp.getEpochSecond(), currentTimestamp.getNano());
Statement statement = Statement.newBuilder(
"INSERT INTO TransactionLedger (TransactionId, AccountId, TransactionType, Amount,"
+ "Timestamp, Category, Description) "
+ "VALUES (@transactionId, @accountId, @transactionType, @amount, @timestamp,"
+ "@category, @description)")
.bind("transactionId").to(Long.parseLong(line[0]))
.bind("accountId").to(accountId)
.bind("transactionType").to(transactionType)
.bind("amount").to(amount)
.bind("timestamp").to(timestamp)
.bind("category").to(line[5])
.bind("description").to(line[6])
.build();
statements.add(statement);
// Track balance changes per account
BigDecimal balanceChange = balanceChanges.getOrDefault(accountId,
BigDecimal.ZERO);
if ("Credit".equalsIgnoreCase(transactionType)) {
balanceChanges.put(accountId, balanceChange.add(amount));
} else if ("Debit".equalsIgnoreCase(transactionType)) {
balanceChanges.put(accountId, balanceChange.subtract(amount));
} else {
System.err.println("Unsupported transaction type: " + transactionType);
continue;
}
count++;
}
// Apply final balance updates
for (Map.Entry<Long, BigDecimal> entry : balanceChanges.entrySet()) {
long accountId = entry.getKey();
BigDecimal balanceChange = entry.getValue();
Struct row = transaction.readRow(
"Accounts",
Key.of(accountId),
List.of("Balance"));
if (row != null) {
BigDecimal currentBalance = row.getBigDecimal("Balance");
BigDecimal updatedBalance = currentBalance.add(balanceChange);
Statement statement = Statement.newBuilder(
"UPDATE Accounts SET Balance = @balance WHERE AccountId = @accountId")
.bind("accountId").to(accountId)
.bind("balance").to(updatedBalance)
.build();
statements.add(statement);
}
}
transaction.batchUpdate(statements);
System.out.println("Inserted " + count + " transactions");
}
return null;
});
}
switch (command)
내 삽입을 위한 main
메서드에 다른 케이스 문을 추가합니다.
case "insert":
String insertType = (args.length >= 2) ? args[1] : "";
if (insertType.equals("customers")) {
insertCustomers(dbClient);
} else if (insertType.equals("accounts")) {
insertAccounts(dbClient);
} else if (insertType.equals("transactions")) {
insertTransactions(dbClient);
} else {
insertCustomers(dbClient);
insertAccounts(dbClient);
insertTransactions(dbClient);
}
break;
마지막으로 printUsageAndExit
메서드에 insert 사용 방법을 추가합니다.
System.out.println(" java -jar target/onlinebanking.jar insert");
System.out.println(" - Insert sample Customers, Accounts, and Transactions into the "
+ "database.\n");
App.java
에 변경사항을 저장합니다.
애플리케이션을 다시 빌드합니다.
mvn package
insert
명령어를 실행하여 샘플 데이터를 삽입합니다.
java -jar target/onlinebanking.jar insert
예상 출력:
Inserting customers... Inserted 100 customers Inserting accounts... Inserted 125 accounts Inserting transactions... Inserted 200 transactions
Spanner 콘솔에서 인스턴스 및 데이터베이스의 Spanner 스튜디오로 다시 이동합니다. 그런 다음 TransactionLedger
테이블을 선택하고 사이드바에서 '데이터'를 클릭하여 데이터가 로드되었는지 확인합니다. 테이블에는 200개의 행이 있어야 합니다.
요약
이 단계에서는 데이터베이스에 샘플 데이터를 삽입했습니다.
다음 단계
다음으로 Vertex AI 통합을 활용하여 Spanner SQL 내에서 직접 은행 거래를 자동으로 분류합니다.
6. Vertex AI로 데이터 분류
이 단계에서는 Vertex AI의 기능을 활용하여 Spanner SQL 내에서 직접 금융 거래를 자동으로 분류합니다. Vertex AI를 사용하면 기존의 사전 학습된 모델을 선택하거나 자체 모델을 학습하고 배포할 수 있습니다. Vertex AI Model Garden에서 사용 가능한 모델을 확인하세요.
이 Codelab에서는 Gemini 모델 중 하나인 Gemini Flash Lite
를 사용합니다. 이 버전의 Gemini는 비용 효율적이지만 대부분의 일상적인 워크로드를 처리할 수 있습니다.
현재 설명에 따라 분류하려는 금융 거래가 여러 건 있습니다 (groceries
, transportation
등). Spanner에 모델을 등록한 다음 ML.PREDICT
를 사용하여 AI 모델을 호출하면 됩니다.
은행 애플리케이션에서는 서비스를 맞춤설정하거나, 이상치를 더 효과적으로 감지하거나, 고객에게 월별 예산을 추적하는 기능을 제공할 수 있도록 거래를 분류하여 고객 행동에 대한 심층적인 통계를 얻을 수 있습니다.
데이터베이스와 스키마를 만들 때 이미 첫 번째 단계가 완료되어 다음과 같은 모델이 생성되었습니다.
다음으로 ML.PREDICT
를 호출하는 메서드를 애플리케이션에 추가합니다.
App.java
를 열고 categorize
메서드를 추가합니다.
// Use Vertex AI to set the category of transactions
public static void categorize(DatabaseClient dbClient) {
System.out.println("Categorizing transactions...");
try {
// Create a prompt to instruct the LLM how to categorize the transactions
String categories = String.join(", ", Arrays.asList("Entertainment", "Gifts", "Groceries",
"Investment", "Medical", "Movies", "Online Shopping", "Other", "Purchases", "Refund",
"Restaurants", "Salary", "Transfer", "Transportation", "Utilities"));
String prompt = "Categorize the following financial activity into one of these "
+ "categories: " + categories + ". Return Other if the description cannot be mapped to "
+ "one of these categories. Only return the exact category string, no other text or "
+ "punctuation or reasoning. Description: ";
String sql = "UPDATE TransactionLedger SET Category = (\n"
+ " SELECT content FROM ML.PREDICT(MODEL `TransactionCategoryModel`, (\n"
+ " SELECT CONCAT('" + prompt + "', CASE WHEN TRIM(Description) = ''\n"
+ " THEN 'Other' ELSE Description END) AS prompt\n"
+ " ))\n"
+ ") WHERE TRUE";
// Use partitioned update to batch update a large number of rows
dbClient.executePartitionedUpdate(Statement.of(sql));
System.out.println("Completed categorizing transactions");
} catch (SpannerException e) {
throw e;
}
}
분류를 위한 main
메서드에 다른 case 문을 추가합니다.
case "categorize":
categorize(dbClient);
break;
마지막으로 printUsageAndExit
메서드에 categorize 사용 방법을 추가합니다.
System.out.println(" java -jar target/onlinebanking.jar categorize");
System.out.println(" - Use AI to categorize transactions in the database.\n");
App.java
에 변경사항을 저장합니다.
애플리케이션을 다시 빌드합니다.
mvn package
categorize
명령어를 실행하여 데이터베이스의 거래를 분류합니다.
java -jar target/onlinebanking.jar categorize
예상 출력:
Categorizing transactions... Completed categorizing transactions
Spanner 스튜디오에서 TransactionLedger
테이블에 대해 데이터 미리보기 문을 실행합니다. 이제 모든 행의 Category
열이 채워집니다.
이제 거래를 분류했으므로 이 정보를 내부 또는 고객 대상 쿼리에 사용할 수 있습니다. 다음 단계에서는 특정 고객이 한 달 동안 특정 카테고리에서 지출한 금액을 찾는 방법을 살펴봅니다.
요약
이 단계에서는 사전 학습된 모델을 사용하여 AI 기반 데이터 분류를 실행했습니다.
다음 단계
다음으로 토큰화를 사용하여 유사 검색 및 전체 텍스트 검색을 실행합니다.
7. 전체 텍스트 검색을 사용한 쿼리
쿼리 코드 추가
Spanner는 다양한 전체 텍스트 검색 쿼리를 제공합니다. 이 단계에서는 일치검색을 실행한 다음 퍼지 검색과 전체 텍스트 검색을 실행합니다.
App.java
를 열고 가져오기를 대체합니다.
package com.google.codelabs;
import java.io.FileReader;
import java.math.BigDecimal;
import java.time.Duration;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import com.google.api.gax.longrunning.OperationFuture;
import com.google.cloud.Timestamp;
import com.google.cloud.bigquery.BigQuery;
import com.google.cloud.bigquery.BigQueryOptions;
import com.google.cloud.spanner.Database;
import com.google.cloud.spanner.DatabaseAdminClient;
import com.google.cloud.spanner.DatabaseClient;
import com.google.cloud.spanner.DatabaseId;
import com.google.cloud.spanner.Key;
import com.google.cloud.spanner.ReadOnlyTransaction;
import com.google.cloud.spanner.ResultSet;
import com.google.cloud.spanner.Spanner;
import com.google.cloud.spanner.SpannerException;
import com.google.cloud.spanner.SpannerExceptionFactory;
import com.google.cloud.spanner.SpannerOptions;
import com.google.cloud.spanner.Statement;
import com.google.cloud.spanner.Struct;
import com.google.cloud.spanner.TimestampBound;
import com.google.spanner.admin.database.v1.CreateDatabaseMetadata;
import com.opencsv.CSVReader;
그런 다음 쿼리 메서드를 추가합니다.
// Get current account balance(s) by customer
public static void getBalance(DatabaseClient dbClient, long customerId) {
String query = "SELECT AccountId, Balance\n"
+ "FROM Accounts\n"
+ "WHERE CustomerId = @customerId";
Statement statement = Statement.newBuilder(query)
.bind("customerId").to(customerId)
.build();
// Ignore ongoing transactions, use stale reads as seconds-old data is sufficient
TimestampBound stalenessBound = TimestampBound.ofMaxStaleness(5, TimeUnit.SECONDS);
try (ReadOnlyTransaction transaction = dbClient.singleUseReadOnlyTransaction(stalenessBound);
ResultSet resultSet = transaction.executeQuery(statement);) {
System.out.println("Account balances for customer " + customerId + ":");
while (resultSet.next()) {
System.out.println(" Account " + resultSet.getLong("AccountId") + ": "
+ resultSet.getBigDecimal("Balance"));
}
}
}
// Find customers by email
public static void findCustomers(DatabaseClient dbClient, String email) {
// Query using fuzzy search (ngrams) to allow for spelling mistakes
String query = "SELECT CustomerId, Email\n"
+ "FROM Customers\n"
+ "WHERE SEARCH_NGRAMS(EmailTokens, @email)\n"
+ "ORDER BY SCORE_NGRAMS(EmailTokens, @email) DESC\n"
+ "LIMIT 10";
Statement statement = Statement.newBuilder(query)
.bind("email").to(email)
.build();
try (ReadOnlyTransaction transaction = dbClient.singleUseReadOnlyTransaction();
ResultSet resultSet = transaction.executeQuery(statement)) {
System.out.println("Customer emails matching " + email + " (top 10 matches):");
while (resultSet.next()) {
System.out.println(" Customer " + resultSet.getLong("CustomerId") + ": "
+ resultSet.getString("Email"));
}
}
}
// Get total monthly spending for a customer by category
public static void getSpending(DatabaseClient dbClient, long customerId, String category) {
// Query category using full-text search
String query = "SELECT SUM(Amount) as TotalSpending\n"
+ "FROM TransactionLedger t\n"
+ "JOIN Accounts a\n"
+ " ON t.AccountId = a.AccountId\n"
+ "WHERE t.TransactionType = 'Debit'\n"
+ " AND a.CustomerId = @customerId\n"
+ " AND t.Timestamp >= TIMESTAMP_ADD(CURRENT_TIMESTAMP(), INTERVAL -30 DAY)\n"
+ " AND (SEARCH(t.CategoryTokens, @category) OR SEARCH(t.DescriptionTokens, @category))";
Statement statement = Statement.newBuilder(query)
.bind("customerId").to(customerId)
.bind("category").to(category)
.build();
try (ReadOnlyTransaction transaction = dbClient.singleUseReadOnlyTransaction();
ResultSet resultSet = transaction.executeQuery(statement);) {
System.out.println("Total spending for customer " + customerId + " under category "
+ category + ":");
while (resultSet.next()) {
BigDecimal totalSpending = BigDecimal.ZERO;
if (!resultSet.isNull("TotalSpending")) {
totalSpending = resultSet.getBigDecimal("TotalSpending");
}
System.out.println(" " + totalSpending);
}
}
}
쿼리의 main
메서드에 case
문을 하나 더 추가합니다.
case "query":
String queryType = (args.length >= 2) ? args[1] : "";
if (queryType.equals("balance")) {
long customerId = (args.length >= 3) ? Long.parseLong(args[2]) : 1L;
getBalance(dbClient, customerId);
} else if (queryType.equals("email")) {
String email = (args.length >= 3) ? args[2] : "";
findCustomers(dbClient, email);
} else if (queryType.equals("spending")) {
long customerId = (args.length >= 3) ? Long.parseLong(args[2]) : 1L;
String category = (args.length >= 4) ? args[3] : "";
getSpending(dbClient, customerId, category);
} else {
printUsageAndExit();
}
break;
마지막으로 쿼리 명령어를 사용하는 방법을 printUsageAndExit
메서드에 추가합니다.
System.out.println(" java -jar target/onlinebanking.jar query balance 1");
System.out.println(" - Query customer account balance(s) by customer id.\n");
System.out.println(" java -jar target/onlinebanking.jar query email madi");
System.out.println(" - Find customers by email using fuzzy search.\n");
System.out.println(" java -jar target/onlinebanking.jar query spending 1 groceries");
System.out.println(" - Query customer spending by customer id and category using "
+ "full-text search.\n");
App.java
에 변경사항을 저장합니다.
애플리케이션을 다시 빌드합니다.
mvn package
고객 계정 잔액에 대한 정확한 일치 검색 실행
일치검색 쿼리는 검색어와 정확하게 일치하는 일치하는 행을 찾습니다.
성능을 개선하기 위해 데이터베이스와 스키마를 만들 때 이미 색인이 추가되었습니다.
"CREATE INDEX AccountsByCustomer\n" + "ON Accounts (CustomerId)",
getBalance
메서드는 이 색인을 암시적으로 사용하여 제공된 customerId와 일치하는 고객을 찾고 해당 고객의 계정에 조인합니다.
Spanner 스튜디오에서 직접 실행할 때 쿼리는 다음과 같이 표시됩니다.
다음 명령어를 실행하여 고객 1
의 계정 잔액을 나열합니다.
java -jar target/onlinebanking.jar query balance 1
예상 출력:
Account balances for customer 1: Account 1: 9875.25 Account 7: 9900 Account 110: 38200
고객이 100명 있으므로 다른 고객 ID를 지정하여 다른 고객 계정 잔액을 쿼리할 수도 있습니다.
java -jar target/onlinebanking.jar query balance 5
java -jar target/onlinebanking.jar query balance 10
java -jar target/onlinebanking.jar query balance 99
고객 이메일에 대해 유사 검색 실행
퍼지 검색을 사용하면 맞춤법 변형 및 오타를 포함하여 검색어와 근사치 일치를 찾을 수 있습니다.
데이터베이스와 스키마를 만들 때 이미 n-gram 색인이 추가되었습니다.
CREATE TABLE Customers ( ... EmailTokens TOKENLIST AS (TOKENIZE_SUBSTRING(Email, ngram_size_min=>2, ngram_size_max=>3, relative_search_types=>["all"])) HIDDEN, ) PRIMARY KEY(CustomerId); CREATE SEARCH INDEX CustomersFuzzyEmail ON Customers(EmailTokens);
findCustomers
메서드는 SEARCH_NGRAMS
및 SCORE_NGRAMS
를 사용하여 이 색인에 쿼리하여 이메일로 고객을 찾습니다. 이메일 열이 n-gram 토큰화되었으므로 이 쿼리에는 맞춤법 오류가 포함되어 있어도 올바른 답변을 반환할 수 있습니다. 결과는 가장 일치하는 항목을 기준으로 정렬됩니다.
다음 명령어를 실행하여 madi
가 포함된 일치하는 고객 이메일 주소를 찾습니다.
java -jar target/onlinebanking.jar query email madi
예상 출력:
Customer emails matching madi (top 10 matches): Customer 39: madison.perez@example.com Customer 64: mason.gray@example.com Customer 91: mabel.alexander@example.com
이 응답에는 madi
또는 유사한 문자열이 포함된 가장 근접한 검색결과가 순위별로 표시됩니다.
Spanner 스튜디오에서 직접 실행하면 쿼리는 다음과 같이 표시됩니다.
퍼지 검색은 emily
의 맞춤법 오류와 같은 맞춤법 오류에도 도움이 됩니다.
java -jar target/onlinebanking.jar query email emily
java -jar target/onlinebanking.jar query email emliy
java -jar target/onlinebanking.jar query email emilee
예상 출력:
Customer emails matching emliy (top 10 matches): Customer 31: emily.lopez@example.com
각 경우 예상 고객 이메일이 상위 검색 결과로 반환됩니다.
전체 텍스트 검색으로 거래 검색
Spanner의 전체 텍스트 검색 기능은 키워드나 문구를 기반으로 레코드를 검색하는 데 사용됩니다. 맞춤법 오류를 수정하거나 동의어를 검색할 수 있습니다.
데이터베이스와 스키마를 만들 때 이미 전체 텍스트 검색 색인이 추가되었습니다.
CREATE TABLE TransactionLedger ( ... CategoryTokens TOKENLIST AS (TOKENIZE_FULLTEXT(Category)) HIDDEN, DescriptionTokens TOKENLIST AS (TOKENIZE_FULLTEXT(Description)) HIDDEN, ) PRIMARY KEY(AccountId, TransactionId), INTERLEAVE IN PARENT Accounts ON DELETE CASCADE; CREATE SEARCH INDEX TransactionLedgerTextSearch ON TransactionLedger(CategoryTokens, DescriptionTokens);
getSpending
메서드는 SEARCH
전체 텍스트 검색 함수를 사용하여 해당 색인과 일치시킵니다. 지정된 고객 ID의 지난 30일 동안의 모든 지출 (차변)을 찾습니다.
다음 명령어를 실행하여 groceries
카테고리의 고객 1
의 지난달 총 지출을 가져옵니다.
java -jar target/onlinebanking.jar query spending 1 groceries
예상 출력:
Total spending for customer 1 under category groceries: 50
이전 단계에서 분류한 다른 카테고리의 지출을 확인하거나 다른 고객 ID를 사용할 수도 있습니다.
java -jar target/onlinebanking.jar query spending 1 transportation
java -jar target/onlinebanking.jar query spending 1 restaurants
java -jar target/onlinebanking.jar query spending 12 entertainment
요약
이 단계에서는 일치검색 쿼리와 퍼지 및 전체 텍스트 검색을 실행했습니다.
다음 단계
다음으로 Spanner를 Google BigQuery와 통합하여 제휴 쿼리를 실행하여 실시간 Spanner 데이터를 BigQuery 데이터와 결합합니다.
8. BigQuery로 통합 쿼리 실행
BigQuery 데이터 세트 만들기
이 단계에서는 통합 쿼리를 사용하여 BigQuery와 Spanner 데이터를 결합합니다.
이렇게 하려면 먼저 Cloud Shell 명령줄에서 MarketingCampaigns
데이터 세트를 만듭니다.
bq mk --location=us-central1 MarketingCampaigns
예상 출력:
Dataset '<PROJECT_ID>:MarketingCampaigns' successfully created.
데이터 세트의 CustomerSegments
테이블은 다음과 같습니다.
bq mk --table MarketingCampaigns.CustomerSegments CampaignId:STRING,CampaignName:STRING,CustomerId:INT64
예상 출력:
Table '<PROJECT_ID>:MarketingCampaigns.CustomerSegments' successfully created.
다음으로 BigQuery에서 Spanner로 연결합니다.
bq mk --connection \
--connection_type=CLOUD_SPANNER \
--properties="{\"database\": \"projects/$GOOGLE_CLOUD_PROJECT/instances/cloudspanner-onlinebanking/databases/onlinebanking\", \"useParallelism\": true, \"useDataBoost\": true}" \
--location=us-central1 \
spanner-connection
예상 출력:
Connection <PROJECT_NUMBER>.us-central1.spanner-connection successfully created
마지막으로 Spanner 데이터와 조인할 수 있는 일부 고객을 BigQuery 테이블에 추가합니다.
bq query --use_legacy_sql=false '
INSERT INTO MarketingCampaigns.CustomerSegments (CampaignId, CampaignName, CustomerId)
VALUES
("campaign1", "Spring Promotion", 1),
("campaign1", "Spring Promotion", 3),
("campaign1", "Spring Promotion", 5),
("campaign1", "Spring Promotion", 7),
("campaign1", "Spring Promotion", 9),
("campaign1", "Spring Promotion", 11)'
예상 출력:
Waiting on bqjob_r76a7ce76c5ec948f_0000019644bda052_1 ... (0s) Current status: DONE Number of affected rows: 6
BigQuery를 쿼리하여 데이터를 사용할 수 있는지 확인할 수 있습니다.
bq query --use_legacy_sql=false "SELECT * FROM MarketingCampaigns.CustomerSegments"
예상 출력:
+------------+------------------+------------+ | CampaignId | CampaignName | CustomerId | +------------+------------------+------------+ | campaign1 | Spring Promotion | 1 | | campaign1 | Spring Promotion | 5 | | campaign1 | Spring Promotion | 7 | | campaign1 | Spring Promotion | 9 | | campaign1 | Spring Promotion | 11 | | campaign1 | Spring Promotion | 3 | +------------+------------------+------------+
BigQuery의 이 데이터는 다양한 은행 워크플로를 통해 추가된 데이터를 나타냅니다. 예를 들어 최근에 계정을 만들었거나 마케팅 프로모션에 가입한 고객의 목록일 수 있습니다. 마케팅 캠페인에서 타겟팅할 고객 목록을 확인하려면 BigQuery의 이 데이터와 Spanner의 실시간 데이터를 모두 쿼리해야 합니다. 통합 쿼리를 사용하면 단일 쿼리로 이 작업을 할 수 있습니다.
BigQuery로 통합 쿼리 실행
다음으로 애플리케이션에 EXTERNAL_QUERY
를 호출하여 제휴 쿼리를 실행하는 메서드를 추가합니다. 이를 통해 BigQuery와 Spanner에서 고객 데이터를 결합하고 분석할 수 있습니다. 예를 들어 최근 지출을 기준으로 마케팅 캠페인 기준을 충족하는 고객을 식별할 수 있습니다.
App.java
를 열고 가져오기를 대체합니다.
package com.google.codelabs;
import java.io.FileReader;
import java.math.BigDecimal;
import java.time.Duration;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import com.google.api.gax.longrunning.OperationFuture;
import com.google.cloud.Timestamp;
import com.google.cloud.bigquery.BigQuery;
import com.google.cloud.bigquery.BigQueryException;
import com.google.cloud.bigquery.BigQueryOptions;
import com.google.cloud.bigquery.connection.v1.ConnectionName;
import com.google.cloud.bigquery.JobException;
import com.google.cloud.bigquery.QueryJobConfiguration;
import com.google.cloud.bigquery.TableResult;
import com.google.cloud.spanner.Database;
import com.google.cloud.spanner.DatabaseAdminClient;
import com.google.cloud.spanner.DatabaseClient;
import com.google.cloud.spanner.DatabaseId;
import com.google.cloud.spanner.Key;
import com.google.cloud.spanner.ReadOnlyTransaction;
import com.google.cloud.spanner.ResultSet;
import com.google.cloud.spanner.Spanner;
import com.google.cloud.spanner.SpannerException;
import com.google.cloud.spanner.SpannerExceptionFactory;
import com.google.cloud.spanner.SpannerOptions;
import com.google.cloud.spanner.Statement;
import com.google.cloud.spanner.Struct;
import com.google.cloud.spanner.TimestampBound;
import com.google.spanner.admin.database.v1.CreateDatabaseMetadata;
import com.opencsv.CSVReader;
그런 다음 campaign
메서드를 추가합니다.
// Get customers for quarterly marketing campaign in BigQuery using Spanner data
public static void campaign(BigQuery bq, DatabaseId db, String location, String campaignId,
int threshold) {
// The BigQuery dataset, table, and Spanner connection must already exist for this to succeed
ConnectionName connection = ConnectionName.of(db.getInstanceId().getProject(), location,
"spanner-connection");
// Use a federated query to bring Spanner data into BigQuery
String bqQuery = "SELECT cs.CampaignName, c.CustomerId, c.FullName, t.TotalSpending\n"
+ "FROM MarketingCampaigns.CustomerSegments cs\n"
+ "JOIN EXTERNAL_QUERY('" + connection.toString() + "',\n"
+ " \"SELECT t.AccountId, SUM(t.Amount) AS TotalSpending"
+ " FROM TransactionLedger t"
+ " WHERE t.Timestamp >= TIMESTAMP_ADD(CURRENT_TIMESTAMP(), INTERVAL -90 DAY)"
+ " GROUP BY t.AccountId"
+ " HAVING SUM(t.Amount) > " + threshold + "\"\n"
+ ") t ON cs.CustomerId = t.AccountId\n"
+ "JOIN EXTERNAL_QUERY('" + connection.toString() + "',\n"
+ " \"SELECT CustomerId, FullName"
+ " FROM Customers\"\n"
+ ") c ON c.CustomerId = cs.CustomerId\n"
+ "WHERE cs.CampaignId = '" + campaignId + "'";
try {
QueryJobConfiguration queryConfig = QueryJobConfiguration.newBuilder(bqQuery).build();
TableResult results = bq.query(queryConfig);
System.out.println("Customers for campaign (" + campaignId + "):");
results.iterateAll().forEach(row -> {
System.out.println(" " + row.get("FullName").getStringValue()
+ " (" + row.get("CustomerId").getStringValue() + ")");
});
} catch (JobException e) {
throw (BigQueryException) e.getCause();
} catch (InterruptedException e) {
throw SpannerExceptionFactory.propagateInterrupt(e);
}
}
캠페인의 main
메서드에 다른 케이스 문을 추가합니다.
case "campaign":
String campaignId = (args.length >= 2) ? args[1] : "";
int threshold = (args.length >= 3) ? Integer.parseInt(args[2]) : 5000;
campaign(bigquery, db, location, campaignId, threshold);
break;
마지막으로 printUsageAndExit
메서드에 캠페인 사용 방법을 추가합니다.
System.out.println(" java -jar target/onlinebanking.jar campaign campaign1 5000");
System.out.println(" - Use Federated Queries (BigQuery) to find customers that match a "
+ "marketing campaign by name based on a recent spending threshold.\n");
App.java
에 변경사항을 저장합니다.
애플리케이션을 다시 빌드합니다.
mvn package
제휴 쿼리를 실행하여 지난 3개월 동안 지출이 $5000
이상인 경우 마케팅 캠페인 (campaign1
)에 포함해야 하는 고객을 확인합니다. campaign
명령어를 실행합니다.
java -jar target/onlinebanking.jar campaign campaign1 5000
예상 출력:
Customers for campaign (campaign1): Alice Smith (1) Eve Davis (5) Kelly Thomas (11)
이제 이러한 고객에게 특별 혜택이나 리워드를 제공하여 타겟팅할 수 있습니다.
또는 지난 3개월 동안 더 낮은 지출 기준을 달성한 더 많은 고객을 찾을 수 있습니다.
java -jar target/onlinebanking.jar campaign campaign1 2500
예상 출력:
Customers for campaign (campaign1): Alice Smith (1) Charlie Williams (3) Eve Davis (5) Ivy Taylor (9) Kelly Thomas (11)
요약
이 단계에서는 실시간 Spanner 데이터를 가져오는 BigQuery의 제휴 쿼리를 실행했습니다.
다음 단계
다음으로, 요금이 청구되지 않도록 이 Codelab에서 만든 리소스를 삭제할 수 있습니다.
9. 정리 (선택사항)
이 단계는 선택사항입니다. Spanner 인스턴스 실험을 계속하려면 지금은 삭제할 필요가 없습니다. 하지만 사용 중인 프로젝트에는 인스턴스에 대한 요금이 계속 청구됩니다. 이 인스턴스가 더 이상 필요하지 않다면 요금이 청구되지 않도록 지금 삭제해야 합니다. 이 Codelab에서는 Spanner 인스턴스 외에도 BigQuery 데이터 세트와 연결을 만들었으며, 더 이상 필요하지 않으면 정리해야 합니다.
Spanner 인스턴스를 삭제합니다.
gcloud spanner instances delete cloudspanner-onlinebanking
계속 진행하겠다고 확인합니다 (Y 입력).
Delete instance [cloudspanner-onlinebanking]. Are you sure? Do you want to continue (Y/n)?
BigQuery 연결 및 데이터 세트를 삭제합니다.
bq rm --connection --location=us-central1 spanner-connection
bq rm -r MarketingCampaigns
BigQuery 데이터 세트 삭제를 확인합니다 (Y 입력).
rm: remove dataset '<PROJECT_ID>:MarketingCampaigns'? (y/N)
10. 축하합니다
🚀 새 Cloud Spanner 인스턴스를 만들고, 빈 데이터베이스를 만들고, 샘플 데이터를 로드하고, 고급 작업 및 쿼리를 실행하고, 원하는 경우 Cloud Spanner 인스턴스를 삭제했습니다.
학습한 내용
- Spanner 인스턴스 설정 방법
- 데이터베이스 및 테이블을 만드는 방법
- Spanner 데이터베이스 테이블에 데이터를 로드하는 방법
- Spanner에서 Vertex AI 모델을 호출하는 방법
- 유사 검색 및 전체 텍스트 검색을 사용하여 Spanner 데이터베이스를 쿼리하는 방법
- BigQuery에서 Spanner에 대해 통합 쿼리를 실행하는 방법
- Spanner 인스턴스를 삭제하는 방법
다음 단계
- 다음을 포함한 고급 Spanner 기능에 대해 자세히 알아보세요.
- 사용 가능한 Spanner 클라이언트 라이브러리를 참고하세요.