מסך אחד לכל חיישני הרחפנים · AfifAI
מקרה בוחן · אבטחת מרחב אווירי / נתונים בזמן אמת · 2026

פיד אחד בזמן אמת מכל חיישני הרחפנים, לכל מסך שצריך אותו

חברה ישראלית בתחום אבטחת המרחב האווירי מפעילה חיישני גילוי רחפנים של כמה ספקים, ולכל אחד פרוטוקול, התחברות ומודל נתונים משלו. הובלתי את התכנון והפיתוח של מערכת מרכזית שקולטת את כולם, הופכת כל גילוי לפורמט אירוע אחד, ומזרימה אותו בזמן אמת לדשבורד של המפעילים ולפלטפורמות לאומיות לניהול תעבורת רחפנים, כל אחת בפורמט שהיא מצפה לו.

⁦.NET 10⁩ / ⁦C#⁩WebSocketsGraphQL subscriptionsAmazon SQSDynamoDBSecrets ManagerECS + ALBReact + TypeScript
לקוח
חברה ישראלית לאבטחת מרחב אווירי ונגד רחפנים (חסוי)
התפקיד שלי
ראש צוות, ארכיטקט ומפתח בפועל
לוח זמנים
אפריל – אוגוסט 2026 · מהוכחת היתכנות לאינטגרציה חיה עם חיישנים
סטטוס
באוויר
במבט אחד
186kאירועים שהצטברו בתור ונוקזו עד אפס אחרי המעבר לעבודה במנות
11 עד 2קריאות API לתור לכל 10 אירועים (קבלה + מחיקה אחת במנה)
10 עד 1פריימים של WebSocket לכל מנה, לכל לקוח מחובר
2 עד 1פרוטוקולים של ספקים שאוחדו לאירוע גילוי קנוני אחד
0שינויי קוד כדי להוסיף צרכן: שורה ברג׳יסטרי וסוד אחד
3פורמטי פלט בשני ערוצים (זרם WebSocket ו־⁠HTTPS push)
≈1 שנייההערכת זמן מהחיישן ועד המסך, מקצה לקצה
4חודשים מה־⁠commit הראשון לאינטגרציה בפרודקשן, 2 ריפוזיטוריז, 154 commits
הבעיה

כל חיישן דיבר בשפה אחרת

החברה פורסת חיישני גילוי של יותר מספק אחד. האחד הוא רשת חיישני RF שמפרסמת איומים דרך GraphQL subscriptions מעל WebSocket, עם זרם פרטים נפרד לכל איום פעיל. השני דוחף מסלולי רחפנים ו־⁠Remote ID בפרוטוקול WebSocket משלו, עם סשנים מבוססי טוקן. לכל אחד חוקי התחברות וחידוש טוקן משלו, דרכי כשל משלו, והגדרה משלו למה זה ”גילוי”.

בצד השני, הדשבורד של המפעילים, פלטפורמה לאומית לניהול תעבורת רחפנים (UTM) ושירות U-space מצפים כל אחד למבנה נתונים אחר, וחלקם רוצים זרם חי ואחרים קריאות HTTPS. חיבור ישיר של כל חיישן לכל צרכן היה נותן N × M אינטגרציות שבירות, כל אחת עם לוגיקת התחברות מחדש משלה.

מה הובלתי

ארכיטקטורה, צד שרת והבאגים הקשים

  • תכננתי את הפיצול לשני שירותים (קליטה והפצה) עם תור ביניהם, ואת אירוע הגילוי הקנוני ששני הצדדים חולקים.
  • בניתי את ה־⁠workers של הספקים: טיפול ב־⁠GraphQL subscriptions עם Rx, לקוח ה־⁠WebSocket, התחברות וחידוש טוקן לכל ספק.
  • בניתי את שירות ההפצה: פולרים מקבילים לתור, mapper לכל לקוח, באפר חסום לכל חיבור ומפרסם ה־⁠HTTPS.
  • העברתי את הספקים והצרכנים מקובצי קונפיגורציה לרג׳יסטרי ב־⁠DynamoDB, עם פרטי הגישה ב־⁠Secrets Manager.
  • אבחנתי ותיקנתי את הצבר של 186 אלף אירועים, זרמים שהשתתקו ובאגים של פקיעת טוקן מול חיישנים חיים.
  • הובלתי את דשבורד המפעילים הדו־לשוני (עברית ואנגלית, RTL) עם פיד הגילויים החי.
ארכיטקטורה

קליטה, חציצה, הפצה

התרשים רחב מהמסך: החליקו הצידה לצפייה בכולו →

שני השירותים רצים כקונטיינרים. שירות הקליטה מכיר רק חיישנים, ושירות ההפצה מכיר רק צרכנים. התור ביניהם מבטיח שצרכן איטי או מנותק לעולם לא יאט חיבור לחיישן, ושאפשר לפרוס כל צד מחדש בלי להפיל את השני.

החלטות תכנון

להפריד בין החיישנים לצרכנים

תור בין הקליטה להפצה

חיבורים לחיישנים הם ארוכי טווח ויקרים להקמה מחדש, וצרכנים באים והולכים. SQS מפריד בין השניים, בולע פרצי עומס ומאפשר לכל צד לגדול ולהיפרס בנפרד. המחיר: עוד קפיצה בדרך ומסירה של at-least-once, שמזהי האירועים הופכים לגלויה.

אירוע קנוני אחד, mapper לכל צרכן

כל גילוי הופך לאותה רשומה (מיקום, גובה, תדר, רמת איום, סיווג) עם הרחבה טיפוסית לפי ספק. לכל צרכן יש mapper שמתגלה בעליית השירות ונרשם לפי מפתח. המחיר: המודל המשותף הוא מכנה משותף, ולכן פרטים ייחודיים לספק יושבים בהרחבה ולא בליבה.

קונפיגורציה כנתונים

הספקים והצרכנים הפעילים, ערוץ התקשורת שלהם (WebSocket או HTTPS) וסוג ה־⁠mapper נשמרים ברג׳יסטרי ב־⁠DynamoDB. פרטי גישה ומפתחות API נשמרים ב־⁠Secrets Manager. הוספת צרכן היא שורה וסוד, לא גרסה חדשה. המחיר: הרג׳יסטרי נקרא בעליית השירות, ולכן שינוי דורש restart.

באפרים חסומים שזורקים את הישן

לכל חיבור יש channel חסום משלו. לקוח שמפגר מאבד את המנות הישנות ביותר במקום להאט את כל השאר. בתמונת אוויר חיה, עדכני עדיף על שלם. המחיר: המסלול הזה אינו לוג ביקורת, וגרסה 1 בכוונה לא שומרת אירועים.

יומן הנדסי

מה נשבר מול חיישנים חיים, ואיך תיקנתי

רוב העבודה האמיתית צצה רק אחרי שחיישנים אמיתיים התחברו. מבחר:

מאי 2026

חיבורים שמתים כשהשמיים שקטים

כשלא היה גילוי במשך זמן מה, ה־⁠load balancer סגר חיבורי WebSocket בלי תעבורה, והלקוחות ראו פיד מת.

לולאת השליחה ממתינה לנתונים עם timeout של 30 שניות, ושולחת frame של keep-alive כשהוא פוקע, הרבה לפני חלון ה־⁠idle של ה־⁠load balancer.

מאי 2026

זרם פרטים לכל סט איומים שמשתנה

רשת ה־⁠RF מפרסמת את רשימת האיומים הפעילים ב־⁠subscription אחד ואת הפרטים בשני, שצריך להוציא מחדש עם סט המזהים העדכני בכל פעם שהוא משתנה.

מידלתי את המזהים הפעילים כ־⁠observable, ריסנתי שינויים ב־⁠50 מילישניות והשתמשתי ב־⁠Switch כך שכל סט מזהים חדש סוגר בצורה נקייה את ה־⁠subscription הקודם.

31 במאי

186,000 אירועים תקועים בתור

תחת עומס חיישנים אמיתי צד ההפצה פיגר: כל הודעה התקבלה, נכתבה לכל סוקט ונמחקה אחת־אחת, והצבר הגיע לכ־⁠186 אלף.

הגדלתי את מספר הפולרים המקבילים, עברתי למחיקה במנות של עד 10, והעברתי כל מנה שהתקבלה ל־⁠channel בזיכרון לכל לקוח, כך שהיא יוצאת כפריים WebSocket אחד. הצבר התרוקן ונשאר על אפס.

13 ביוני

”הצלחה” שלא הייתה הצלחה

חידוש הטוקן מול אחד הספקים נכשל בדרכים מוזרות. הספק מחזיר HTTP 200 גם כשהקריאה נכשלת, והתוצאה האמיתית נמצאת בגוף התשובה.

בודקים את קוד השגיאה בגוף התשובה, לא רק את הסטטוס. אם החידוש נכשל, הסשן נזרק והתחברות מחדש מתבצעת תחת נעילה, כדי שקריאות מקבילות לא יפעילו כמה התחברויות.

