Computer >> 컴퓨터 >  >> 프로그램 작성 >> Redis

서버리스 데이터베이스 간의 지연 시간 비교:DynamoDB 대 FaunaDB 대 Upstash

이 기사에서는 일반적인 웹 사용 사례에 대해 세 가지 서버리스 데이터베이스 DynamoDB, FaunaDB, Upstash(Redis)의 대기 시간을 비교하겠습니다.

샘플 뉴스 웹사이트를 만들고 웹사이트에 대한 각 요청과 함께 데이터베이스 관련 대기 시간을 기록하고 있습니다. 웹사이트와 소스코드를 확인하세요.

7001개의 NY Times 기사를 각 데이터베이스에 삽입했습니다. 기사는 New York Times Archive API(2021년 1월의 모든 기사)에서 수집되었습니다. 나는 무작위로 각 기사에 점수를 매겼습니다. 각 페이지 요청에서 World 아래의 상위 10개 기사를 쿼리합니다. 각 데이터베이스의 섹션.

저는 서버리스 함수(AWS Lambda)를 사용하여 각 데이터베이스에서 기사를 로드합니다. 10개의 기사를 가져오는 응답 시간은 람다 함수 내부에 대기 시간으로 기록됩니다. 기록된 대기 시간은 람다 함수와 데이터베이스 사이에만 있습니다. 브라우저와 서버 간의 대기 시간이 아닙니다.

각 읽기 요청 후에 점수를 무작위로 업데이트하여 동적 데이터를 시뮬레이션합니다. 하지만 이 부분은 지연 계산에서 제외합니다.

먼저 신청서를 검토한 다음 결과를 살펴보겠습니다.

AWS 람다 설정

지역:US-West-1

메모리:1024Mb

런타임:nodejs14.x

DynamoDB 설정

US-West-1에서 읽기 및 쓰기 용량이 50(기본값은 5)인 DynamoDB 테이블을 생성했습니다.

내 색인은 파티션 키가 section (String)인 GSI입니다. 및 정렬 키 view_count (Number) .

서버리스 데이터베이스 간의 지연 시간 비교:DynamoDB 대 FaunaDB 대 Upstash

FaunaDB 설정

FaunaDB는 전역적으로 복제되는 데이터베이스이므로 지역을 선택할 수 있는 방법이 없습니다. GraphQL API에 약간의 오버헤드가 있을 수 있다고 가정하고 FQL을 사용했습니다.

아래와 같이 용어 섹션과 값 ref로 인덱스를 만들었습니다. 성능 향상을 위해 무직렬화했습니다.

CreateIndex({

name: "section_by_view_count",

unique: false,

serialized: false,

source: Collection("news"),

terms: [

{ field: ["data", "section"] }

],

values: [

{ field: ["data", "view_count"], reverse: true },

{ field: ["ref"] }

]

})

레디스 설정

Upstash의 US-West-1 지역에 표준형 데이터베이스를 만들었습니다. 각 뉴스 카테고리별로 Sorted Set을 사용했습니다. 따라서 모든 World 뉴스 기사는 World 키가 있는 Sorted Set에 있습니다. .

데이터베이스 초기화

NYTimes API 사이트에서 7001 뉴스 기사를 JSON 파일로 다운로드한 다음 JSON을 읽고 데이터베이스에 뉴스 레코드를 삽입하는 각 데이터베이스마다 NodeJS 스크립트를 생성했습니다. 파일 보기:initDynamo.js, initFauna.js, initRedis.js

DynamoDB 쿼리

AWS SDK를 사용하여 DynamoDB에 연결했습니다. 지연 시간을 최소화하기 위해 DynamoDB 연결을 활성 상태로 유지하고 있습니다. perf_hooks를 사용했습니다. 응답 시간을 측정하는 라이브러리. DynamoDB에 상위 10개 기사를 쿼리하기 직전의 현재 시간을 기록합니다. DynamoDB에서 응답을 받자마자 지연 시간을 계산했습니다. 그런 다음 기사에 무작위로 점수를 매기고 대기 시간 수를 Redis 정렬 세트에 삽입하지만 이러한 부분은 대기 시간 계산 부분 밖에 있습니다. 아래 코드 참조:

