자습서: 지역 분산 데이터베이스 구현(Azure SQL Database)

적용 대상:Azure SQL Database

SQL Database와 클라이언트 애플리케이션의 데이터베이스를 원격 지역으로 장애 조치하도록 구성하고 장애 조치 계획을 테스트합니다. 다음 방법에 대해 설명합니다.

  • 장애 조치 그룹 만들기
  • Java 애플리케이션을 실행하여 SQL Database에서 데이터베이스 쿼리
  • 테스트 장애 조치

Azure 구독이 아직 없는 경우 시작하기 전에 체험 계정을 만듭니다.

사전 요구 사항

참고 항목

이 문서에서는 Azure와 상호 작용하는 데 권장되는 PowerShell 모듈인 Azure Az PowerShell 모듈을 사용합니다. Az PowerShell 모듈을 시작하려면 Azure PowerShell 설치를 참조하세요. Az PowerShell 모듈로 마이그레이션하는 방법에 대한 자세한 내용은 Azure PowerShell을 AzureRM에서 Azure로 마이그레이션을 참조하세요.

중요

PowerShell Azure Resource Manager 모듈은 여전히 Azure SQL Database에서 지원되지만 향후의 모든 개발은 Az.Sql 모듈을 위한 것입니다. 이러한 cmdlet은 AzureRM.Sql을 참조하세요. Az 모듈 및 AzureRm 모듈의 명령에 대한 인수는 실질적으로 동일합니다.

이 자습서를 완료하려면 다음 항목을 설치해야 합니다.

  • Azure PowerShell

  • Azure SQL Database의 단일 데이터베이스. 데이터베이스를 만들려면 다음 중 하나를 사용합니다.

    참고 항목

    이 자습서에서는 AdventureWorksLT 샘플 데이터베이스를 사용합니다.

Important

이 자습서의 단계를 수행하는 컴퓨터의 공용 IP 주소를 사용하도록 방화벽 규칙을 설정하세요. 데이터베이스 수준 방화벽 규칙은 보조 서버에 자동으로 복제합니다.

자세한 내용은 데이터베이스 수준 방화벽 규칙 만들기를 참조하세요. 사용 중인 컴퓨터용 서버 수준 방화벽 규칙에 사용되는 IP 주소를 확인하려면 서버 수준 방화벽 만들기를 참조하세요.

장애 조치 그룹 만들기

Azure PowerShell을 사용하여 기존 서버와 다른 지역의 새 서버 간에 장애 조치 그룹을 만듭니다. 그런 다음 장애 조치(failover) 그룹에 샘플 데이터베이스를 추가합니다.

Important

이 샘플에는 Azure PowerShell Az 1.0 이상이 필요합니다. Get-Module -ListAvailable Az를 실행하여 설치된 버전을 확인합니다. 설치해야 하는 경우 Azure PowerShell 모듈 설치를 참조하세요.

Connect-AzAccount를 실행하여 Azure에 로그인합니다.

장애 조치 그룹을 만들려면 다음 스크립트를 실행합니다.

$admin = "<adminName>"
$password = "<password>"
$resourceGroup = "<resourceGroupName>"
$location = "<resourceGroupLocation>"
$server = "<serverName>"
$database = "<databaseName>"
$drLocation = "<disasterRecoveryLocation>"
$drServer = "<disasterRecoveryServerName>"
$failoverGroup = "<globallyUniqueFailoverGroupName>"

# create a backup server in the failover region
New-AzSqlServer -ResourceGroupName $resourceGroup -ServerName $drServer `
    -Location $drLocation -SqlAdministratorCredentials $(New-Object -TypeName System.Management.Automation.PSCredential `
    -ArgumentList $admin, $(ConvertTo-SecureString -String $password -AsPlainText -Force))

# create a failover group between the servers
New-AzSqlDatabaseFailoverGroup –ResourceGroupName $resourceGroup -ServerName $server `
    -PartnerServerName $drServer –FailoverGroupName $failoverGroup –FailoverPolicy Automatic -GracePeriodWithDataLossHours 2

# add the database to the failover group
Get-AzSqlDatabase -ResourceGroupName $resourceGroup -ServerName $server -DatabaseName $database | `
    Add-AzSqlDatabaseToFailoverGroup -ResourceGroupName $resourceGroup -ServerName $server -FailoverGroupName $failoverGroup