⁦17 – 23⁩ ביוני

זרם שהשתתק במקום להיכשל

חריגות נרשמו ללוג ונבלעו בתוך ה־⁠handlers של הזרם. ה־⁠worker נראה תקין, אבל שום אירוע לא הגיע.

שגיאות מפילות עכשיו את הזרם, וה־⁠worker נכנס ללולאת ההתחברות מחדש עם המתנה קבועה (3 ו־⁠5 שניות לפי ספק). בדפדפן, הלקוח מתחבר מחדש עם back-off אקספוננציאלי משנייה אחת ועד 15 שניות.

יולי 2026

צרכן שרצה HTTPS ולא סוקט

פלטפורמת U-space מקבלת אירועים רק כקריאות HTTPS מאומתות, בזמן שהאחרים נרשמים לזרם.

הוספתי שדה ערוץ לרג׳יסטרי הצרכנים, ומפרסם ששולח כל מנה לכל יעדי ה־⁠HTTPS במקביל, שומר טוקן לכל יעד ומתאמת מחדש פעם אחת על 401. יעד אחד שנכשל לא חוסם את האחרים.

10 באוגוסט

שגיאות אימות שלא היו חריגות

אחרי פקיעת טוקן, שרת ה־⁠GraphQL דיווח על כשל אימות כ־⁠payload של שגיאה ב־⁠subscription של הפרטים. שום דבר לא נזרק, ולכן ה־⁠worker לא התחבר מחדש.

מזהים את קוד שגיאת האימות בתוך ה־⁠payload ומפילים את הזרם בכוונה, מה שמפעיל התחברות מלאה מחדש.

לפני ואחרי

כמה עולה להעביר 10 אירועים

מספר הקריאות שנדרש כדי להוציא מנה של 10 אירועים מהתור ולמסור אותה ללקוח מחובר אחד. נגזר מהקוד לפני ואחרי השינוי של 31 במאי, לא מבנצ׳מרק.

תוצאה

איפה זה עומד

  • שני ספקי חיישנים על זרם אירועים קנוני אחד, בזמן אמת, עם התחברות, חידוש טוקן והתחברות מחדש שמתקנים את עצמם.
  • הצבר בתור ירד מכ־⁠186 אלף לאפס, עם בערך פי 5 פחות קריאות לתור ופי 10 פחות כתיבות לסוקט לכל מנה.
  • שלושה פורמטים במורד הזרם (דשבורד, snapshot חיישנים ל־⁠UTM, אירוע U-space) ב־⁠WebSocket וב־⁠HTTPS מאותו זרם.
  • צרכנים מצטרפים בהוספת שורה ברג׳יסטרי וסוד אחד, בלי פרטי גישה בקובצי קונפיגורציה.
  • דשבורד מפעילים דו־לשוני בעברית ובאנגלית עם פיד גילויים חי (50 האחרונים, עם השהיה) וסטטוס חיישנים.
  • הצעדים הבאים כבר מוגדרים: אימות לקוחות חזק יותר ממפתח ב־⁠query string, מאגר אירועים לביקורת, ומונים חיים בדשבורד.
טכנולוגיות

טכנולוגיות

צד שרת
⁦.NET 10⁩, ⁦C#⁩, ⁦ASP.NET Core⁩ minimal API, hosted services
זרמים
WebSockets, GraphQL subscriptions, System.Reactive, System.Threading.Channels
ענן
AWS: SQS, DynamoDB, Secrets Manager, קונטיינרים מאחורי ALB, CloudWatch Logs
ביצועים
System.Text.Json עם source generation, קבלה ומחיקה במנות, פולרים מקבילים
צד לקוח
React 18, TypeScript, Vite, Redux Toolkit, Ant Design, i18next (עברית / אנגלית, RTL)
אספקה
Docker multi-stage, ענפי dev / QA / stage / production

שיחת אבחון חינם · 30 דקות · ללא התחייבות

הביאו תהליך אחד. קבלו תוכנית ומספר לשפר.

כתבו בכמה שורות: איזה תהליך או מערכת, מה כואב היום (זמן, טעויות, עלויות או פיילוט AI שנתקע) ומי עובד עם זה. אחרי שיחה קצרה מקבלים תוך כ־3 ימים תוכנית כתובה והצעת מחיר סגורה.

לשיחת אבחון חינםafif.mansour@gmail.com

נפתח מייל מוכן מראש · מענה תוך יום עסקים

ה־AI קורא. הקוד סופר.

כל הפרויקטים