var AWS = require("aws-sdk");
AWS.config.update({
  region: "us-west-1",
});
const https = require("https");
const agent = new https.Agent({
  keepAlive: true,
  maxSockets: Infinity,
});

AWS.config.update({
  httpOptions: {
    agent,
  },
});

const Redis = require("ioredis");
const { performance } = require("perf_hooks");
const tableName = "news";
var params = {
  TableName: tableName,
  IndexName: "section-view_count-index",
  KeyConditionExpression: "#sect = :section",
  ExpressionAttributeNames: {
    "#sect": "section",
  },
  ExpressionAttributeValues: {
    ":section": process.env.SECTION,
  },
  Limit: 10,
  ScanIndexForward: false,
};
const docClient = new AWS.DynamoDB.DocumentClient();

module.exports.load = (event, context, callback) => {
  let start = performance.now();
  docClient.query(params, (err, result) => {
    if (err) {
      console.error(
        "Unable to scan the table. Error JSON:",
        JSON.stringify(err, null, 2)
      );
    } else {
      // response is ready so we can set the latency
      let latency = performance.now() - start;
      let response = {
        statusCode: 200,
        headers: {
          "Access-Control-Allow-Origin": "*",
          "Access-Control-Allow-Credentials": true,
        },
        body: JSON.stringify({
          latency: latency,
          data: result,
        }),
      };
      // we are setting random score to top-10 items to simulate real time dynamic data
      result.Items.forEach((item) => {
        let view_count = Math.floor(Math.random() * 1000);
        var params2 = {
          TableName: tableName,
          Key: {
            id: item.id,
          },
          UpdateExpression: "set view_count = :r",
          ExpressionAttributeValues: {
            ":r": view_count,
          },
        };
        docClient.update(params2, function (err, data) {
          if (err) {
            console.error(
              "Unable to update item. Error JSON:",
              JSON.stringify(err, null, 2)
            );
          }
        });
      });
      // pushing the latency to the histogram
      const client = new Redis(process.env.LATENCY_REDIS_URL);
      client.lpush("histogram-dynamo", latency, (resp) => {
        client.quit();
        callback(null, response);
      });
    }
  });
};

FunaDB 쿼리

faunadb를 사용했습니다. FaunaDB를 연결하고 쿼리하는 라이브러리입니다. 나머지 부분은 DynamoDB 코드와 매우 유사합니다. 대기 시간을 최소화하기 위해 연결을 유지하고 있습니다. perf_hooks를 사용했습니다. 응답 시간을 측정하는 라이브러리. 상위 10개 기사에 대해 FaunaDB에 쿼리하기 직전의 현재 시간을 기록합니다. FaunaDB에서 응답을 받자마자 지연 시간을 계산했습니다. 그런 다음 기사에 무작위로 점수를 매기고 대기 시간 수를 Redis 정렬 세트로 보내지만 이러한 부분은 대기 시간 계산 부분 밖에 있습니다. 아래 코드 참조:

const faunadb = require("faunadb");
const Redis = require("ioredis");
const { performance } = require("perf_hooks");
const q = faunadb.query;
const client = new faunadb.Client({
  secret: process.env.FAUNA_SECRET,
  keepAlive: true,
});
const section = process.env.SECTION;

module.exports.load = async (event) => {
  let start = performance.now();
  let ret = await client
    .query(
      // the below is Fauna API for "select from news where section = 'world' order by view_count limit 10"
      q.Map(
        q.Paginate(q.Match(q.Index("section_by_view_count"), section), {
          size: 10,
        }),
        q.Lambda(["view_count", "X"], q.Get(q.Var("X")))
      )
    )
    .catch((err) => console.error("Error: %s", err));
  console.log(ret);
  // response is ready so we can set the latency
  let latency = performance.now() - start;
  const rclient = new Redis(process.env.LATENCY_REDIS_URL);
  await rclient.lpush("histogram-fauna", latency);
  await rclient.quit();

  let result = [];
  for (let i = 0; i < ret.data.length; i++) {
    result.push(ret.data[i].data);
  }

  // we are setting random scores to top-10 items asynchronously to simulate real time dynamic data
  ret.data.forEach((item) => {
    let view_count = Math.floor(Math.random() * 1000);
    client
      .query(
        q.Update(q.Ref(q.Collection("news"), item["ref"].id), {
          data: { view_count },
        })
      )
      .catch((err) => console.error("Error: %s", err));
  });

  return {
    statusCode: 200,
    headers: {
      "Access-Control-Allow-Origin": "*",
      "Access-Control-Allow-Credentials": true,
    },
    body: JSON.stringify({
      latency: latency,
      data: {
        Items: result,
      },
    }),
  };
};

쿼리 Redis

ioredis를 사용했습니다. Upstash의 Redis에서 연결하고 읽을 수 있는 라이브러리입니다. ZREVRANGE 명령을 사용하여 Sorted Set에서 데이터를 로드했습니다. 대기 시간을 최소화하기 위해 함수 외부에서 Redis 클라이언트를 생성하는 연결을 재사용했습니다. DynamoDB 및 FaunaDB와 마찬가지로 히스토그램 계산을 위해 점수를 업데이트하고 대기 시간을 다른 Redis DB로 보냅니다. 코드 보기:

const Redis = require("ioredis");
const { performance } = require("perf_hooks");
const client = new Redis(process.env.REDIS_URL);
module.exports.load = async (event) => {
  let section = process.env.SECTION;
  let start = performance.now();
  let data = await client.zrevrange(section, 0, 9);
  let items = [];
  for (let i = 0; i < data.length; i++) {
    items.push(JSON.parse(data[i]));
  }
  // response is ready so we can set the latency
  let latency = performance.now() - start;
  // we are setting random scores to top-10 items to simulate real time dynamic data
  for (let i = 0; i < data.length; i++) {
    let view_count = Math.floor(Math.random() * 1000);
    await client.zadd(section, view_count, data[i]);
  }
  // await client.quit();
  // pushing the latency to the histogram
  const client2 = new Redis(process.env.LATENCY_REDIS_URL);
  await client2.lpush("histogram-redis", latency);
  await client2.quit();
  return {
    statusCode: 200,
    headers: {
      "Access-Control-Allow-Origin": "*",
      "Access-Control-Allow-Credentials": true,
    },
    body: JSON.stringify({
      latency: latency,
      data: {
        Items: items,
      },
    }),
  };
};

히스토그램 계산

hdr-histogram-js를 사용했습니다. 히스토그램을 계산하는 라이브러리. 이것은 Gil Tene의 hdr-히스토그램 라이브러리의 js 구현입니다. 대기 시간을 수신하고 히스토그램을 계산하는 람다 함수의 코드를 참조하십시오.

const Redis = require("ioredis");
const hdr = require("hdr-histogram-js");

module.exports.load = async (event) => {
  const client = new Redis(process.env.LATENCY_REDIS_URL);
  let dataRedis = await client.lrange("histogram-redis", 0, 10000);
  let dataDynamo = await client.lrange("histogram-dynamo", 0, 10000);
  let dataFauna = await client.lrange("histogram-fauna", 0, 10000);
  const hredis = hdr.build();
  const hdynamo = hdr.build();
  const hfauna = hdr.build();
  dataRedis.forEach((item) => {
    hredis.recordValue(item);
  });
  dataDynamo.forEach((item) => {
    hdynamo.recordValue(item);
  });
  dataFauna.forEach((item) => {
    hfauna.recordValue(item);
  });
  await client.quit();
  return {
    statusCode: 200,
    headers: {
      "Access-Control-Allow-Origin": "*",
      "Access-Control-Allow-Credentials": true,
    },
    body: JSON.stringify(
      {
        redis_min: hredis.minNonZeroValue,
        dynamo_min: hdynamo.minNonZeroValue,
        fauna_min: hfauna.minNonZeroValue,
        redis_mean: hredis.mean,
        dynamo_mean: hdynamo.mean,
        fauna_mean: hfauna.mean,
        redis_histogram: hredis,
        dynamo_histogram: hdynamo,
        fauna_histogram: hfauna,
      },
      null,
      2
    ),
  };
};

