قلّل حجم JSON في Node.js API قبل ما تزود السيرفرات
مستوى القارئ: متوسط
هتخرج من المقال بخطة عملية تقلل حجم استجابة JSON وتخفض P95 latency قبل ما تدفع فلوس في سيرفرات زيادة.
المشكلة باختصار
لو عندك endpoint في Node.js بيرجع قائمة منتجات أو طلبات أو مستخدمين، أسهل غلطة هي إنك ترجع كل الحقول مرة واحدة. الطريقة دي بتفشل لما الداتا تكبر: الشبكة تتعب، المتصفح يقعد يفك JSON كبير، والـ API يظهر بطيء حتى لو قاعدة البيانات كويسة.
الافتراض إن عندك REST API مبني بـ Express، وعدد الطلبات حوالي 200 إلى 1000 طلب في الدقيقة. عندك endpoint مثل /api/orders بيرجع 500 order، وكل order فيه customer كامل، address، notes، audit fields، وitems. في قياس واقعي تقديري، الاستجابة ممكن تبقى 1.8MB وP95 حوالي 1200ms على اتصال متوسط. بعد تقليل الحقول والصفحات والضغط، ممكن تنزل إلى 180KB وP95 حوالي 380ms. ركز: الرقم ده تقديري للتوضيح، مش وعد ثابت.
ابدأ بالقياس بدل التخمين
أفضل طريقة هنا إنك تقيس حاجتين قبل أي تعديل: حجم الـ payload وP95 latency. حجم الاستجابة بيقولك هل المشكلة في البيانات المنقولة. P95 بيقولك 95% من الطلبات خلصت تحت زمن معين، وده أهم من المتوسط لما عندك مستخدمين حقيقيين.
جرّب الأمر ده من جهاز قريب من بيئة الاختبار:
# قياس الحجم الفعلي للاستجابة
curl -s -o /tmp/orders.json -w "size=%{size_download} bytes time=%{time_total}s\n" \
"https://api.example.com/api/orders?limit=500"
# قياس ضغط بسيط باستخدام wrk لمدة 30 ثانية
wrk -t4 -c80 -d30s "https://api.example.com/api/orders?limit=500"
لو الحجم فوق 500KB لصفحة عادية، غالبًا عندك فرصة تحسين كبيرة. لو الحجم 40KB والـ P95 عالي، المشكلة غالبًا في query، lock، أو خدمة خارجية. متخلطش المشاكل.
قلّل الحقول قبل ما تضغط
الضغط مهم، لكنه مش بديل عن تقليل البيانات. بدل ما ترجع كل order، رجّع الحقول المطلوبة للشاشة الحالية فقط. مثال: صفحة جدول الطلبات غالبًا تحتاج id، status، total، createdAt، واسم العميل. مش محتاجة notes ولا audit trail.
import express from "express";
import compression from "compression";
const app = express();
app.use(compression({ threshold: 1024 }));
app.get("/api/orders", async (req, res) => {
const limit = Math.min(Number(req.query.limit ?? 50), 100);
const cursor = req.query.cursor ?? null;
const rows = await db.orders.findMany({
take: limit + 1,
...(cursor ? { cursor: { id: cursor }, skip: 1 } : {}),
orderBy: { id: "desc" },
select: {
id: true,
status: true,
total: true,
createdAt: true,
customer: { select: { name: true } }
}
});
const hasMore = rows.length > limit;
const data = rows.slice(0, limit);
res.json({
data,
nextCursor: hasMore ? data[data.length - 1].id : null
});
});