Azure Portal에서 데이터베이스를 선택한 다음 설정>지역에서 복제를 선택하여 지역에서 복제 설정을 변경할 수도 있습니다.

Geo-replication settings

샘플 프로젝트 실행

  1. 콘솔에서 다음 명령을 사용하여 Maven 프로젝트를 만듭니다.

    mvn archetype:generate "-DgroupId=com.sqldbsamples" "-DartifactId=SqlDbSample" "-DarchetypeArtifactId=maven-archetype-quickstart" "-Dversion=1.0.0"
    
  2. Y를 입력하고 Enter 키를 누릅니다.

  3. 디렉터리를 새 프로젝트로 변경합니다.

    cd SqlDbSample
    
  4. 원하는 편집기를 사용하여 프로젝트 폴더에서 pom.xml 파일을 엽니다.

  5. 다음 dependency 섹션을 추가하여 SQL Server 종속성용 Microsoft JDBC Driver를 추가합니다. 종속성은 더 큰 dependencies 섹션 내에 붙여넣어야 합니다.

    <dependency>
      <groupId>com.microsoft.sqlserver</groupId>
      <artifactId>mssql-jdbc</artifactId>
     <version>6.1.0.jre8</version>
    </dependency>
    
  6. dependencies 섹션 뒤에 properties 섹션을 추가하여 Java 버전을 지정합니다.

    <properties>
      <maven.compiler.source>1.8</maven.compiler.source>
      <maven.compiler.target>1.8</maven.compiler.target>
    </properties>
    
  7. build 섹션 다음에 properties 섹션을 추가하여 매니페스트 파일을 지원합니다.

    <build>
      <plugins>
        <plugin>
          <groupId>org.apache.maven.plugins</groupId>
          <artifactId>maven-jar-plugin</artifactId>
          <version>3.0.0</version>
          <configuration>
            <archive>
              <manifest>
                <mainClass>com.sqldbsamples.App</mainClass>
              </manifest>
            </archive>
         </configuration>
        </plugin>
      </plugins>
    </build>
    
  8. pom.xml 파일을 저장하고 닫습니다.

  9. ..\SqlDbSample\src\main\java\com\sqldbsamples에 있는 App.java 파일을 열고 다음 코드로 내용을 바꿉니다.

    package com.sqldbsamples;
    
    import java.sql.Connection;
    import java.sql.Statement;
    import java.sql.PreparedStatement;
    import java.sql.ResultSet;
    import java.sql.Timestamp;
    import java.sql.DriverManager;
    import java.util.Date;
    import java.util.concurrent.TimeUnit;
    
    public class App {
    
       private static final String FAILOVER_GROUP_NAME = "<your failover group name>";  // add failover group name
    
       private static final String DB_NAME = "<your database>";  // add database name
       private static final String USER = "<your admin>";  // add database user
       private static final String PASSWORD = "<your password>";  // add database password
    
       private static final String READ_WRITE_URL = String.format("jdbc:" +
          "sqlserver://%s.database.windows.net:1433;database=%s;user=%s;password=%s;encrypt=true;" +
          "hostNameInCertificate=*.database.windows.net;loginTimeout=30;",
          FAILOVER_GROUP_NAME, DB_NAME, USER, PASSWORD);
       private static final String READ_ONLY_URL = String.format("jdbc:" +
          "sqlserver://%s.secondary.database.windows.net:1433;database=%s;user=%s;password=%s;encrypt=true;" +
          "hostNameInCertificate=*.database.windows.net;loginTimeout=30;",
          FAILOVER_GROUP_NAME, DB_NAME, USER, PASSWORD);
    
       public static void main(String[] args) {
          System.out.println("#######################################");
          System.out.println("## GEO DISTRIBUTED DATABASE TUTORIAL ##");
          System.out.println("#######################################");
          System.out.println("");
    
          int highWaterMark = getHighWaterMarkId();
    
          try {
             for(int i = 1; i < 1000; i++) {
                 //  loop will run for about 1 hour
                 System.out.print(i + ": insert on primary " +
                    (insertData((highWaterMark + i)) ? "successful" : "failed"));
                 TimeUnit.SECONDS.sleep(1);
                 System.out.print(", read from secondary " +
                    (selectData((highWaterMark + i)) ? "successful" : "failed") + "\n");
                 TimeUnit.SECONDS.sleep(3);
             }
          } catch(Exception e) {
             e.printStackTrace();
       }
    }
    
    private static boolean insertData(int id) {
       // Insert data into the product table with a unique product name so we can find the product again
       String sql = "INSERT INTO SalesLT.Product " +
          "(Name, ProductNumber, Color, StandardCost, ListPrice, SellStartDate) VALUES (?,?,?,?,?,?);";
    
       try (Connection connection = DriverManager.getConnection(READ_WRITE_URL);
               PreparedStatement pstmt = connection.prepareStatement(sql)) {
          pstmt.setString(1, "BrandNewProduct" + id);
          pstmt.setInt(2, 200989 + id + 10000);
          pstmt.setString(3, "Blue");
          pstmt.setDouble(4, 75.00);
          pstmt.setDouble(5, 89.99);
          pstmt.setTimestamp(6, new Timestamp(new Date().getTime()));
          return (1 == pstmt.executeUpdate());
       } catch (Exception e) {
          return false;
       }
    }
    
    private static boolean selectData(int id) {
       // Query the data previously inserted into the primary database from the geo replicated database
       String sql = "SELECT Name, Color, ListPrice FROM SalesLT.Product WHERE Name = ?";
    
       try (Connection connection = DriverManager.getConnection(READ_ONLY_URL);
               PreparedStatement pstmt = connection.prepareStatement(sql)) {
          pstmt.setString(1, "BrandNewProduct" + id);
          try (ResultSet resultSet = pstmt.executeQuery()) {
             return resultSet.next();
          }
       } catch (Exception e) {
          return false;
       }
    }
    
    private static int getHighWaterMarkId() {
       // Query the high water mark id stored in the table to be able to make unique inserts
       String sql = "SELECT MAX(ProductId) FROM SalesLT.Product";
       int result = 1;
       try (Connection connection = DriverManager.getConnection(READ_WRITE_URL);
               Statement stmt = connection.createStatement();
               ResultSet resultSet = stmt.executeQuery(sql)) {
          if (resultSet.next()) {
              result =  resultSet.getInt(1);
             }
          } catch (Exception e) {
           e.printStackTrace();
          }
          return result;
       }
    }
    
  10. App.java 파일을 저장하고 닫습니다.

  11. 명령 콘솔에서 다음 명령을 실행합니다.

    mvn package
    
  12. 수동으로 중지할 때까지 약 1시간 동안 실행할 애플리케이션을 시작하여 장애 조치 테스트를 실행할 시간을 확보합니다.

    mvn -q -e exec:java "-Dexec.mainClass=com.sqldbsamples.App"
    
    #######################################
    ## GEO DISTRIBUTED DATABASE TUTORIAL ##
    #######################################
    
    1. insert on primary successful, read from secondary successful
    2. insert on primary successful, read from secondary successful
    3. insert on primary successful, read from secondary successful
    ...
    

