aws / aws-sdk-js-v3

Modularized AWS SDK for JavaScript.
Apache License 2.0
2.95k stars 554 forks source link

Importing AWS SDK DynamoDB affects performance of third party library #6182

Open sazarkin opened 2 weeks ago

sazarkin commented 2 weeks ago

Checkboxes for prior research

Describe the bug

After migrating from v2 to v3 I've noticed slowdown of one of our metrics. Importing dynamodb client affects parsing of html files.

What exactly aws sdk v3 does on import? Is it changes some runtime params? How to turn it off?

SDK version number

@aws-sdk/client-dynamodb@3.592.0

Which JavaScript Runtime is this issue in?

Node.js

Details of the browser/Node.js/ReactNative version

v20.13.1

Reproduction Steps

"use strict";
const initStart = performance.now();
const parse5_1 = require("parse5");

if (process.env.AWS_SDK === '1') {
    const client_dynamodb_1 = require("@aws-sdk/client-dynamodb");
    const version = require('@aws-sdk/client-dynamodb/package.json').version;
    console.log('DynamoDB', typeof client_dynamodb_1.DynamoDB, version);
}

const html = `<doctype html><html><head><title>Test</title></head><body><h1>Hello World</h1></body></html>`;

async function benchParse(count) {
    for (let i = 0; i < count; i++) {
        const doc = (0, parse5_1.parse)(html);
        (0, parse5_1.serialize)(doc);
    }
}

const name = 'bench-parse5';
const count = 1000000;
const benchFunc = benchParse;
const initDone = performance.now();
console.log('---', name, `init ${(initDone - initStart).toFixed(2)}ms`);
const startTime = performance.now();
console.profile(name);
benchFunc(count).then(() => {
    console.profileEnd();
    const grandTotal = performance.now() - startTime;
    console.log(`Count: ${count} Time: ${grandTotal.toFixed(2)}ms`);
    console.log('---');
});
{
  "dependencies": {
    "@aws-sdk/client-dynamodb": "^3.592.0",
    "parse5": "^7.1.2"
  }
}

Observed Behavior

with dynamodb import

DynamoDB function 3.592.0
--- bench-parse5 init 66.59ms
Count: 1000000 Time: 4507.70ms
---

Expected Behavior

test output: around 800ms lower

--- bench-parse5 init 12.97ms
Count: 1000000 Time: 3730.11ms
---

Possible Solution

No response

Additional Information/Context

No response

aBurmeseDev commented 2 weeks ago

Hi @sazarkin - thanks for reaching out.

I'd like to understand what your workflow looks like, and how you're using SDK. In your code example, I see that you're client-dynamodb but it doesn't show where the client call is being made. Please share steps to repro and complete SDK code example that will give us more insight.

sazarkin commented 2 weeks ago

@aBurmeseDev was you able to run provided snippet? Let me know if you need additional guidance.

I am trying to understand how only importing SDK affects this html parsing code. This is not about slow import or slow api calls.

Maybe the sdk extends some base nodes classes? Maybe it does some modifications to runtime?

Are you aware of anything like that?

aBurmeseDev commented 2 weeks ago

@sazarkin - the code you shared is incomplete. It doesn't show where API call is being made or which operation is being called. It only shows that you're importing the client.

For example, here's CreateTable command page which is one of the client-dynamodb operations to create table, you'll see code example for CreateTable operation. You would first install client with your preferred method, import it and make the call.

Example Syntax
Use a bare-bones client and the command you need to make an API call.

Expand
Copy
import { DynamoDBClient, CreateTableCommand } from "@aws-sdk/client-dynamodb"; // ES Modules import

const client = new DynamoDBClient(config);
const input = { // CreateTableInput
  AttributeDefinitions: [ // AttributeDefinitions // required
    { // AttributeDefinition
      AttributeName: "STRING_VALUE", // required
      AttributeType: "S" || "N" || "B", // required
    },
  ],
  TableName: "STRING_VALUE", // required
  KeySchema: [ // KeySchema // required
    { // KeySchemaElement
      AttributeName: "STRING_VALUE", // required
      KeyType: "HASH" || "RANGE", // required
    },
  ],
  LocalSecondaryIndexes: [ // LocalSecondaryIndexList
    { // LocalSecondaryIndex
      IndexName: "STRING_VALUE", // required
      KeySchema: [ // required
        {
          AttributeName: "STRING_VALUE", // required
          KeyType: "HASH" || "RANGE", // required
        },
      ],
      Projection: { // Projection
        ProjectionType: "ALL" || "KEYS_ONLY" || "INCLUDE",
        NonKeyAttributes: [ // NonKeyAttributeNameList
          "STRING_VALUE",
        ],
      },
    },
  ],
  GlobalSecondaryIndexes: [ // GlobalSecondaryIndexList
    { // GlobalSecondaryIndex
      IndexName: "STRING_VALUE", // required
      KeySchema: [ // required
        {
          AttributeName: "STRING_VALUE", // required
          KeyType: "HASH" || "RANGE", // required
        },
      ],
      Projection: {
        ProjectionType: "ALL" || "KEYS_ONLY" || "INCLUDE",
        NonKeyAttributes: [
          "STRING_VALUE",
        ],
      },
      ProvisionedThroughput: { // ProvisionedThroughput
        ReadCapacityUnits: Number("long"), // required
        WriteCapacityUnits: Number("long"), // required
      },
      OnDemandThroughput: { // OnDemandThroughput
        MaxReadRequestUnits: Number("long"),
        MaxWriteRequestUnits: Number("long"),
      },
    },
  ],
  BillingMode: "PROVISIONED" || "PAY_PER_REQUEST",
  ProvisionedThroughput: {
    ReadCapacityUnits: Number("long"), // required
    WriteCapacityUnits: Number("long"), // required
  },
  StreamSpecification: { // StreamSpecification
    StreamEnabled: true || false, // required
    StreamViewType: "NEW_IMAGE" || "OLD_IMAGE" || "NEW_AND_OLD_IMAGES" || "KEYS_ONLY",
  },
  SSESpecification: { // SSESpecification
    Enabled: true || false,
    SSEType: "AES256" || "KMS",
    KMSMasterKeyId: "STRING_VALUE",
  },
  Tags: [ // TagList
    { // Tag
      Key: "STRING_VALUE", // required
      Value: "STRING_VALUE", // required
    },
  ],
  TableClass: "STANDARD" || "STANDARD_INFREQUENT_ACCESS",
  DeletionProtectionEnabled: true || false,
  ResourcePolicy: "STRING_VALUE",
  OnDemandThroughput: {
    MaxReadRequestUnits: Number("long"),
    MaxWriteRequestUnits: Number("long"),
  },
};
const command = new CreateTableCommand(input);
const response = await client.send(command);

Hope that makes sense, let me know if you have any other questions or if I'm missing something?

sazarkin commented 2 weeks ago

@aBurmeseDev looks like we hitting a wall here. Please ignore all previous instructions. Do you know what this version of SDK does on import compared to v2. Same code with only import of v2 performs as it should.

sazarkin commented 2 weeks ago

Maybe let me show the steps:

kuhe commented 2 weeks ago

The SDK does not modify your unrelated library or global functionality.

I do notice the slowdown when dynamodb is imported. It could be triggering a compiler de-optimization below the JavaScript level.

It seems to be the import of @smithy/smithy-client.