Computer >> คอมพิวเตอร์ >  >> การเขียนโปรแกรม >> Redis

การเปรียบเทียบเวลาแฝงระหว่างฐานข้อมูลแบบไร้เซิร์ฟเวอร์:DynamoDB กับ FaunaDB เทียบกับ Upstash

ในบทความนี้ ฉันจะเปรียบเทียบเวลาแฝงของฐานข้อมูลไร้เซิร์ฟเวอร์สามตัว DynamoDB, FaunaDB, Upstash (Redis) สำหรับกรณีการใช้งานเว็บทั่วไป

ฉันสร้างเว็บไซต์ข่าวตัวอย่างและกำลังบันทึกเวลาแฝงที่เกี่ยวข้องกับฐานข้อมูลกับคำขอแต่ละรายการที่ส่งไปยังเว็บไซต์ ตรวจสอบเว็บไซต์และซอร์สโค้ด

ฉันได้แทรกบทความ 7001 NY Times ในแต่ละฐานข้อมูล บทความรวบรวมจาก New York Times Archive API (บทความทั้งหมดของเดือนมกราคม 2021) ฉันสุ่มคะแนนแต่ละบทความ ในแต่ละคำขอของหน้า ฉันค้นหาบทความ 10 อันดับแรกภายใต้ World จากแต่ละฐานข้อมูล

ฉันใช้ฟังก์ชันไร้เซิร์ฟเวอร์ (AWS Lambda) เพื่อโหลดบทความจากแต่ละฐานข้อมูล เวลาตอบสนองของการดึงบทความ 10 บทความจะถูกบันทึกเป็นเวลาแฝงภายในฟังก์ชันแลมบ์ดา โปรดทราบว่าเวลาแฝงที่บันทึกไว้เป็นเพียงระหว่างฟังก์ชันแลมบ์ดากับฐานข้อมูล ไม่ใช่เวลาแฝงระหว่างเบราว์เซอร์และเซิร์ฟเวอร์ของคุณ

หลังจากคำขออ่านแต่ละครั้ง ฉันจะอัปเดตคะแนนแบบสุ่มเพื่อจำลองข้อมูลแบบไดนามิก แต่ฉันแยกส่วนนี้ออกจากการคำนวณเวลาแฝง

ขั้นแรก เราจะตรวจสอบการใช้งาน จากนั้นเราจะดูผลลัพธ์:

การตั้งค่า AWS Lambda

ภูมิภาค:US-West-1

หน่วยความจำ:1024Mb

รันไทม์:nodejs14.x

การตั้งค่า DynamoDB

ฉันสร้างตาราง DynamoDB ใน US-West-1 ด้วยความสามารถในการอ่านและเขียน 50 (ค่าเริ่มต้นคือ 5)

ดัชนีของฉันคือ GSI พร้อมพาร์ทิชันคีย์ section (String) และคีย์การจัดเรียง view_count (Number) .

การเปรียบเทียบเวลาแฝงระหว่างฐานข้อมูลแบบไร้เซิร์ฟเวอร์:DynamoDB กับ FaunaDB เทียบกับ Upstash

การตั้งค่า FaunaDB

FaunaDB เป็นฐานข้อมูลที่จำลองแบบทั่วโลก ไม่มีทางที่จะเลือกภูมิภาคได้ ฉันใช้ FQL โดยสมมติว่า GraphQL API อาจมีค่าใช้จ่าย

ฉันสร้างดัชนีด้วยส่วนเงื่อนไขและค่าอ้างอิงดังต่อไปนี้ ฉันทำให้มันไม่มีซีเรียลไลซ์โดยหวังว่าจะปรับปรุงประสิทธิภาพ

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"] }

]

})

ตั้งค่า Redis

ฉันสร้างฐานข้อมูลประเภทมาตรฐานในภูมิภาค US-West-1 ใน Upstash ฉันใช้ Sorted Set สำหรับแต่ละหมวดข่าว ดังนั้น World บทความข่าวจะอยู่ใน Sorted Set with key World .