테스트 장애 조치

다음 스크립트를 실행하여 장애 조치를 시뮬레이션하고 애플리케이션 결과를 관찰합니다. 데이터베이스 마이그레이션을 진행하는 동안 일부 삽입 및 선택 작업이 어떻게 실패하는지 확인합니다.

다음 명령을 사용하여 테스트하는 동안 재해 복구 서버의 역할을 확인할 수 있습니다.

(Get-AzSqlDatabaseFailoverGroup -FailoverGroupName $failoverGroup `
    -ResourceGroupName $resourceGroup -ServerName $drServer).ReplicationRole

다음과 같이 장애 조치를 테스트합니다.

  1. 장애 조치 그룹의 수동 장애 조치를 시작합니다.

    Switch-AzSqlDatabaseFailoverGroup -ResourceGroupName $resourceGroup `
     -ServerName $drServer -FailoverGroupName $failoverGroup
    
  2. 장애 조치(failover) 그룹을 주 서버로 되돌립니다.

    Switch-AzSqlDatabaseFailoverGroup -ResourceGroupName $resourceGroup `
     -ServerName $server -FailoverGroupName $failoverGroup
    

다음 단계

고가용성 및 재해 복구 체크리스트를 검토합니다.

관련 Azure SQL Database 콘텐츠: