ในบทความวันนี้ เราจะพูดถึงวิธีใช้ประโยชน์จาก Upstash เก็บและเข้าถึงข้อมูล shelter map ของประเทศอย่างปลอดภัยด้วย Redis และอัปเดต database realtime ผ่าน QStash
บทนำ
ในบริบทโลกปัจจุบัน ภัยธรรมชาติและภัยคุกคามทางทหารกลายเป็น prevalent มากขึ้น จึงต้องการ digitalisation ใน social services
ตั้งแต่ emergency broadcast systems เช่น AMBER alerts ไปจนถึง COVID-19 tracking apps และ SOS systems เราเห็นเทคโนโลยีหลากหลายถูกใช้ตอบสนองสถานการณ์อันตรายที่ส่งผลต่อประเทศ
ตอนนี้ เราจะดูว่า Redis และ serverless workloads เล่นบทบาทสำคัญในการสร้าง realtime emergency response system ช่วยพลเมืองหา bunkers ใกล้ที่สุด พร้อมแจ้ง capacity, features (เช่น disabled persons facilities) และ resources (น้ำ, ไฟฟ้า, ยา ฯลฯ) ที่มีอยู่
Courtesy of Project Cloud4
ทำไมถึง Serverless?
Serverless concept ได้รับความนิยมในหมู่นักพัฒนาหลายปี เห็น startup เพิ่มขึ้นเลือก architecture นี้ และ large corporations ก็ adopt มากขึ้น
โดยเฉพาะพูดถึง state actors rapid development environment และ auto-managed services ลด complexity และ overhead ของการสร้าง emergency response systems อย่างมาก เราต้องพิจารณา 2 factors อื่น:
- Budget: ถ้าประเทศไม่ผ่าน emerging disaster คนใน decision-making role จะ approve payments สำหรับ unused compute power ได้ไหม? ผมสงสัย
- Traffic spikes: จินตนาการ alarm triggered ล้านคนเข้าถึง web platform หา shelter ใกล้ที่สุด REST API จะจัดการ sudden rps (request-per-second) increase อย่างไร? Essential realtime components จะเป็นยังไง?
คำถามต่างๆ นี้ตอบได้ด้วย true serverless platforms อย่าง Upstash และ Ably ขับเคลื่อน heavy-lifting servers ที่ auto-scale และคิดค่าใช้จ่ายแค่ resources ที่ใช้
ทำไมถึง Redis?
Data requirements ของโปรเจกต์รวม algorithmic operations ต่างๆ ที่ต้อง perform ด้วย highest possible speed จาก basic data structures represent shelter info ไปจนถึง simple way sort available shelters ใน location ด้วย criteria ต่างๆ Redis ให้ flexibility ของ NoSQL database พร้อม functions หลากหลาย simplify และ accelerate development
ทำไมถึง QStash?
Traditional message queues ให้ solid way architect event-driven systems แต่มี caveat: พวกมัน fully depend on stateful protocols เช่น AMPQ หมายถึงไม่ ready สำหรับ short-running environments เช่น serverless functions
QStash แก้ปัญหานี้โดยอนุญาต requests ผ่าน HTTP stateless protocol ที่ใช้ใน environments เหล่านี้ รวมกับ webhooks สร้าง realtime pipelines update และ fetch data จากและเข้า database ได้
Requirements
P.S.: ถ้าอยากดู samples ข้าม requirements และ setup section ได้ พวกมันแสดงว่าเราใช้อะไรและอย่างไร
อยากสร้างระบบแบบนี้เอง ต้องมี:
- Upstash account สร้าง Redis database และใช้ QStash endpoint หรือ topic
- Ably account และ webhook integration setup
- Next.js project
- Tunneling service อย่าง ngrok proxy local requests ไป internet URL (optional)
- Package manager รองรับ libraries ที่ขอ ใช้ npm ในบทความ แต่ไม่ mandatory
หมายเหตุ: บทความให้ basic proof-of-concept samples สาธิตใช้ technologies ใน architecture ของแอป โค้ดที่ให้มา NOT production ready ลดทอนและ shorten เพื่ออ่านง่าย ต้อง modify ก่อน deploy (exception handling, security, ฯลฯ)
Setup
Serverless Functions
ใช้ Next.js edge API routes รัน serverless workloads ใกล้ user location
สร้าง Next.js project รัน npx create-next-app@latest <your-app-name> ใน parent directory รัน npm run dev dev server, npm run build production bundle, npm run start production server
Next.js ใช้ node environment สำหรับ API routes default เปลี่ยนได้ 2 วิธี:
- Per-route approach: เพิ่ม export ใน route ที่ต้องการ:
export const config = { runtime: "experimental-edge", }; - Global approach: ทำ edge เป็น default runtime เพิ่มใน next.config.js:
const nextConfig = { // ... experimental: { runtime: "experimental-edge", }, // ... };
Upstash
สร้าง Upstash provider ตาม guide นี้
Setup QStash-receiver route ตาม guide นี้ Ably ให้ 2 options webhook message encoding JSON และ MessagePack ถ้าอยาก用前者 อาจต้องใช้ authorization header แทน JWT เพื่อ encryption compatibility problems เราจะใช้ JSON เพื่อความเรียบง่าย
Ably
Realtime functionality ใช้ Ably protocols default WebSockets เริ่มตาม guide นี้ ใช้ Next.js ดู @ably-labs/react-hooks npm package
Setup webhook integration point ไป QStash URL เพิ่ม headers ที่จำเป็น
ถ้า production เช็ก authentication และ security ด้วย
Optional: Local Tunneling
เริ่ม ngrok ตาม guide นี้
Architecture
Flow
เข้าใจ app flow ดู diagram (สร้างด้วย excalidraw.com):

มี 2 user types:
- the citizen เข้าถึง shelter หรือ location info และประกาศ intention ไป bunker
- the admin แก้ไข logistics data ของ bunker และ availability (เช่น confirm คน/ครอบครัว arrived)
พยายามทำ data available realtime รักษา frontend และ backend decoupled ใช้ Ably channel communicate กับ client trigger webhook ทุกครั้ง availability เปลี่ยน
ใช้ Ably REST API publish messages จาก backend ไม่ควรใช้ stateful connection
Data
ต้องเก็บ 2 main data types:
-
Shelter information เป็น Redis hash properties:
- Key name:
country-city-number(ex:RO-CJ-01) - Availability (ex:
300) - Features (ex:
["disabled persons special acces", "counseling"]) - Resources:
ex:{ resource: quantity, or resource: list ... }{ "water": "200 liters", "medicine": ["insuline", ...] } - Electricity:
yes/no - Heating:
yes/no - Position:
[longitude, latitude]หรือ{ longitude: number, latitude: number } - _id: generate randomly
- Key name:
-
Location information
เรา สามารถ nested query หา shelters available สำหรับ location แต่อาจดีกว่าเก็บเป็น separate data structure simplify และ optimize query ใช้ Redis set ชื่อ elements
<geographical-unit>-<name>(ex:country-ROหรือcity-CJ) ป้องกัน duplicationex:
city-CJ : ["RO-CJ-01", "RO-CJ-02", "RO-CJ-03"]
แต่ยังมีปัญหา: แยกความต่างระหว่าง citizen's intention ไป shelter และ actual occupancy update จาก admin confirmation ได้ยังไง? 2 วิธี:
-
Constantly update shelter's availability key: User ประกาศ intention มา (คนเดียว/ครอบครัว) => increment by x; Admin พบ confirmation ไม่ valid => decrement by x (หรือ increment by -x)
ปัญหา: Admin ต้องเป็น source of truth track citizen location; shelter data ไม่ไว้ใจได้ทันที
-
สร้าง separate string key ชื่อ
shelter-"availability"(ex:RO-CJ-01-availability) เก็บ realtime-updated value ทำให้ update realtime ได้ และเก็บ hash's availability key เป็น source of truthClient side fetch ทั้ง hash และ string key แสดงเป็น actual availability และ projected alternative approach fetch แค่ string revert ไป source of truth เมื่อต้องการ แม้ลด network load แต่ admin ต้อง verify data ตลอด (เหมือน scenario แรก) และ fetch น้อยจริงๆ ต้อง store shelter's availability key แยก (ยังต้องได้ info อื่นๆ!)
Sorting
ใช้ Redis data type อีกอย่าง叫 sorted set อยาก sort bunkers ใน location ด้วย availability, facilities หรือ resources: nested query ทำได้แต่ cost เวลาและ data load ใหญ่ better solution สร้าง sorted sets สำหรับ filtering criteria (ชื่อ <geographical-unit>-<name>-<criteria> เช่น city-CJ-availability หรือ country-RO-food)
ex: city-B-availability
| Score | Content |
|---|---|
| 200 | RO-B-02 |
| 100 | RO-B-01 |
ในตัวอย่าง availability score คำนวณจาก total seats - current occupation
Examples
เขียน basic code สาธิต API และ client behavior
Endpoints
หลักๆ ต้องมี:
- Data endpoint serve client requested information
- Endpoint create/update data types
Client
หลัง track user location auto connect "availability" channel publish message ถ้า citizen intends ไป bunker
ก่อนอื่น สร้าง connection จาก /_app.js:
import { useEffect, useState } from "react";
import "../styles/globals.css";
import { configureAbly } from "@ably-labs/react-hooks";
export default function App({ Component, pageProps }) {
const [loaded, setLoaded] = useState(false);
useEffect(() => {
configureAbly({
// Production system ใช้ authentication key
key: process.env.NEXT_PUBLIC_ABLY_API_KEY,
});
setLoaded(true);
}, []);
if (!loaded) return <div>loading...</div>;
return <Component {...pageProps} />;
}
ต่อไป connect channel ใน component ที่ต้องการ:
import { useChannel } from "@ably-labs/react-hooks";
export default function Test() {
const [availability] = useChannel("availability", (msg) => {
console.log(msg);
});
return <></>;
}
ทำงานกับ Database
เพิ่มข้อมูลใหม่
เพิ่ม data ใหม่ใน Redis ใช้ HSET, SET, SADD commands
export default async (req) => {
let data = await req.json();
if (data.type === "shelter") {
// Remove 'type' key ไม่จำเป็นแล้ว
delete data.type;
const { name: shelter, availability } = data;
let shelterData = data;
// Generate random id
shelterData._id = Math.random()
.toString(36)
.replace(/[^a-z]+/g, "")
.substring(0, 7);
// Store shelter info as hash
await redis.hset(shelter, shelterData);
// Store realtime-updated availability as string
await redis.set(shelter + "-availability", availability);
return new Response("ok");
} else if (data.type === "location") {
const { name: location, shelters } = data;
await redis.sadd(location, ...shelters);
return new Response("ok");
}
};
อัปเดตข้อมูลที่มีอยู่
นอกจาก basic UD (update-delete) operations ใช้ Redis INCRBY และ HINCRBY commands แก้ไข shelter's availability
ถ้า citizen ประกาศ intention ไป bunker publish message จาก client app
availability.publish("update", {
shelter: "RO-CJ-01",
availability: 1, // หรือ -x ถ้า user cancel
});
ตาม architecture จะ trigger webhook ดึง data อัปเดต shelter-"availability" key
await redis.incrby(shelter + "-availability", value);
ถ้าอัปเดตจาก server (ex: admin confirmations) อย่าลืมใช้ REST API แทน
ดึงข้อมูล
บน server ใช้ GET, SMEMBERS, HGETALL commands
บน client listen messages ใน channel อัปเดต state ตามนั้น
สรุป
วันนี้เราดูการใช้ powerful serverless architecture สร้าง real-world emergency tracking app แม้มี multiple approaches ทำได้ ผมแนะนำ flow แบบนี้เพราะ simplified developer experience
อาจเป็น senior developer 習慣建構 distributed computing systems รู้สึกต้องการ managed service หรือ beginner มี idea ที่เปลี่ยนโลกให้ดีขึ้น หา right tools เป็น essential part ของ process เสมอ
บอกความคิดเห็นเกี่ยวกับบทความนี้ และถ้ามีคำถาม message ผม LinkedIn หรือเช็ก codes อื่นๆ บน Github