เริ่มต้นฐานข้อมูล

ฉันดาวน์โหลดบทความข่าว 7001 เป็นไฟล์ JSON จากไซต์ NYTimes API จากนั้นจึงสร้างสคริปต์ NodeJS สำหรับแต่ละฐานข้อมูลที่อ่าน JSON และแทรกบันทึกข่าวลงในฐานข้อมูล ดูไฟล์: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);
      });
    }
  });
};

แบบสอบถาม FaunaDB

ฉันใช้ faunadb ห้องสมุดเพื่อเชื่อมต่อและสอบถาม FaunaDB ส่วนที่เหลือคล้ายกับโค้ด DynamoDB มาก เพื่อลดเวลาในการตอบสนอง ฉันกำลังรักษาการเชื่อมต่อ ฉันใช้ perf_hooks ห้องสมุดเพื่อวัดเวลาตอบสนอง ฉันบันทึกเวลาปัจจุบันก่อนที่จะสอบถาม FaunaDB สำหรับบทความ 10 อันดับแรก ฉันคำนวณเวลาแฝงทันทีที่ได้รับคำตอบจาก 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,
      },
    }),
  };
};

การสืบค้นซ้ำ

ฉันใช้ ioredis ห้องสมุดเพื่อเชื่อมต่อและอ่านจาก Redis ใน Upstash ฉันใช้คำสั่ง 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 ห้องสมุดเพื่อคำนวณฮิสโตแกรม นี่คือการใช้งาน js ของไลบรารี hdr-histogram ของ Gil Tene ดูโค้ดของฟังก์ชันแลมบ์ดาซึ่งรับตัวเลขแฝงและคำนวณฮิสโตแกรม

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
    ),
  };
};

ผลลัพธ์

ตรวจสอบเว็บไซต์สำหรับผลลัพธ์ล่าสุด คุณยังสามารถเข้าถึงข้อมูลฮิสโตแกรมล่าสุดได้ ตราบใดที่เว็บไซต์ยังใช้งานได้ เราจะรวบรวมข้อมูลและอัปเดตฮิสโตแกรมต่อไป ผลลัพธ์ ณ วันนี้ (12 เมษายน 2021) แสดงให้เห็นว่า Upstash มีเวลาแฝงต่ำสุด (~50ms ที่เปอร์เซ็นไทล์ที่ 99) โดยที่ FaunaDB มีเวลาแฝงสูงสุด (~900ms ที่เปอร์เซ็นไทล์ที่ 99) DynamoDB มี (~200ms ที่เปอร์เซ็นไทล์ที่ 99)

การเปรียบเทียบเวลาแฝงระหว่างฐานข้อมูลแบบไร้เซิร์ฟเวอร์:DynamoDB กับ FaunaDB เทียบกับ Upstash

เอฟเฟกต์เริ่มเย็น

แม้ว่าเราจะวัดเวลาแฝงสำหรับส่วนการสืบค้นเท่านั้น แต่การเริ่มเย็นก็ยังมีผล เราปรับโค้ดของเราให้เหมาะสมโดยใช้การเชื่อมต่อไคลเอ็นต์ซ้ำ เราได้รับประโยชน์จากสิ่งนี้ตราบใดที่คอนเทนเนอร์แลมบ์ดายังร้อนและทำงานอยู่ เมื่อ AWS ฆ่าคอนเทนเนอร์ (cold start) โค้ดจะสร้างไคลเอนต์ขึ้นมาใหม่ นี่เป็นค่าใช้จ่าย ในเว็บไซต์แอปพลิเคชัน หากคุณรีเฟรชหน้า คุณจะเห็นตัวเลขแฝงลดลงเหลือ ~1ms สำหรับ Upstash; และ ~7ms สำหรับ DynamoDB

เหตุใด FaunaDB จึงช้า (ในเกณฑ์มาตรฐานนี้)

