
צ׳ק‑ליסט פרקטי להאצת אפליקציות על Postgres: תוכניות ביצוע (EXPLAIN), אינדקסים, caching עם Redis, ומה חשוב לדעת כשעובדים עם Serverless Postgres כמו Neon.
ביצועי מסד נתונים הם אחת הדרכים המהירות לשפר UX. הרבה פעמים “איטי” זה פשוט המתנה ל‑DB.
- תוכניות ביצוע (EXPLAIN), אינדקסים חסרים ופילטרים עם קרדינליות גבוהה
- שימוש בחיבורים ו‑pooling (במיוחד בסביבות serverless)
- caching עם Redis לקריאות חמות / נתונים מחושבים
- pagination בטוחה והימנעות מ‑OFFSET עמוק
- ניטור: slow queries, p95 latency, cache hit rate
Serverless Postgres נותן אלסטיות טובה, אבל דורש משמעת בניהול חיבורים וביעילות שאילתות. מתייחסים לביצועי DB כאל מוצר: מודדים ומשפרים.
בחודש שעבר, פיצ׳ר חיפוש המשתמשים שלנו זחל במהירות של ~2.5 שניות לשאילתה. משתמשים התלוננו, ואני איבדתי שינה. האשם? אינדקס בודד שחסר וכמה דפוסי שאילתה עצלנים שכתבתי לפני חודשים ושכחתי.
אחרי סוף שבוע של פרופיילינג, הורדתי את זה ל־~180ms — שיפור של כ־92%. עם Redis cache הגעתי ל־~8ms לבקשות חמות. כאן אני מפרק צעד־אחר־צעד מה עשיתי, עם SQL, TypeScript ו־EXPLAIN שאתם יכולים להעתיק לפרויקטים שלכם.
טבלת users הייתה בת ~50,000 שורות. לא ענקית, אבל השאילתה סרקה כל שורה בכל חיפוש:
1-- בדיקת ביצועי שאילתות עם EXPLAIN ANALYZE
2EXPLAIN ANALYZE
3SELECT u.id, u.name, COUNT(o.id) as order_count
4FROM users u
5LEFT JOIN orders o ON o.user_id = u.id
6WHERE u.created_at > '2024-01-01'
7GROUP BY u.id, u.name
8ORDER BY order_count DESC
9LIMIT 20;
10
11-- דוגמת פלט:
12-- Seq Scan on users u (cost=0.00..1234.56 rows=5000)
13-- אם רואים "Seq Scan" על טבלאות גדולות, צריך אינדקס!1-- אינדקס B-tree לסינון לפי תאריך (הנפוץ ביותר)
2CREATE INDEX idx_users_created_at ON users(created_at);
3
4-- אינדקס מורכב עבור JOIN + WHERE
5CREATE INDEX idx_orders_user_created ON orders(user_id, created_at);
6
7-- אינדקס חלקי רק למשתמשים פעילים (חוסך מקום)
8CREATE INDEX idx_active_users ON users(id) WHERE status = 'active';
9
10-- אינדקס GIN לחיפוש טקסט מלא
11CREATE INDEX idx_users_name_gin ON users USING GIN (to_tsvector('english', name));
12
13-- בדיקת שימוש באינדקסים
14SELECT schemaname, tablename, indexname, idx_scan, idx_tup_read
15FROM pg_stat_user_indexes
16ORDER BY idx_scan DESC;1import { Pool } from 'pg';
2import { neon, neonConfig } from '@neondatabase/serverless';
3
4// Connection pooling מסורתי (שרתי Node.js)
5export const pool = new Pool({
6 connectionString: process.env.DATABASE_URL,
7 max: 20, // גודל Pool מקסימלי
8 idleTimeoutMillis: 30000,
9 connectionTimeoutMillis: 2000,
10});
11
12// Neon serverless (ללא חיבורים קבועים)
13neonConfig.fetchConnectionCache = true;
14export const sql = neon(process.env.DATABASE_URL!);
15
16// שאילתה עם ניהול חיבור אוטומטי
17export async function getUser(id: string) {
18 // ב-serverless: Neon מטפל בחיבור דרך HTTP
19 const result = await sql`SELECT * FROM users WHERE id = ${id}`;
20 return result[0];
21}
22
23// מעקב אחרי בריאות ה-pool
24pool.on('error', (err) => {
25 console.error('Pool error:', err);
26});
27
28pool.on('connect', () => {
29 console.log('חיבור חדש למסד הנתונים');
30});1import { Redis } from '@upstash/redis';
2
3const redis = new Redis({
4 url: process.env.UPSTASH_REDIS_REST_URL!,
5 token: process.env.UPSTASH_REDIS_REST_TOKEN!,
6});
7
8// Cache-aside pattern עם TTL
9export async function getCachedUser(userId: string) {
10 const cacheKey = `user:${userId}`;
11
12 // ניסיון קודם במטמון
13 const cached = await redis.get<User>(cacheKey);
14 if (cached) {
15 console.log('Cache HIT:', cacheKey);
16 return cached;
17 }
18
19 // Cache MISS: שליפה ממסד הנתונים
20 console.log('Cache MISS:', cacheKey);
21 const user = await sql`SELECT * FROM users WHERE id = ${userId}`;
22
23 // שמירה במטמון עם TTL של 5 דקות
24 await redis.setex(cacheKey, 300, JSON.stringify(user[0]));
25
26 return user[0];
27}
28
29// ביטול מטמון בעת כתיבה
30export async function updateUser(userId: string, data: Partial<User>) {
31 await sql`UPDATE users SET ${sql(data)} WHERE id = ${userId}`;
32 await redis.del(`user:${userId}`); // ביטול מטמון
33}1// ❌ רע: OFFSET pagination (איטי על datasets גדולים)
2export async function getBadPagination(page: number, limit: number) {
3 const offset = page * limit;
4 // נעשה יותר ויותר איטי ככל ש-offset גדל!
5 return await sql`
6 SELECT * FROM posts
7 ORDER BY created_at DESC
8 LIMIT ${limit} OFFSET ${offset}
9 `;
10}
11
12// ✅ טוב: Cursor-based pagination (ביצועים קבועים)
13export async function getCursorPagination(cursor?: string, limit: number = 20) {
14 if (cursor) {
15 // שליפת פוסטים אחרי ה-cursor
16 return await sql`
17 SELECT id, title, created_at
18 FROM posts
19 WHERE created_at < ${cursor}
20 ORDER BY created_at DESC
21 LIMIT ${limit}
22 `;
23 } else {
24 // עמוד ראשון
25 return await sql`
26 SELECT id, title, created_at
27 FROM posts
28 ORDER BY created_at DESC
29 LIMIT ${limit}
30 `;
31 }
32}
33
34// שימוש:
35const firstPage = await getCursorPagination(undefined, 20);
36const lastPost = firstPage[firstPage.length - 1];
37const secondPage = await getCursorPagination(lastPost.created_at, 20);רוצה ייעוץ? לחץ כאן לקביעת שיחה.
קבע שיחהאיך אני ניגש ל‑Real‑time (צ׳אט, עדכונים חיים): WebSockets, גבולות אימות/סשן, שליטה בעומסים (backpressure), ואיך שומרים על ביצועים צפויים במערכות Next.js + Node.js.

מדריך מקיף ומנוסה לבחירה בין MongoDB ל-PostgreSQL: scalability, ביצועים, transactions, אינדקסים, שירותים מנוהלים ותרחישי שימוש אמיתיים ל-SaaS ו-eCommerce. כולל מסגרות החלטה, דיאגרמות ודוגמאות קוד.

מדריך מעודכן ל-WebMCP: מה חדש ב-Early Preview של Chrome, איך מתחילים נכון, איך זה עוזר למפתחים ולמשתמשים, ומה ההבדל מול אינטגרציית MCP ב-backend.
בכל מקום שבו אתה נמצא, בוא נעבוד יחד על הפרויקט הבא שלך.
מעדיף לדבר ישירות? קבע שיחה ונדבר על הפרויקט שלך בלייב.
קבע שיחה