결과

웹사이트에서 최신 결과를 확인하세요. 최신 히스토그램 데이터에 접근할 수도 있습니다. 웹사이트가 가동되는 한 계속해서 데이터를 수집하고 히스토그램을 업데이트할 것입니다. 오늘(2021년 4월 12일) 결과에 따르면 Upstash의 대기 시간이 가장 낮고(99번째 백분위수에서 ~50ms) FaunaDB가 가장 높은 대기 시간(99번째 백분위수에서 ~900ms)이 있습니다. DynamoDB는 (99번째 백분위수에서 ~200ms)

서버리스 데이터베이스 간의 지연 시간 비교:DynamoDB 대 FaunaDB 대 Upstash

콜드 스타트 ​​효과

쿼리 부분에 대해서만 대기 시간을 측정하지만 콜드 스타트는 여전히 영향을 미칩니다. 클라이언트 연결을 재사용하여 코드를 최적화합니다. Lambda 컨테이너가 뜨겁고 실행 중인 한 이점이 있습니다. AWS가 컨테이너를 종료하면(콜드 스타트), 코드는 클라이언트를 다시 생성합니다. 이는 오버헤드입니다. 응용 프로그램 웹 사이트에서 페이지를 새로 고치면; Upstash의 경우 대기 시간이 ~1ms로 감소하는 것을 볼 수 있습니다. DynamoDB의 경우 ~7ms입니다.

이 벤치마크에서 FaunaDB가 느린 이유는 무엇입니까?

FaunaDB의 상태 페이지에서 수백 개의 대기 시간을 볼 수 있습니다. 따라서 내 구성에 큰 결함이 없다고 가정합니다. 이 지연 시간 차이에는 두 가지 이유가 있을 수 있습니다.

강력한 일관성: 기본적으로 DynamoDB와 Upstash는 모두 읽기에 대한 최종 일관성을 제공합니다. FaunaDB는 Calvin 기반의 강력한 일관성과 격리를 제공합니다. 강력한 일관성에는 성능 오버헤드가 따릅니다.

전역 복제: Upstash와 DynamoDB 모두 동일한 AWS 리전에 있도록 데이터베이스와 람다 함수를 구성할 수 있습니다. FaunaDB에서는 데이터가 전 세계에 복제됩니다. 따라서 지역을 선택할 수 있는 옵션이 없습니다. 데이터베이스 클라이언트가 전 세계에 있는 경우 이점이 될 수 있습니다. 그러나 백엔드를 특정 지역에 배포하면 추가 지연 시간이 발생합니다.

Redis는 밀리초 미만의 대기 시간을 제공합니다. 여기에서는 그렇지 않은 이유는 무엇입니까?

AWS Lambda 함수에서 새 Redis 연결을 생성하면 상당한 오버헤드가 발생합니다. 애플리케이션이 안정적인 트래픽을 얻지 못하기 때문에 AWS Lambda는 대부분의 경우 연결(콜드 스타트)을 다시 생성합니다. 따라서 히스토그램의 대부분의 대기 시간에는 연결 생성 시간이 포함됩니다. 15초마다 웹사이트를 가져오는 작업을 실행합니다. Upstash의 지연 시간이 ~1ms로 감소한 것을 확인했습니다. 페이지를 새로고침하면 비슷한 효과를 볼 수 있습니다. 짧은 대기 시간을 위해 서버리스 애플리케이션을 최적화하는 방법은 블로그 게시물을 참조하세요.

출시 예정

Upstash는 데이터가 여러 가용 영역에 복제되는 프리미엄 제품을 곧 출시할 예정입니다. 영역 복제의 효과를 보기 위해 추가하겠습니다.

Twitter 또는 Discord에서 피드백을 알려주세요.

업데이트

나의 기준과 Fauna의 성능에 대해 HackerNews에서 활발한 토론이 있었습니다. 제안 사항을 적용하고 FaunaDB 응용 프로그램을 다시 시작했습니다. 그래서 히스토그램의 FaunaDB 레코드 수가 다른 레코드보다 적습니다.