

# Node.js および DAX
<a name="DAX.client.run-application-nodejs-3"></a>

# Node.js のデフォルトのクライアント構成
<a name="DAX-client-config-JS"></a>

DAX JavaScript SDK クライアントを構成する際には、さまざまなパラメータをカスタマイズすることでパフォーマンス、接続処理、エラー耐性を最適化できます。次の表は、タイムアウト値、再試行メカニズム、認証情報管理、ヘルスモニタリングオプションなど、クライアントが DAX クラスターとやり取りする方法をコントロールするデフォルト構成設定をまとめたものです。詳細については、「[DynamoDBClient Operations](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/client/dynamodb/)」を参照してください。


**DAX JS SDK クライアントのデフォルト**  

| パラメータ | Type | 説明 | 
| --- | --- | --- | 
|  `region` オプション  |  `string`  |  DAX クライアントに使用する AWS リージョン (例 - 'us-east-1')。環境変数を介して指定されていない場合は必須のパラメータとなります。  | 
|  `endpoint` 必須  |  `string`  | SDK が接続するクラスターのエンドポイント。 例: 暗号化されていない場合 – dax-cluster-name.region.amazonaws.com 暗号化されている場合 – daxs://my-cluster.l6fzcv.dax-clusters.us-east-1.amazonaws.com  | 
|  `requestTimeout` デフォルトは 6000 ミリ秒  |  `number`  | クライアントが DAX からのレスポンスを待機する最大時間を定義します。  | 
|  `writeRetries` デフォルトは 1  |  `number`  | 書き込みリクエスト失敗時の再試行回数。  | 
|  `readRetries` デフォルトは 1  |  `number`  | 読み取りリクエスト失敗時の再試行回数。  | 
|  `maxRetries` デフォルトは 1  |  `number`  | リクエスト失敗時の最大再試行回数。 readRetries/writeRetries が設定されている場合、readRetries と writeRetries の構成セットが maxRetries よりも優先されます。  | 
|  `connectTimeout` デフォルトは 10000 ミリ秒  |  `number`  | いずれかのクラスターノードへの接続を確立するためのタイムアウト (ミリ秒単位)。  | 
|  `maxConcurrentConnections` デフォルトは 100  |  `number`  | クライアントインスタンスが DAX クラスター内のノードごとに作成できる同時接続の最大数を制限します。  | 
|  `maxRetryDelay` デフォルトは 7000 ミリ秒  |  `number`  | `waitForRecoveryBeforeRetrying` フラグを true に設定してリカバリが必要であることを DAX サーバーが示すと、クライアントは再試行前に一時停止します。これらのリカバリ期間中は、`maxRetryDelay` パラメータによってある再試行から次の再試行までの最大待機時間が決定されます。このリカバリ固有の構成は、DAX サーバーがリカバリモードになっている場合にのみ適用されます。他のすべてのシナリオでは、再試行回数に基づく指数関数的な遅延 (`writeRetries`、`readRetries`、`maxRetries` のいずれかのパラメータによって制御)、または例外タイプに応じた即時再試行のいずれかのパターンの再試行動作に従います。  | 
|  `credentials` オプション  |  `[AwsCredentialIdentity](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/Package/-aws-sdk-credential-providers/)` \$1 `[AwsCredentialIdentityProvider](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/Package/-aws-sdk-credential-providers/)`  |  リクエストの認証に使用する AWS 認証情報。AwsCredentialIdentity または AwsCredentialIdentityProvider として指定できます。指定しなかった場合、AWS SDK ではデフォルトの認証情報プロバイダーチェーンを自動的に使用します。例: `\$1 accessKeyId: 'AKIA...', secretAccessKey: '...', sessionToken: '...' \$1` \$1 @default ではデフォルトの AWS 認証情報プロバイダーチェーンを使用します。  | 
|  `healthCheckInterval` デフォルトは 5000 ミリ秒  |  `number`  | クラスターヘルスチェックの間隔 (ミリ秒単位)。間隔を短くするほどより頻繁にチェックされます。  | 
|  `healthCheckTimeout` デフォルトは 1000 ミリ秒  |  `number`  | ヘルスチェックが完了するまでのタイムアウト (ミリ秒単位)。  | 
|  `skipHostnameVerification` デフォルトは false  |  `boolean`  |  TLS 接続のホスト名検証をスキップします。暗号化されていないクラスターには影響しません。デフォルトではホスト名検証が実行されます。これを True に設定すると検証がスキップされます。オフにすると接続先のクラスターを認証できなくなるため注意してください。  | 
|  `unhealthyConsecutiveErrorCount` デフォルトは 5  |  `number`  | エラーが何回連続で発生したらヘルスチェック間隔内でノードの異常を通知するかを設定します。  | 
|  `clusterUpdateInterval` デフォルトは 4000 ミリ秒  |  `number`  | メンバーシップ変更についてクラスターメンバーをポーリングする間隔を返します。  | 
|  `clusterUpdateThreshold` デフォルトは 125  |  `number`  | メンバーシップ変更についてクラスターがポーリングされない下限しきい値を返します。  | 
|  `credentailProvider` 省略可 \$1 デフォルトは null  |  `[AwsCredentialIdentityProvider](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/Package/-aws-sdk-credential-providers/)`  | DAX へのリクエストを認証するために使用される AWS 認証情報のユーザー定義プロバイダー。  | 