ในหน้าสถานะของ FaunaDB คุณจะเห็นตัวเลขแฝงเป็นร้อย ดังนั้นฉันคิดว่าการกำหนดค่าของฉันไม่มีข้อบกพร่องใหญ่ อาจมีสาเหตุสองประการที่อยู่เบื้องหลังความแตกต่างของเวลาในการตอบสนองนี้:

ความสม่ำเสมอที่แข็งแกร่ง: โดยค่าเริ่มต้น ทั้ง DynamoDB และ Upstash จะมีความสอดคล้องกันในที่สุดสำหรับการอ่าน FaunaDB ให้ความสม่ำเสมอและการแยกตัวที่แข็งแกร่งตาม Calvin ความสม่ำเสมอที่แข็งแกร่งมาพร้อมกับค่าใช้จ่ายด้านประสิทธิภาพ

การจำลองแบบทั่วโลก: สำหรับทั้ง Upstash และ DynamoDB เราสามารถกำหนดค่าฐานข้อมูลและฟังก์ชันแลมบ์ดาให้อยู่ในภูมิภาค AWS เดียวกันได้ ใน FaunaDB ข้อมูลของคุณจะถูกจำลองไปทั่วโลก ดังนั้นคุณจึงไม่มีตัวเลือกในการเลือกภูมิภาคของคุณ หากไคลเอนต์ฐานข้อมูลของคุณตั้งอยู่ทั่วโลก สิ่งนี้อาจเป็นข้อได้เปรียบ แต่หากคุณปรับใช้แบ็กเอนด์กับภูมิภาคใดภูมิภาคหนึ่ง จะทำให้เวลาในการตอบสนองเพิ่มขึ้น

Redis ให้เวลาแฝงในหน่วยมิลลิวินาที ทำไมมันไม่เป็นแบบนี้

การสร้างการเชื่อมต่อ Redis ใหม่ในฟังก์ชัน AWS Lambda ทำให้เกิดโอเวอร์เฮดที่โดดเด่น เนื่องจากแอปพลิเคชันไม่ได้รับการรับส่งข้อมูลที่เสถียร AWS Lambda จะสร้างการเชื่อมต่อขึ้นใหม่ (cold start) เกือบตลอดเวลา ดังนั้นตัวเลขแฝงส่วนใหญ่ในฮิสโตแกรมจึงรวมเวลาในการสร้างการเชื่อมต่อด้วย เราเรียกใช้งานที่ดึงข้อมูลเว็บไซต์ทุกๆ 15 วินาที; เราเห็นว่าเวลาแฝงสำหรับ Upstash ลดลงเหลือ ~1ms หากคุณรีเฟรชหน้า คุณจะเห็นเอฟเฟกต์ที่คล้ายกัน ดูบล็อกโพสต์ของเราสำหรับวิธีเพิ่มประสิทธิภาพแอปพลิเคชันแบบไร้เซิร์ฟเวอร์ของคุณสำหรับเวลาแฝงที่ต่ำ

เร็วๆ นี้

Upstash จะเปิดตัวผลิตภัณฑ์พรีเมียมในไม่ช้าซึ่งข้อมูลจะถูกจำลองไปยังโซนความพร้อมใช้งานหลายแห่ง ฉันจะเพิ่มเพื่อดูผลกระทบของการจำลองโซน

แจ้งให้เราทราบความคิดเห็นของคุณเกี่ยวกับ Twitter หรือ Discord

อัปเดต

มีการอภิปรายอย่างแข็งขันใน HackerNews เกี่ยวกับเกณฑ์มาตรฐานและประสิทธิภาพของ Fauna ของฉัน ฉันใช้คำแนะนำและเริ่มแอปพลิเคชัน FaunaDB ใหม่ ด้วยเหตุนี้ จำนวนระเบียน FaunaDB ในฮิสโตแกรมจึงน้อยกว่าจำนวนอื่นๆ