Skip to main content
0
Back-End DeveloperDevInit

การทำ Caching ด้วย Redis บน Node.js

สวัสดีครับ วันนี้เราจะมาดูการทำ caching ด้วย Redis กัน โดยจะมีหัวข้อที่จะเรียนรู้กัน ดังนี้

  • Caching คืออะไร
  • ทำไมต้องทำ Caching
  • Redis คืออะไร
  • ตัวอย่างการทำ caching ด้วย Redis

Caching คืออะไร ?

Caching เป็นรูปแบบการจัดเก็บข้อมูลรูปแบบหนึ่งแต่เป็นการเก็บข้อมูลชั่วคราวเท่านั้นซึ่งมีความรวดเร็วในการอ่านข้อมูลกว่าฐานข้อมูลแบบอื่นที่บันทึกข้อมูลลงใน Disk

ทำไมต้องทำ Caching ด้วยหละ?

เรามาพูดถึงการจัดเก็บและเรียกดูข้อมูลในการพัฒนาโปรแกรมกรณีที่ไม่มีการทำ caching กันก่อน

การสื่อสารระหว่าง Application server กับ Database server

จากรูปหากมีระบบ ระบบหนึ่งยกตัวอย่าง เช่น ระบบซื้อขายสินค้า เมื่อผู้ใช้ เข้าไปที่หน้ารายการของสินค้าเพื่อต้องการดูสินค้าที่มีวางจำหน่ายอยู่นั้น Application server เองจะต้องทำการ Query ข้อมูลเพื่อดึงรายการสินค้าทั้งหมดที่มีมาแสดงที่หน้าจอของผู้ใช้ หากมีผู้ใช้ขอดู 10 คน ก็ต้อง query 10 รอบ 100 คน ก็ 100 รอบ แล้วถ้า 10,000 คน 100,000 คน ล่ะ พอจะนึกภาพกันออกแล้วใช่มั้ยล่ะครับ ว่าการอ่านข้อมูลจากฐานข้อมูลแบบนี้นั้นนอกจากจะใช้เวลานานแล้ว ยังเปลือง connection ของฐานข้อมูลเองอีกด้วย และถ้าการอ่านข้อมูลนั้นมี query ที่ซับซ้อนก็ยิ่งทำให้ระยะเวลาในการ query ข้อมูลนั้นยิ่งจะนานมากขึ้น ทำให้เกิดปัญหาคอขวดที่ Database server ได้

จากปัญหาข้างต้นจึงทำให้เกิดการทำ caching ขึ้นเพื่อลดปัญหาปริมาณการเชื่อมต่อกับฐานข้อมูลและระยะเวลาในการอ่านข้อมูลจากฐานข้อมูล

การสื่อสารข้อมูลระหว่าง Application server Redis server และ Database server

ในรูปข้างต้นหากเราทำ caching เพื่อแก้ปัญหาระยะเวลาในการอ่านข้อมูลจาก database server เราจะทำการอ่านข้อมูลจาก cache ก่อน หากมีข้อมูลใน cache เราก็จะไม่ต้องไปอ่านข้อมูลจากฐานข้อมูล แต่หากไม่มีใน cache แล้วถึงค่อยไปอ่านใน database server แล้วมาเก็บข้อมูลไว้ใน cache เพื่อในครั้งถัดไปเมื่ออ่านข้อมูลใน cache ก็สามารถดึงข้อมูลไปใช้ได้เลยไม่ต้องไปอ่านข้อมูลใน database server ทุกครั้ง ซึ่งการเก็บข้อมูลใน cache ใช้เวลาน้อยกว่าการอ่านข้อมูลใน database server ที่เก็บข้อมูลใน disk เนี่ย มากๆๆๆๆๆ ทำให้ลดระยะเวลาในการอ่านข้อมูลและการคอขวดที่ database server ได้

Redis คืออะไร ?

Redis คือฐานข้อมูลยี่ห้อหนึ่งที่มีการเก็บข้อมูลในรูปแบบของ in-memory หรือหน่วยความจำที่เป็น RAM นั่นเอง รองรับข้อมูลหลายประเภท เช่น

  • Strings
  • Hashes
  • Lists
  • Sets
  • Sorted Sets
  • JSON