**DaxDocument のページ分割構成**  

| 名前 | 型 | 詳細 | 
| --- | --- | --- | 
|  `client`  |  DaxDocument  |  DaxDocument タイプのインスタンス。  | 
|  `pageSize`  |  数値  |  ページごとの項目数を指定します。  | 
|  `startingToken` オプションです。  |  すべて  |  前のレスポンスの LastEvaluatedKey を後続のリクエストに使用できます。  | 

ページ分割の使用については、「[TryDax.js](DAX.client.tutorial-TryDax.md)」を参照してください。

# DAX Node.js SDK V3 への移行
<a name="DAX.client.run-application-nodejs-3-migrating"></a>

この移行ガイドは、既存の DAX Node.js アプリケーションの移行に役立ちます。新しい SDK には Node.js 18 以降が必要で、DynamoDB Accelerator コードの作成方法にいくつかの重要な変更が導入されています。このガイドでは、構文の変更、新しいインポート方法、更新された非同期プログラミングパターンなど、主な違いについて説明します。

## V2 Node.js DAX の使用方法
<a name="DAX.client.run-application-nodejs-3-migrating-V2-usage"></a>

```
const AmazonDaxClient = require('amazon-dax-client');
const AWS = require('aws-sdk');

var region = "us-west-2";

AWS.config.update({
  region: region,
});

var client = new AWS.DynamoDB.DocumentClient();

if (process.argv.length > 2) {
  var dax = new AmazonDaxClient({
    endpoints: [process.argv[2]],
    region: region,
  });
  client = new AWS.DynamoDB.DocumentClient({ service: dax });
}

// Make Get Call using Dax
var params = {
    TableName: 'TryDaxTable',
    pk: 1,
    sk: 1
}
client.get(params, function (err, data) {
    if (err) {
        console.error(
            "Unable to read item. Error JSON:",
            JSON.stringify(err, null, 2)
          );
    } else {
        console.log(data);
    }
});
```

## V3 Node.js DAX の使用方法
<a name="DAX.client.run-application-nodejs-3-migrating-V3-dax-usage"></a>

DAX Node.js V3 ノードバージョン 18 以降を使用することをお勧めします。ノード 18 に移行するには以下を使用します。

