הערה: כאשר מספר המשתמשים עולה על 500 אלף ומספר המנויים עולה על 10 מיליון, טבלת follows פשוטה ללא אינדקס מתחילה להאט: ספירת עוקבים הופכת לסריקה מלאה. אנחנו צוות עם ניסיון של 5+ שנים בפיתוח אפליקציות חברתיות; סיפקנו מעל 30 פרויקטים עם מערכות מנויים המטפלות בעומסים של מיליונים. אנו פותרים בעיה זו באופן מקיף — מסכמת נתונים עם אינדקסים ודה-נורמליזציה ועד התראות push והמלצות. אינדקס על followee_id מאיץ את COUNT(*) פי אלף במיליון רשומות, ומונה דה-נורמלי ב-users נותן תגובה מתחת ל-1 אלפית השנייה. הזמינו פיתוח מערכת המנויים שלכם מאיתנו — אנו מבטיחים יציבות תחת כל עומס. הניסיון שלנו מכסה את כל הניואנסים: App Store Review Guidelines (סעיף 4.2/5.1), דרישות סודיות וביצועים. בפרויקט אחד לרשת חברתית עם 2M משתמשים פעילים יומית, תכננו תת-מערכת המעבדת 10K פעולות בשנייה ללא השבתה. במאמר זה נדון באיזו סכמת מסד נתונים אופטימלית, כיצד להימנע מהידרדרות ביצועים תחת צמיחה, ומדוע עדכוני UI אופטימיים הם קריטיים לחוויית המשתמש.
סכמת נתונים ושאילתות
טבלת follows:
CREATE TABLE follows (
follower_id BIGINT NOT NULL,
followee_id BIGINT NOT NULL,
created_at TIMESTAMP DEFAULT NOW(),
PRIMARY KEY (follower_id, followee_id)
);
CREATE INDEX idx_follows_followee ON follows (followee_id);אינדקס על CREATE TABLE follows ( follower_id BIGINT NOT NULL, followee_id BIGINT NOT NULL, created_at TIMESTAMP DEFAULT NOW(), PRIMARY KEY (follower_id, followee_id) ); CREATE INDEX idx_follows_followee ON follows (followee_id); הוא חובה — הוא מאיץ ספירת עוקבים פי 1000 במיליון רשומות. דה-נורמליזציה של מונים בטבלת followee_id (שדות users ו-followers_count) המעודכנת באמצעות טריגר או תור נותנת זמני תגובה מתחת ל-1 אלפית השנייה.
| שיטה | זמן ביצוע | עומס על מסד הנתונים |
|---|---|---|
| שדה דה-נורמלי | <1 אלפית השנייה | ללא |
| SELECT COUNT(*) עם אינדקס | 10–100 אלפיות השנייה | בינוני |
דרך נוספת להאיץ בדיקת מעקב הדדי היא caching ב-Redis: מהיר פי 100 מ-SQL במיליון רשומות. עבור שאילתות תכופות (מי עוקב אחרי), אנו מאחסנים סט hash follower_id → Set<followee_id>.
כיצד ליישם כפתור Follow/Unfollow עם עדכון אופטימי
עדכון אופטימי הוא חובה — הכפתור מתחלף באופן מיידי לפני שהשרת מגיב. במקרה של שגיאה, המצב חוזר לאחור. ב-iOS אנו משתמשים ב-Combine, באנדרואיד — StateFlow. שלבי היישום:
- החלף את מצב כפתור ה-UI (לדוגמה, מ-"Follow" ל-"Following").
- שלח בקשת follow/unfollow אסינכרונית.
- בהצלחה, אשר את המצב החדש.
- בשגיאה, החזר את ה-UI למצב המקורי.
// iOS func toggleFollow(userId: String, currentlyFollowing: Bool) {
let optimisticState = !currentlyFollowing
updateFollowButton(isFollowing: optimisticState)
let request = optimisticState ? apiService.follow(userId) : apiService.unfollow(userId)
request.sink(
receiveCompletion: { [weak self] completion in
if case .failure = completion {
self?.updateFollowButton(isFollowing: currentlyFollowing) // откат
}
},
receiveValue: { _ in }
).store(in: &cancellables)
}שלושה מצבי כפתור: "Follow", "Following" ו-"Unfollow" (מוצג בלחיצה ארוכה). חשוב לא להפוך את "Unfollow" לטקסט הראשי — משתמשים עלולים לטעות ולחשוב שהם מאשרים מנוי. עבור SwiftUI אנו משתמשים ב-following_count עם תבנית העדכון האופטימי, עבור Jetpack Compose — // iOS func toggleFollow(userId: String, currentlyFollowing: Bool) { let optimisticState = !currentlyFollowing updateFollowButton(isFollowing: optimisticState) let request = optimisticState ? apiService.follow(userId) : apiService.unfollow(userId) request.sink( receiveCompletion: { [weak self] completion in if case .failure = completion { self?.updateFollowButton(isFollowing: currentlyFollowing) // откат } }, receiveValue: { _ in } ).store(in: &cancellables) } .
חשבונות פרטיים ובקשות מעקב
אם האפליקציה תומכת בפרופילים פרטיים, אנו מציגים טבלת @State. המשתמש היעד רואה בקשות נכנסות, מאשר או דוחה אותן. עם האישור, הרשומה עוברת ל-mutableStateOf, ונשלחת התראת push למבקש.
כיצד מאורגן pagination של רשימת העוקבים
Pagination מבוסס cursor לפי follow_requests (requester_id, target_id, status, created_at). עבור כל משתמש ברשימה, שאילתת אצווה בודקת follows: created_at DESC — שאילתה אחת לעמוד.
שאילתה מותאמת לבדיקת isFollowedByMe
SELECT followee_id FROM follows WHERE follower_id = ? AND followee_id IN (?, ?, ?); ב-iOS אנו משתמשים ב-isFollowedByMe עם טעינה מוקדמת של 3 תאים לפני הסוף. באנדרואיד — SELECT followee_id FROM follows WHERE follower_id = ? AND followee_id IN (?) עם SELECT followee_id FROM follows WHERE follower_id = ? AND followee_id IN (?, ?, ?); ו-UITableViewDiffableDataSource. עבור SwiftUI — LazyColumn עם Paging 3, עבור Jetpack Compose — RemoteMediator עם List.
התראות על מנוי
במנוי חדש, אנו שולחים התראת push: "ג'ון נרשם אליך" (FCM/APNs). Deeplink מוביל לפרופיל של העוקב. איגום: אם 5 אנשים נרשמים תוך דקה, נשלחת התראה אחת "5 מנויים חדשים".
| סוג התראה | ערוץ | איגום |
|---|---|---|
| מנוי חדש | Push (FCM/APNs) | עד 5 אירועים בדקה |
| בקשה אושרה | Push | ללא |
הגדרת התראות push דורשת טיפול נכון בתעודות APNs ומפתחות FCM. אנו מכינים provisioning profiles עבור iOS ו-google-services.json עבור אנדרואיד.
המלצות: "מי לעקוב"
היוריסטיקה פשוטה — חברים של חברים. SQL:
SELECT DISTINCT f2.followee_id
FROM follows f1
JOIN follows f2 ON f1.followee_id = f2.follower_id
WHERE f1.follower_id = :me
AND f2.followee_id != :me
AND NOT EXISTS (
SELECT 1
FROM follows
WHERE follower_id = :me
AND followee_id = f2.followee_id
)
LIMIT 20;עבור גרפים גדולים, חישוב מוקדם באמצעות worker ב-Redis מפחית את זמן התגובה ל-5 אלפיות השנייה.
כיצד אנו מפתחים את מודול המנויים: שלבי עבודה
| שלב | משך | תוצאה |
|---|---|---|
| ניתוח | יום אחד | קביעת עומס ודרישות |
| עיצוב | יום אחד | סכמת DB, אינדקסים, cache |
| יישום API | 1–2 ימים | נקודות קצה: follow/unfollow, רשימה, המלצות |
| אינטגרציית לקוח | 1–2 ימים | iOS (Swift/SwiftUI), אנדרואיד (Kotlin/Jetpack Compose) |
| בדיקות | יום אחד | בדיקות עומס עד מיליון מנויים |
| פריסה וניטור | 0.5 יום | הוראות פריסה |
מה כלול בעבודה
- סכמת מסד נתונים עם אינדקסים ודה-נורמליזציה
- נקודות קצה של API בשרת (REST או GraphQL)
- קוד לקוח עבור iOS (Swift/SwiftUI) ואנדרואיד (Kotlin/Jetpack Compose)
- התראות push עם איגום
- תיעוד API ואינטגרציה
- הוראות פריסה וניטור
לוחות זמנים
מערכת בסיסית (follow/unfollow, מונים, רשימה) — 1‑2 ימים. עם חשבונות פרטיים, התראות והמלצות — 3‑5 ימים. התמחור נקבע באופן אישי. קבלו ייעוץ — נעריך את הפרויקט שלכם ביום אחד. צרו קשר כדי לדון בפרטים ולהזמין פיתוח. אנו גם מסייעים בפרסום ב-App Store וב-Google Play, תוך התחשבות ב-App Store Review Guidelines ובדרישות Google Play Console.