โดยนักพัฒนานิยมนำ Redis มาทำเป็น Cache Database, Document Database, Vector Database, Streaming Engine และ Message Broker Engine โดยบทความนี้เราจะพูดถึง Cache Database กันนะครับ

ลงมือทำกันดีกว่า

เอาหล่ะครับหลังจากที่เราได้รู้จักความหมายต่างๆ กันมาแล้ว เราลองมาลงมือทำมันกันดีกว่าครับ

เริ่มจากสร้างโปรเจ็คด้วย node.js กันดีกว่า สำหรับใครที่ยังไม่ได้ติดตั้ง node.js ต้องทำการติดตั้ง node.js เสียก่อน โดยคลิกที่นี่ หากใครติดตั้งเรียบร้อยแล้วก็ไปลุยกันเลยยยย

  1. สร้างโฟลเดอร์สำหรับโปรเจคขึ้นมาแล้วใช้คำสั่ง
npm init -y
เริ่มต้นโปรเจคด้วยคำสั่ง npm i -y

2. ติดตั้ง redis package ด้วยคำสั่ง

npm i redis
ติดตั้ง redis ด้วยคำสั่ง npm i redis

เมื่อทำ 2 ขั้นตอนเสร็จสิ้นแล้วเราจะมีโฟลเดอร์ node_modules ในโปรเจค และในไฟล์ package.json จะมี package redis อยู่ใน dependencies

3. สร้างฐานข้อมูล Redis กันนนน

หากใครไม่อยากติดตั้ง redis server เอง ทาง Redis ก็มี cloud ให้เราได้ลองเข้าไปเล่นกันได้กับ Redis Cloud

เมื่อลงทะเบียนเสร็จแล้ว ก็สามารถสร้างฐานข้อมูลได้เลย หากสร้างเรียบร้อยแล้วจะได้ตามรูปด้านล่าง

Redis Database on cloud

เมื่อสร้างฐานข้อมูลเสร็จเรียบร้อยแล้วเราจะได้ public endpoint มา เพื่อที่ต้องนำ endpoint นี้ไปเขียนโค้ดเชื่อมต่อระหว่าง client กับ server

หรือหากใครที่ต้องการติดตั้ง Redis Server เองก็สามารถติดตั้งได้ตามลิงค์นี้เลยครับ

เมื่อติดตั้งเสร็จแล้วเราสามารถลองเชื่อมต่อเพื่อดูฐานข้อมูลได้ผ่าน Redis Insight ใครยังไม่ได้ติดตั้งไปติดตั้งกันเลยยย~~

ใครที่ติดตั้งเรียบร้อยแล้วก็สามารถเข้าไปที่ Redis Insight เพื่อเชื่อมต่อฐานข้อมูลได้เลยครับ เมื่อเข้ามาแล้วให้เลือก Add connection details manually ตามตัวอย่างด้านล่าง

จากนั้นให้เราใส่รายละเอียด Connection ที่เราต้องการจะเชื่อมต่อ เมื่อใส่รายละเอียดครบถ้วนแล้วให้เลือก Add Redis Database ตามตัวอย่างด้านล่าง

เมื่อเชื่อมต่อกับฐานข้อมูลแล้วจะได้ตามรูปภาพตัวอย่างด้านล่างครับ

ลำดับถัดไปเราลองเชื่อมต่อ Redis ผ่าน Node.js เพื่ออ่าน/เขียนข้อมูลกันดีกว่า

4. เชื่อมต่อ Redis ผ่าน Node.js

const redis = require("redis");
async function initRedis() {
  const client = redis.createClient({
    url: "redis://127.0.0.1:8889",
  });
  client.on("error", (err) => console.error("Redis Client Error", err));
  await client.connect();
}

setTimeout(async () => {
  await initRedis();
},1000);
การทำงานของโค้ด