```
import { DaxDocument } from '@amazon-dax-sdk/lib-dax';
import { DynamoDBDocument } from '@aws-sdk/lib-dynamodb';
import { DynamoDBClient } from '@aws-sdk/client-dynamodb';

let client: DynamoDBDocument | DaxDocument = DynamoDBDocument.from(
  new DynamoDBClient({ region: 'us-west-2' })
);

if (process.argv.length > 2) {
  client = new DaxDocument({
    endpoints: [process.argv[2]],
    region: 'us-west-2',
  });
}

const params = {
  TableName: 'TryDaxTable',
  Key: { pk: 1, sk: 1 },
};

try {
  const results = await client.get(params);
  console.log(results);
} catch (err) {
  console.error(err);
}
```

DAX SDK for Node.js v3.x は [AWS SDK for Node.js v3.x](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/introduction/) と互換性があります。DAX SDK for Node.js v3.x では[集約](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/introduction/#high-level-concepts)クライアントの使用をサポートしています。DAX では必要最低限のクライアントの作成をサポートしていないことに注意してください。サポートされていない機能の詳細については、「[AWS SDK V3 と同等のものがない機能](#DAX.client.run-application-nodejs-3-not-in-parity)」を参照してください。

このステップに従って、Amazon EC2 インスタンスで Node.js サンプルアプリケーションを実行します。

**DAX の Node.js サンプルを実行するには**

1. 以下のように、Amazon EC2 インスタンスで Node.js を設定します。

   1. ノードバージョンマネージャー (`nvm`) をインストールします。

      ```
      curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash
      ```

   1. nvm を使用して Node.js をインストールします。

      ```
      nvm install 18
      ```

   1. nvm を使用して Node 18 を使用する

      ```
      nvm use 18
      ```

   1. Node.js が正しくインストールおよび実行されているかテストします。

      ```
      node -e "console.log('Running Node.js ' + process.version)"
      ```

      これにより次のメッセージが表示されるはずです。

      `Running Node.js v18.x.x`

1. ノードパッケージマネージャー (`npm`) を使用して、DaxDocument Node.js クライアントをインストールします。

   ```
   npm install @amazon-dax-sdk/lib-dax
   ```

## TryDax サンプルコード
<a name="DAX.client.run-application-nodejs-3-TryDax-sample-code"></a>

DynamoDB Accelerator (DAX) のパフォーマンス上の利点を評価するには、以下の手順に従って、標準の DynamoDB クラスターと DAX クラスター間の読み取り操作時間を比較するサンプルテストを実行します。

1. ワークスペースを設定し、`lib-dax` を依存関係としてインストールしたら、[TryDax.js](DAX.client.tutorial-TryDax.md) をプロジェクトにコピーします。

1. DAX クラスターに対してプログラムを実行します。DAX クラスターのエンドポイントを確認するには、次のいずれかを選択します。
   +  **DynamoDB コンソールの使用** — DAX クラスターを選択します。次の例のように、クラスターエンドポイントがコンソールに表示されます。

     ```
     dax://my-cluster.l6fzcv.dax-clusters.us-east-1.amazonaws.com
     ```
   + **AWS CLI の使用** — 次のコマンドを入力します。

     ```
     aws dax describe-clusters --query "Clusters[*].ClusterDiscoveryEndpoint"
     ```

     次の例のように、クラスターエンドポイントが出力に表示されます。

     ```
     {
         "Address": "my-cluster.l6fzcv.dax-clusters.us-east-1.amazonaws.com",
         "Port": 8111,
         "URL": "dax://my-cluster.l6fzcv.dax-clusters.us-east-1.amazonaws.com"
     }
     ```

1. 次に、クラスターエンドポイントをコマンドラインパラメータとして指定してプログラムを実行します。

   ```
   node TryDax.js dax://my-cluster.l6fzcv.dax-clusters.us-east-1.amazonaws.com
   ```

   次のような出力が表示されます:

   ```
   Attempting to create table; please wait...
   Successfully created table. Table status: ACTIVE
   Writing data to the table...
   Writing 20 items for partition key:  1
   Writing 20 items for partition key:  2
   Writing 20 items for partition key:  3
   ...
   Running GetItem Test
           Total time: 153555.10 µs - Avg time: 383.89 µs
           Total time: 44679.96 µs - Avg time: 111.70 µs
           Total time: 36885.86 µs - Avg time: 92.21 µs
           Total time: 32467.25 µs - Avg time: 81.17 µs
           Total time: 32202.60 µs - Avg time: 80.51 µs
   Running Query Test
           Total time: 14869.25 µs - Avg time: 2973.85 µs
           Total time: 3036.31 µs - Avg time: 607.26 µs
           Total time: 2468.92 µs - Avg time: 493.78 µs
           Total time: 2062.53 µs - Avg time: 412.51 µs
           Total time: 2178.22 µs - Avg time: 435.64 µs
   Running Scan Test
           Total time: 2395.88 µs - Avg time: 479.18 µs
           Total time: 2207.16 µs - Avg time: 441.43 µs
           Total time: 2443.14 µs - Avg time: 488.63 µs
           Total time: 2038.24 µs - Avg time: 407.65 µs
           Total time: 1972.17 µs - Avg time: 394.43 µs
   Running Pagination Test
   Scan Pagination
   [
     { pk: 1, sk: 1, someData: 'XXXXXXXXXX' },
     { pk: 1, sk: 2, someData: 'XXXXXXXXXX' },
     { pk: 1, sk: 3, someData: 'XXXXXXXXXX' }
   ]
   [
     { pk: 1, sk: 4, someData: 'XXXXXXXXXX' },
     { pk: 1, sk: 5, someData: 'XXXXXXXXXX' },
     { pk: 1, sk: 6, someData: 'XXXXXXXXXX' }
   ]
   ...
   Query Pagination
   [
     { pk: 1, sk: 1, someData: 'XXXXXXXXXX' },
     { pk: 1, sk: 2, someData: 'XXXXXXXXXX' },
     { pk: 1, sk: 3, someData: 'XXXXXXXXXX' }
   ]
   [
     { pk: 1, sk: 4, someData: 'XXXXXXXXXX' },
     { pk: 1, sk: 5, someData: 'XXXXXXXXXX' },
     { pk: 1, sk: 6, someData: 'XXXXXXXXXX' }
   ]
   ...
   Attempting to delete table; please wait...
   Successfully deleted table.
   ```

   タイミング情報を書き留めます。`GetItem`、`Query`、`Scan` の各テストに必要なマイクロ秒数。

1. このケースでは、DAX クラスターに対してプログラムを実行しました。次に、プログラムを再度実行します。今回は DynamoDB に対して実行します。

1. ここでプログラムを再度実行しますが、今度はクラスターエンドポイント URL をコマンドラインパラメータとして指定しません。

   ```
   node TryDax.js
   ```

   出力を見て、タイミング情報を書き留めます。`GetItem`、`Query` および `Scan` の経過時間は、DynamoDB と比較して DAX の方が大幅に低いはずです。

## AWS SDK V3 と同等のものがない機能
<a name="DAX.client.run-application-nodejs-3-not-in-parity"></a>
+ [必要最低限の](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/introduction/#high-level-concepts)クライアント – Dax Node.js V3 では必要最低限のクライアントをサポートしていません。

  ```
  const dynamoDBClient = new DynamoDBClient({ region: 'us-west-2' });
  const regularParams = {
      TableName: 'TryDaxTable',
      Key: {
          pk: 1,
          sk: 1
      }
  };
  // The DynamoDB client supports the send operation.
  const dynamoResult = await dynamoDBClient.send(new GetCommand(regularParams));
  
  // However, the DaxDocument client does not support the send operation.
  const daxClient = new DaxDocument({
      endpoints: ['your-dax-endpoint'],
      region: 'us-west-2',
  });
  
  const params = {
      TableName: 'TryDaxTable',
      Key: {
          pk: 1,
          sk: 1
      }
  };
  
  // This will throw an error - send operation is not supported for DAX. Please refer to documentation.
  const result = await daxClient.send(new GetCommand(params));
  console.log(result);
  ```
+ [ミドルウェアスタック](https://aws.amazon.com/blogs/developer/middleware-stack-modular-aws-sdk-js/) – Dax Node.js V3 ではミドルウェア関数の使用をサポートしていません。

  ```
  const dynamoDBClient = new DynamoDBClient({ region: 'us-west-2' });
  // The DynamoDB client supports the middlewareStack.
  dynamoDBClient.middlewareStack.add(
    (next, context) =>> async (args) => {
      console.log("Before operation:", args);
      const result = await next(args);
      console.log("After operation:", result);
      return result;
    },
    {
      step: "initialize", // or "build", "finalizeRequest", "deserialize"
      name: "loggingMiddleware",
    }
  );
  
  // However, the DaxDocument client does not support the middlewareStack.
  const daxClient = new DaxDocument({
      endpoints: ['your-dax-endpoint'],
      region: 'us-west-2',
  });
  
  // This will throw an error - custom middleware and middlewareStacks are not supported for DAX. Please refer to documentation.
  daxClient.middlewareStack.add(
    (next, context) => async (args) => {
      console.log("Before operation:", args);
      const result = await next(args);
      console.log("After operation:", result);
      return result;
    },
    {
      step: "initialize", // or "build", "finalizeRequest", "deserialize"
      name: "loggingMiddleware",
    }
  );
  ```

# TryDax.js
<a name="DAX.client.tutorial-TryDax"></a>

```
import { DynamoDB, waitUntilTableExists, waitUntilTableNotExists } from "@aws-sdk/client-dynamodb";
import { DaxDocument, daxPaginateScan, daxPaginateQuery } from "@amazon-dax-sdk/lib-dax";
import { DynamoDBDocument, paginateQuery, paginateScan } from "@aws-sdk/lib-dynamodb";

const region = "us-east-1";
const tableName = "TryDaxTable";

// Determine the client (DynamoDB or DAX)
let client = DynamoDBDocument.from(new DynamoDB({ region }));
if (process.argv.length > 2) {
  client = new DaxDocument({ region, endpoint: process.argv[2] });
}

// Function to create table
async function createTable() {
  const dynamodb = new DynamoDB({ region });
  const params = {
    TableName: tableName,
    KeySchema: [
      { AttributeName: "pk", KeyType: "HASH" },
      { AttributeName: "sk", KeyType: "RANGE" },
    ],
    AttributeDefinitions: [
      { AttributeName: "pk", AttributeType: "N" },
      { AttributeName: "sk", AttributeType: "N" },
    ],
    ProvisionedThroughput: { ReadCapacityUnits: 25, WriteCapacityUnits: 25 },
  };

  try {
    console.log("Attempting to create table; please wait...");
    await dynamodb.createTable(params);
    await waitUntilTableExists({ client: dynamodb, maxWaitTime: 30 }, { TableName: tableName });
    console.log("Successfully created table. Table status: ACTIVE");
  } catch (err) {
    console.error("Error in table creation:", err);
  }
}

// Function to insert data
async function writeData() {
  console.log("Writing data to the table...");
  const someData = "X".repeat(10);
  for (let ipk = 1; ipk <= 20; ipk++) {
    console.log("Writing 20 items for partition key: ", ipk)
    for (let isk = 1; isk <= 20; isk++) {
      try {
        await client.put({ TableName: tableName, Item: { pk: ipk, sk: isk, someData } });
      } catch (err) {
        console.error("Error inserting data:", err);
      }
    }
  }
}

// Function to test GetItem
async function getItemTest() {
  console.log("Running GetItem Test");
  for (let i = 0; i < 5; i++) {
    const startTime = performance.now();
    const promises = [];
    for (let ipk = 1; ipk <= 20; ipk++) {
      for (let isk = 1; isk <= 20; isk++) {
        promises.push(client.get({ TableName: tableName, Key: { pk: ipk, sk: isk } }));
      }
    }
    await Promise.all(promises);
    const endTime = performance.now();
    const iterTime = (endTime - startTime) * 1000;
    const totalTime = iterTime.toFixed(2).padStart(3, ' ');
    const avgTime = (iterTime / 400).toFixed(2).padStart(3, ' ');
    console.log(`\tTotal time: ${totalTime} \u00B5s - Avg time: ${avgTime} \u00B5s`);
  }
}

// Function to test Query
async function queryTest() {
  console.log("Running Query Test");
  for (let i = 0; i < 5; i++) {
    const startTime = performance.now();
    const promises = [];
    for (let pk = 1; pk <= 5; pk++) {
      const params = {
        TableName: tableName,
        KeyConditionExpression: "pk = :pkval and sk between :skval1 and :skval2",
        ExpressionAttributeValues: { ":pkval": pk, ":skval1": 1, ":skval2": 2 },
      };
      promises.push(client.query(params));
    }
    await Promise.all(promises);
    const endTime = performance.now();
    const iterTime = (endTime - startTime) * 1000;
    const totalTime = iterTime.toFixed(2).padStart(3, ' ');
    const avgTime = (iterTime / 5).toFixed(2).padStart(3, ' ');
    console.log(`\tTotal time: ${totalTime} \u00B5s - Avg time: ${avgTime} \u00B5s`);
  }
}

// Function to test Scan
async function scanTest() {
  console.log("Running Scan Test");
  for (let i = 0; i < 5; i++) {
    const startTime = performance.now();
    const promises = [];
    for (let pk = 1; pk <= 5; pk++) {
      const params = {
        TableName: tableName,
        FilterExpression: "pk = :pkval and sk between :skval1 and :skval2",
        ExpressionAttributeValues: { ":pkval": pk, ":skval1": 1, ":skval2": 2 },
      };
      promises.push(client.scan(params));
    }
    await Promise.all(promises);
    const endTime = performance.now();
    const iterTime = (endTime - startTime) * 1000;
    const totalTime = iterTime.toFixed(2).padStart(3, ' ');
    const avgTime = (iterTime / 5).toFixed(2).padStart(3, ' ');
    console.log(`\tTotal time: ${totalTime} \u00B5s - Avg time: ${avgTime} \u00B5s`);
  }
}

// Function to test Pagination
async function paginationTest() {
  console.log("Running Pagination Test");
  console.log("Scan Pagination");
  const scanParams = { TableName: tableName };
  const paginator = process.argv.length > 2 ? daxPaginateScan : paginateScan;
  for await (const page of paginator({ client, pageSize: 3 }, scanParams)) {
    console.log(page.Items);
  }

  console.log("Query Pagination");
  const queryParams = {
    TableName: tableName,
    KeyConditionExpression: "pk = :pkval and sk between :skval1 and :skval2",
    ExpressionAttributeValues: { ":pkval": 1, ":skval1": 1, ":skval2": 10 },
  };
  const queryPaginator = process.argv.length > 2 ? daxPaginateQuery : paginateQuery;
  for await (const page of queryPaginator({ client, pageSize: 3 }, queryParams)) {
    console.log(page.Items);
  }
}

// Function to delete the table
async function deleteTable() {
  const dynamodb = new DynamoDB({ region });
  console.log("Attempting to delete table; please wait...")
  try {
    await dynamodb.deleteTable({ TableName: tableName });
    await waitUntilTableNotExists({ client: dynamodb, maxWaitTime: 30 }, { TableName: tableName });
    console.log("Successfully deleted table.");
  } catch (err) {
    console.error("Error deleting table:", err);
  }
}

// Execute functions sequentially
(async function () {
  await createTable();
  await writeData();
  await getItemTest();
  await queryTest();
  await scanTest();
  await paginationTest();
  await deleteTable();
})();
```