จากโค้ดข้างต้นบรรทัดแรกนั้น เป็นการ import redis package เข้ามาในโปรเจค
สร้าง redis client ด้วย method createClient()
โดยนำ Public endpoint ที่ได้จากขั้นตอนที่ 3 หรือ Connection ที่ติดตั้ง Redis Server มาใส่ใน url
จากตัวอย่าง const client = redis.createClient({url: “redis://127.0.0.1:8889”,});
จากนั้นเชื่อมต่อฐานข้อมูลด้วย method connect จากตัวอย่าง client.connect()

5. เขียน/อ่าน ฐานข้อมูล

const redis = require("redis");

async function initRedis() {
  const client = redis.createClient({
    url: "redis://127.0.0.1:8889",
  });
  client.on("error", (err) => console.error("Redis Client Error", err));
  await client.connect();
  await client.set("key", "value");
    const value = await client.get("key");
    
   await client.hSet("user-session:123456", {
     name: "John",
     surname: "Smith",
     company: "Redis",
     age: 29,
   });

  let userSession = await client.hGetAll("user-session:123456");
  console.log(JSON.stringify(userSession, null, 2));
}

setTimeout(async () => {
  await initRedis();
});

อย่างที่ทราบกันดีว่า redis จัดเก็บข้อมูลในรูปแบบของ key-value โดยในตัวอย่างเราจะอ่านและเขียนข้อมูลจาก redis ด้วยวิธีการ 2 แบบ คือ 1. กรณีที่ค่ามีประเภทของข้อมูลเป็น String 2. กรณีที่ข้อมูลมีประเภทข้อมูลเป็น Hash ในกรณีแรกนั้นทำการอ่านค่าด้วย get method ส่วนในกรณีที่สองนั้นอ่านค่าด้วย hget method

จากโค้ดเราจะเห็นได้ว่าสามารถ set ค่าข้อมูลเข้าไปใน redis ได้ 2 แบบ คือ 1. ข้อความ และ 2. Hash

โดยหากค่าที่ต้องการเขียนลงฐานข้อมูลมีประเภทของข้อมูลเป็นข้อความให้ใช้ set method แต่หากเป็น Hash ให้ใช้ hset method ตามโค้ดตัวอย่าง

เมื่อเราทำการรันโค้ดแล้วลองกลับไปดูใน Redis Insight เราจะพบว่ามีข้อมูลเข้ามาในฐานข้อมูลแล้วตามรูปภาพตัวอย่างด้านล่าง

ใน Redis Insight จะมี key 2 key เพิ่มเข้ามานั่นก็คือ 1. key 2.user-sessions:123456

หากต้องการดูค่าที่อยู่ใน key นั้น สามารถเลือก key ที่ต้องการดูจากฝั่งซ้าย และค่าที่เก็บไว้ใน key จะแสดงในฝั่งขวาตามตัวอย่างด้านล่าง

ตัวอย่างเลือกดูค่าของ key
ตัวอย่างเลือกค่าของ user-sessions:123456

ตัวอย่างการทำ Caching ด้วย Redis บน Node.js

require("dotenv").config();
const express = require("express");
const app = express();
const port = 32000;
const redis = require("redis");
const mongoose = require("mongoose");
const mongoUser = encodeURIComponent(process.env.MONGO_DB_USER);
const mongoPassword = encodeURIComponent(process.env.MONGO_DB_PASSWORD);
const mongoDb = process.env.MONGO_DB_URL;
const mongoUrl = `mongodb+srv://${mongoUser}:${mongoPassword}@${mongoDb}`;
mongoose
  .connect(mongoUrl, {
    useNewUrlParser: true,
    useUnifiedTopology: true,
  })
  .then((res) => {
    console.log("Connect db success.");
  })
  .catch((err) => {
    console.error(err);
  });

app.use((req, res, next) => {
  console.log(`${req.method} request for ${req.url}`);
  next();
});
app.use(express.json());

const userProfileSchema = new mongoose.Schema({
  username: String,
  name: String,
  lastname: String,
  dob: Date,
  email: String,
});
const UserProfile = mongoose.model("UserProfile", userProfileSchema);

const client = redis.createClient({
  url: "redis://127.0.0.1:8889",
});

client.on("error", (err) => console.error("Redis Client Error", err));
client
  .connect()
  .then((res) => {
    console.log("Connect Redis Success.");
  })
  .catch((err) => {
    console.error(err);
  });

app.post("/userProfile", (req, res) => {
  const newProfile = new UserProfile(req.body);
  newProfile
    .save()
    .then((data) => {
      return res.status(201).send(data);
    })
    .catch((err) => {
      return res.status(500).send(err);
    });
});

app.get("/userProfiles", (req, res) => {
  UserProfile.find()
    .then((data) => res.status(200).send(data))
    .catch((err) => res.status(500).send(err));
});

app.get("/userProfile/:username", (req, res) => {
  const username = req.params.username;
  client
    .hGetAll(`username:${username}`)
    .then((data) => {
      console.log("user profile from cache data");
      console.log(JSON.stringify(data, null, 2));
      const profile = JSON.parse(JSON.stringify(data));
      const hasCacheData = Object.keys(profile).length != 0;
      if (!hasCacheData) {
        console.log("get data from mongo db");
        UserProfile.findOne({ username })
          .then((data) => {
            console.log("store cache data to redis");
            console.log(data);
            client
              .hSet(`username:${username}`, JSON.parse(JSON.stringify(data)))
              .then((_data) => {
                console.log("store cache data success");
                console.log(_data);
                return res.status(200).send(data);
              })
              .catch((err) => {
                console.error(err);
              });
          })
          .catch((err) => res.status(500).send(err));
      } else {
        return res.status(200).send(profile);
      }
    })
    .catch((err) => {
      console.error(err);
    });
});

app.listen(port, () => {
  console.log(`Server running at <http://localhost>:${port}/`);
});

จากโค้ดข้างต้นเป็นการแสดงตัวอย่างการทำ caching โดยเป็นการยกตัวอย่างกรณีเรียกดูข้อมูล user profile ของ API GET:/userProfile/:usernameโดยมีการ Query ข้อมูล จาก redis ที่มี key เป็น “username:<username>” หากไม่เจอข้อมูลใน Redis ก็ให้ไปดึงข้อมูลจาก MongoDB เมื่อได้ข้อมูลจาก MongoDB แล้ว ก็ให้บันทึกข้อมูลที่ได้ลงใน Redis เมื่อมีการค้นหาข้อมูลในครั้งถัดไปก็สามรถดึงข้อมูลจาก Redis ไปใช้ได้เลยโดยที่ไม่ต้องไปดึงข้อมูลจาก MongoDB อีก ซึ่งเมื่อเปิดดูข้อมูลใน Redis Insight ก็จะได้ข้อมูลตามรูปภาพด้านล่างซึ่งเป็นข้อมูลที่ได้จาก MongoDB แล้วบันทึกไว้ใน Redis

รูปภาพแสดง Cache Data

เป็นไงกันบ้างครับสำหรับการทำ caching ด้วย redis ไม่ยากเลยใช่มั้ยล่ะครับ หากเพื่อนๆ คนไหน สนใจที่จะศึกษาเกี่ยวกับ Redis เพิ่มเติมสามารถเข้าไปศึกษาได้ตาม guide นี้เลยยย

แหล่งอ้างอิง

https://www.somkiat.cc/basic-of-caching

https://redis.io/docs/latest/get-started

https://redis.io/docs/latest/develop/connect/clients/nodejs

อนุชิต รักษาพล

Author อนุชิต รักษาพล

Software Developer

More posts by อนุชิต รักษาพล

เราใช้คุกกี้เพื่อพัฒนาประสิทธิภาพ และประสบการณ์ที่ดีในการใช้เว็บไซต์ของคุณ คุณสามารถศึกษารายละเอียดได้ที่ นโยบายความเป็นส่วนตัว และสามารถจัดการความเป็นส่วนตัวเองได้ของคุณได้เองโดยคลิกที่ ตั้งค่า

ตั้งค่าความเป็นส่วนตัว

คุณสามารถเลือกการตั้งค่าคุกกี้โดยเปิด/ปิด คุกกี้ในแต่ละประเภทได้ตามความต้องการ ยกเว้น คุกกี้ที่จำเป็น

ยอมรับทั้งหมด
จัดการความเป็นส่วนตัว
  • คุกกี้ที่จำเป็น
    เปิดใช้งานตลอด

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

  • คุกกี้สำหรับการติดตามทางการตลาด

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

บันทึกการตั้งค่า