xhtml ssi מפת האתר דף ראשי

«
«
«
«
«
«
ראשי  » אפאצ'י  »  מהירות שרתי אפאצ'י

מאמר 2: הגברת מהירות שרתי אפאצ'י / דין גודה

2.1 הקדמה

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

לרוב האתרים יש רוחב פס יוצא הקטן מ- 10Mb, אותו אפאצ'י יכול למלא בעזרת שרת אינטרנט מבוסס פנטיום בלבד (ולא הפנטיום המפותח ביותר). בפועל, אתרים בעלי רוחב פס גדול יותר זקוקים ליותר ממכונה אחת למלא את רוחב הפס, בגלל אילוצים אחרים (כגון CGI או תקורה של מסד הנתונים).
מסיבות אלה, הפיתוח של אפאצ'י התמקד בעיקר על נכונות ואפשרויות קונפיגורציה.

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

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

מאמר זה מותאם לאפאצ'י 1.3 ב- Unix. חלק מהמאמר מתייחס לאפאצ'י על NT (אשר טרם כויל לביצועים וסביר שביצועיו אינם טובים כי ביצועי NT מחייבים מודל תכנות שונה).



2.2 נושאים הקשורים לחומרה ומערכת ההפעלה

הנושא הגדול ביותר הקשור לחומרה המשפיע על ביצועיו של שרת אינטרנט הוא RAM. אסור שיתעורר צורך אי פעם לשרת אינטרנט לבצע
החלפה ( swap ).
החלפה מגדילה את הזמן של כל בקשה, מעבר לנקודה שמשתמשים תופסים כ"מהירה מספיק". זה גורם למשתמש ללחוץ על Stop ו- Reload, מה שמגביר את העומס עוד יותר. אפשר וכדאי לקבוע את ההגדרה MaxClients כדי למנוע מהשרת שלך להוליד כ"כ הרבה צאצאים כשהוא מתחיל לבצע swapping.

מעבר לכך, הכל פשוט: תשיג לעצמך CPU, כרטיס רשת מהיר מספיק, ודיסקים מהירים דיים. "מהיר דיו" - נקבע לפי ניסוי וטעייה.

בחירת מערכת ההפעלה היא בעיקר עניין של תנאים מקומיים. עדיף תמיד להחיל את הטלאים המעודכנים ביותר מסוג TCP/IP של המוכר.
הגשה ב- HTPP שוברת לחלוטין כמה מההנחות שנבנו לתוך חלקי Unix עד 1994 ואפילו 1995. בחירות טובות הן למשל FreeBSD ולינוקס (Linux).



2.3 נושאים הקשורים לקונפיגורציה של זמן ההרצה

לפני אפאצ'י 1.3, ברירת המחדל של HostNameLookups היתה on. זה הוסיף זמן המתנה לכל בקשה כי היא מחייבת שחיפוש DNS יסתיים לפני השלמת הבקשה.
באפאצ'י 1.3 ברירת המחדל היא off. אך בגרסא זו של אפאצ'י או גרסה מאוחרת יותר, אם תשתמש בפקודות allow from domain או deny from domain כלשהן - תשלם בחיפוש DNS בעל היפוך-כפול (היפוך ואחריו חיפוש קדימה כדי לוודא שהחיפוש לאחור בסדר).
לכן כדי לקבל את הביצוע הטוב ביותר, אל תשתמש בפקודות אלה (אך בהחלט אפשר להשתמש בכתובות IP במקום שמות domain).
שים לב שאפשר לתחום את הפקודות, כמו למשל בתוך קטע . במקרה כזה, חיפושי ה- DNS מתבצעים רק על הבקשות המתאימות לקריטריונים.

להלן דוגמא אשר פוסלת את כל החיפושים מלבד עבור קבצי html. ו-CGI. :
HostnameLookups off 
<Files ~ "\.(html|cgi)$>
     HostnameLookups on
</Files>
אך, עדיין, אם אתה רק זקוק לשמות DNS בכמה CGI בלבד, תוכל להשתמש בקריאת gethostbyname באותם CGI אשר זקוקים לכך.

FollowSymLinks and SymLinksIfOwnerMatch

בכל מקום בו אין לך OptionFollowSymLinks ברווח ה-URL או שיש לך
OptionsSymLinksIfOwnerMatch, אפאצ'י יצטרך לספק קריאות מערכת נוספות כדי לבדוק את ה- symlinks - קריאה נוספת אחת לכל רכיב של שם הקובץ.
למשל, אם יש לך:
 DocumentRoot /www/htdocs
<Directory />
    Options SymLinksIfOwnerMatch
</Directory>
ויש בקשה ל- index.html URI/, אזי אפאצ'י יבצע (2) Istat על www , /www/htdocs/ וגם על
www/htdocs/index.html/.
אף פעם אין תופסים את התוצאות של ה- Istats הללו ולכן הם יופיעו בכל בקשה ובקשה.
אם אתה מעוניין בבדיקת הבטיחות של symlinks, תוכל לעשות משהו כזה :
 DocumentRoot /www/htdocs 
<Directory />
    Options FollowSymLinks
</Directory>
<Directory /www/htdocs>
    Options -FollowSymLinks +SymLinksIfOwnerMatch
</Directory>
לפחות כך, תמנע את הבדיקות הנוספות למסלול של DocumentRoot.
שים לב שתצטרך להוסיף מקטעים חדשים אם יש לך מסלולים מסוג Alias או RewriteRule מחוץ לשורש המסמך.

לביצועים הטובים ביותר, וללא הגנת symlink, קבע בכל מקום FollowSymLinks ולעולם אל תקבע את SymLinksIfOwnerMatch.

2.3.1 התרת דריסה (AllowOverride)

בכל מקום ברווח URL בו אתה מאפשר דריסה (בדר"כ קבצי htaccess.), אפאצ'י ינסה לפתוח את htaccess. לכל רכיב בשם הקובץ.
למשל אם יש לך:
DocumentRoot /www/htdocs
<Directory />
    AllowOverride all
</Directory> 
ומוגשת בקשה ל- URL הבא: index.html/, אפאצ'י ינסה לפתוח /htaccess , /www/.htaccess./ וגם www/htdocs/.htaccess/ .
הפתרונות דומים למקרה הקודם של OptionsFollwoSymLinks.
לביצועים הטובים ביותר, השתמש ב- AllowOverride None בכל מקום במערכת הקבצים שלך.

2.3.2 משא ומתן (Negotiation )

במידת האפשר, המנע לחלוטין ממשא ומתן ( negotiation ) על תוכן, אם מה שמעניין אותך הם הביצועים. בפועל, התועלת מ- negotiation גדולה מהחסרונות ( פגיעה בביצועים ).
יש מקרה בודד בו אתה מסוגל להגביר את מהירות השרת. במקום Wildcard כגון DirectoryIndex index, השתמש ברשימה מלאה של אופציות:
DirectoryIndex index.cgi index.pl index.shtml Index.html , כשהבחירה הנפוצה ביותר תהיה הראשונה ברשימה.

2.3.3 יצירת תהליכים:

לפני אפאצ'י 1.3 ההגדרות הבאות היו בעלות השפעות דרסטיות על תוצאות ה-benchmark ( הערכת ביצועי המחשב):
StartServers, MaxSpareServers, MinSpareServers.
אפאצ'י היה זקוק לתקופת זמן כדי להגיע למספר בנים גדול דיו כדי לשרת את העומס. לאחר "לידה" ראשונה של בני StartServers, רק בן אחד בשניה יוצר כדי לעמוד בהגדרת MinSpareServers. לכן נדרשו כ- 95 שניות לשרת, אשר 100 לקוחות מתחברים אליו בו זמנית, באמצעות ברירת מחדל ב- StartServers של 5, על מנת "ללדת" מספיק בנים כדי לטפל בעומס זה. בשרתים אמיתיים, זה פועל מצוין כיוון שלא מבצעים restart עליהם לעתים תכופות - אך זה משפיע לרעה על הרצות ה- benchmark אשר עשויות להסתיים אחרי 10 דקות בלבד.

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

נכון לאפאצ'י 1.3, הקוד יאפשר הגמשת כלל האחד-בכל-שניה. המכונה תוליד בן, תמתין שניה אחת, ואז תלד שני בנים, תמתין שניה, תלד ארבעה ותמשיך אקספוננציאלית עד שהיא מגיעה ל- 32 בנים בשניה. המכונה תפסיק כשהיא תעמוד בהגדרת MinSpareServers.

נראה שהתגובה בכך היא מספיק טובה וכמעט אין צורך לכייל את ה- MinSpareServers, MaxSpareServers ו- StartServers . כשיותר מארבעה בנים נולדים בשניה, מסר יועבר ל- Errorlog . אם תראה הרבה שגיאות כאלה, יש לשקול כיול ההגדרות דלעיל. השתמש בפלט mod_status כמדריך.

נושא הקשור ליצירת תהליך הוא מות התהליך, הנגרם ע"י ההגדרה MaxRequestsPerChild. ברירת המחדל נקבעה ל- 30 וסביר להניח שזה נמוך מדי, אלא אם השרת שלך משתמש במודול, כגון mod_perl , אשר גורם לניפוח הזיכרון לבנים שלך. אם השרת שלך מציג בעיקר עמודים סטטיים, יש להעלות את ברירת המחדל ל - 10,000, בערך, ואז הקוד חזק מספיק להבטיח שלא תיווצר בעיה.

כשמשתמשים ב- keep-alive, הבנים יהיו עסוקים בשום דבר וימתינו לבקשות נוספות על החיבור שכבר פתוח. ברירת המחדל של KeepAliveTimeout היא 15 שניות, ע"מ לצמצם תופעה זו. ההתלבטות כאן היא בין רוחב הפס של הרשת לבין משאבי השרת.
אין להגדיל את ההגדרה מעל כ- 60 שניות כי אז יאבדו רוב היתרונות.



2.4 נושאים הקשורים לקונפיגורציה של זמן הקומפילציה

mod_status and Rule STATUS=yes
אם אתה מוסיף mod_status ומגדיר Rule STATUS=yes בבניית האפאצ'י, בכל בקשה, אפאצ'י יבצע שתי קריאות ל- (2) gettimeoufday. בגרסאות ותיקות יותר מ- 1.3, אפאצ'י יבצע מספר קריאות נוספות ל- (2)time. אפאצ'י עושה כל זאת כדי שדו"ח המצב (status report) יכלול אינדיקציות זמן.
כדי לקבל את הביצועים הטובים ביותר, קבע Rule STATUS=no.

2.4.1 שקעים ( sockets ) מרובים

כעת נדון בחסרון בשקע יוניקס (API (Unix . נניח ששרת האינטרנט שלך משתמש ב- listen מרובים על מנת להקשיב ליציאות מרובות או לכתובות מרובות.
כדי לבדוק בכל שקע אם החיבור מוכן, אפאצ'י משתמש ב- (2) Select. כך רואים האם לשקע אין שום חיבור או יש לפחות חיבור אחד הממתין עליו. המודל של אפאצ'י כולל בנים מרובים, וכל הבנים "הבטלים" (idle) בודקים קיום חיבורים חדשים באותה עת.
יישום נאיבי יראה כמו הדוגמא להלן:
for (;;) {
    for (;;) {
	   fd_set accept_fds;
		FD_ZERO (&accept_fds);
 		for (i = first_socket; i <= last_socket; ++i) {
 			FD_SET (i, &accept_fds);
		}
 		rc = select (last_socket+1, &accept_fds, NULL, NULL, NULL);
 		if (rc < 1) continue;
 		new_connection = -1;
 		for (i = first_socket; i <= last_socket; ++i) {
			if (FD_ISSET (i, &accept_fds)) {
 				new_connection = accept (i, NULL, NULL);
 				if (new_connection != -1) break;
 			}
 		}
 		 if (new_connection != -1) break;
 	}
 	process the new_connection;
   }
אך בישום נאיבי זה יש בעיות הרעבה (Starvation) חמורה.
זכור כי באותו זמן, בנים מרובים מבצעים לולאה זו וכך, בנים מרובים יעצרו ב- select, כשהם נמצאים בין בקשות. כל הבנים החסומים יתעוררו וישובו מ- select כשבקשה אחת תופיע באחד מהשקעים (מספר הבנים שמתעוררים משתנה, תלוי במערכת ההפעלה ובנושאים הקשורים לעיתוי) ואז הם כולם ייגשו אל הלולאה וינסו לבצע accept של החיבור - אך רק אחד יצליח (בהנחה שעדיין רק חיבור אחד מוכן) - השאר ייחסמו ב- accept. זה נועל את הבנים הללו לשירות בקשות מאותו שקע יחיד ולא משום שקע אחר, והם יהיו תקועים שם, עד שמספיק בקשות חדשות יופיעו על השקע הזה כדי להעירם. בעיית הרעבה זו תועדה לראשונה ב- PR#467.
לבעיה זו לפחות שתי פתרונות :

פתרון אחד הוא ליצור שקעים שאינם נחסמים. במקרה כזה, ה- accept לא יחסום את הבנים ויותר להם להמשיך ללא הפרעה. אבל, פתרון זה מבזבז זמן CPU. נניח שיש לך 10 בנים בטלים ב- select ומגיע חיבור אחד. אז, תשעה מהבנים הללו יתעוררו, ינסו לבצע accept לחיבור, ייכשלו, ויחזרו ללולאה חזרה אל select , לאחר שלא הצליחו לבצע דבר. בינתיים, אף אחד מהבנים הללו לא משרת בקשות שהופיעו על שקעים אחרים, עד שהם שוב חוזרים ומגיעים ל- select.
בסה"כ, פתרון זה אינו פורה כ"כ , אלא אם יש לך מספר CPU (קופסה מרובת-מעבדים) פנויים , כמספר הבנים הבטלים - ומצב זה אינו סביר.

פתרון אחר הוא (וזהו הפתרון של האפאצ'י ) לקבוע את סדר הכניסה ללולאה הפנימית. הלולאה נראית כך :
for (;;) {
           accept_mutex_on ();
           for (;;) {
               fd_set accept_fds;
                 FD_ZERO (&accept_fds);
               for (i = first_socket; i <= last_socket; ++i) {
                   FD_SET (i, &accept_fds);
               }
               rc = select (last_socket+1, &accept_fds, NULL, NULL, NULL);
               if (rc < 1) continue;
               new_connection = -1;
               for (i = first_socket; i <= last_socket; ++i) {
                   if (FD_ISSET (i, &accept_fds)) {
                       new_connection = accept (i, NULL, NULL);
                       if (new_connection != -1) break;
                   }
               }
               if (new_connection != -1) break;
           }
           accept_mutex_off ();
           process the new_connection;
       }
הפונקציות accept_mutex_on ו- accept_mutex_off מיישמות סמפורים (semaphore). רק בן אחד יכול להיות בעל mutex בכל רגע. ישנם אפשרויות שונות למימוש ה- mutex. הבחירה מוגדרת ב- src/conf.h (בגרסאות לפני 1.3) או src/main/conf.h (גרסאות 1.3 והלאה).
ישנן ארכיטקטורות אשר אין להן בחירת נעילה. בארכיטקטורות אלה, אין להשתמש בהנחיות Listen מרובות, בגלל בעיות בטיחות.

USE_FLOCK_SERIALIZED_ACCEPT
שיטה זו משתמשת בקריאת המערכת (2)flock כדי לנעול קובץ נעילה (ניתן לאתר בעזרת הנחיית LockFile).

USE_FCNTL_SERIALIZED_ACCEPT
שיטה זו משתמשת בקריאת המערכת (2)fcntl כדי לנעול קובץ נעילה (ניתן לאתר בעזרת הנחיית LockFile).

USE_SYSVSEN_SERIALIZED_ACCEPT (גרסה 1.3 והלאה).
שיטה זו משתמשת בסמפורים בסגנון SysV כדי ליישם את ה- mutex.
לדאבוננו, לסמפורים מסגנון זה יש מספר תופעות לוואי שליליות. הראשונה היא, שתיתכן האפשרות שהאפאצ'י ימות מבלי לנקות את הסמפורים. תופעה אחרת היא שהסמפור API מאפשר התקפה של "סירוב שירות" ע"י כל CGI שרץ תחת אותו מספר זיהוי משתמש כמו שרת האינטרנט (ז"א, כל ה- CGI , אלא אם תשתמש במשהו כמו suexec או cgiwrapper). מסיבות אלה, אין משתמשים בשיטה זו על שום ארכיטקטורה מלבד IRIX (על רוב קופסאות IRIX, שתי השיטות הקודמות הן יקרות מאד).

USE_USLOCK_SERIALIZED_ACCEPT (גרסה 1.3 והלאה)
שיטה זו זמינה רק על IRIX והיא משתמשת ב- (2)usconfig כדי ליצור mutex.
אמנם שיטה זו מונעת את הבלגן של סמפורים מסוג SysV, אך אין היא ברירת המחדל ל- IRIX. הסיבה נעוצה בעובדה שבקופסאות IRIX בעלות מעבד אחד (5.3 או 6.2), קוד uslock הוא איטי יותר בשני סדרי גודל בהשוואה לקוד של סמפורי SysV.
בקופסאות IRIX מרובות מעבדים, קוד הנעילה מהר יותר מקוד הסמפורים SysV.
זהו מצב מבולגן למדי. ולכן, אם אתה משתמש בקופסת IRIX מרובת-מעבדים, יש לבנות מחדש את שרת האינטרנט שלך באמצעות: DUSE_USLOCK_SERIALIZED_ACCEPT ב- CFLAGAS_EXTRA.

USE_PTHREAD_SERIALIZED_ACCEPT (גרסה 3.1 והלאה)
שיטה זו משתמשת ב- mutex מסוג Posix ואמורה לעבוד בכל ארכיטקטורה המיישמת את המפרט המלא של Posix threads - אך נראה כי היא פועלת רק על Solaris (גרסה 2.5 והלאה).
אם למערכת שלך יש שיטת Serialization אחרת שאינה מופיעה ברשימה דלעיל, אזי אולי עדיף להוסיף לה קוד.

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

באופן אידיאלי, עליך להריץ שרתים ללא משפטי Listen מרובים, אם רצונך לקבל ביצועים גבוהים. אך המשך לקרוא.


2.4.2 שקע אחד

כל הנ"ל בסדר גמור עבור שרתים מרובי-שקעים - אך מה קורה עם שרתים עם שקע אחד? בתיאוריה, הם לא אמורים להוות בעיה כי כל הבנים פשוט יכולים להיחסם ב- (2)accept עד שמגיע חיבור, ולא תהיה הרעבה. בפועל, זה מסתיר את התנהגות שנדונה בפתרון ללא חסימה. לפי אופן היישום הרגיל של רוב ה- TCP stacks, ה- kernel ( גרעין מערכת ההפעלה ) מעורר את כל התהליכים שנחסמו בתוך accept כשמגיע חיבור אחד. אחד מהתהליכים מקבל את החיבור וחוזר ל- userspace, השאר חוזרים לישון כשהם מגלים שלא מחכה להם חיבור.
פעולה זו נסתרת מהקוד של user-land, אך היא נמצאת שם בכל זאת. זה עלול להביא לתוצאות דומות לתוצאות הפתרון ללא חסימה במקרה של שקעים מרובים.

מסיבה זו מצאנו שמספר גדול יותר של ארכיטקטורות מתנהגות "יפה יותר" אם נבצע עליהן serialization, אפילו במקרה של שקע בודד. ולכן זוהי ברירת המחדל כמעט ברוב המקרים. ניסויים גסים תחת לינוקס (Linux) (2.0.30 על פנטיום 166 עם RAM של MB128) הוכיחו ש- serialization של שקע אחד גרם לירידה של פחות מ- 3% בבקשות לשניה, בהשוואה לשקע בודד שלא עבר serialization. אבל שקע כזה הפגין זמן המתנה של 10 מילי-שניות נוספות בכל בקשה. המתנה זו משמעותית רק ב- LAN. אם אתה רוצה לדרוס Serialization של תא בודד, תוכל להגדיר SAFE_UNSERIALIZED_ACCEPT ואז שרתים עם שקע בודד לא יבצעו Serialization בכלל.




2.5 עיכוב בסגירה

כדי ששרת HTTP יישם פרוטוקול בצורה מהימנה, הוא חייב לסגור כל כיוון תקשורת באופן בלתי תלוי (זכור שחיבור TCP הוא דו-כיווני, כל צד בלתי תלוי בשני). שרתים אחרים נוהגים להתעלם מעובדה זו, אך באפאצ'י , החל מגרסה 1.2 והלאה, היא מיושמת כהלכה.
כשתכונה זו נוספה לאפאצ'י , היא גרמה להרבה בעיות על גרסאות של יוניקס ( Unix ).
מפרט ה- TCP אינו קובע כי למצב FIN_WAIT_2 יש timeout, אך הוא אינו אוסר זאת.

במערכות ללא timeout, אפאצ'י 1.2 גורם לשקעים רבים להישאר לעד במצב FIN_WAIT_2. במקרים רבים, ניתן למנוע זאת ע"י שידרוג לטלאיי TCP/IP, המעודכנים ביותר של המוכר.
במקרים בהם מוכר מעולם לא הנפיק טלאים, כמו למשל SunOS4, אפאצ'י החליט לוותר על תכונה זו.
אפשר לעשות זאת בשתי דרכים:
הראשונה היא אופציית השקע SO_Linger. אך זה לעולם לא יושם כהלכה ברוב ה- TCP/IP Stacks. אפילו על המחסניות בעלי יישום תקין
(למשל לינוקס 2.0.31) השיטה יקרה יותר מבחינת זמן CPU בהשוואה לפתרון הבא:
לרוב, אפאצ'י מיישם זאת בפונקציה המכונה lingering_close (ב- http_main.c). הפונקציה נראית בדר"כ כך:

    void lingering_close (int s)
    {
	char junk_buffer[2048];
	/* shutdown the sending side */
	shutdown (s, 1);
	signal (SIGALRM, lingering_death);
	alarm (30);
	for (;;) {
	    select (s for reading, 2 second timeout);
	    if (error) break;
	    if (s is ready for reading) {
		read (s, junk_buffer, sizeof (junk_buffer));
		/* just toss away whatever is here */
	    }
	}
	close (s);
    }

באופן טבעי, זה מוסיף עלות בקצה החיבור, אך זה הכרחי כדי להגיע ליישום מהימן.
ככל ש- HTTP/1 נעשה נפוץ יותר, וכל החיבורים יהיו קבועים, הוצאה זו תקטן על פני מספר גדול יותר של בקשות. אם אתה רוצה להסתכן ולבטל תכונה זו, תוכל להגדיר NO_LINGCLOSE - אך זה כלל לא מומלץ.



2.6 קובץ לוח התוצאות

ההורים והבנים באפאצ'י יכולים לתקשר אחד עם השני באמצעות "לוח תוצאות". באופן אידיאלי יש ליישם זאת ע"י זיכרון משותף. במערכות הפעלה שיש לנו גישה אליהן, או שקיבלנו ports מפורטות עבורן, זה מיושם באמצעות זיכרון משותף. השאר משתמשים בברירת המחדל באמצעות קובץ on-disk. אך קובץ זה, לא רק שהוא איטי, הוא גם אינו מהימן (ובעל פחות אפשרויות).

בדוק את הקובץ src/main/conf.h לארכיטקטורה שלך וחפש HAVE_HMAP או HAVE_SHMGET. הגדרת אחד משני אלה מפעילה את קוד הזיכרון המשותף שניתן.
אם למערכת שלך יש סוג אחר של זיכרון משותף, אזי ערוך את קובץ src/main/http_main.c והוסף את הנדרש ע"מ להשתמש בו באפאצ'י (אל תשכח לשלוח לאפאצ'י טלאי).

DYNAMIC_MODULE_LIMIT
אם אין לך כוונה להשתמש במודולים הנטענים באופן דינמי, אזי הוסף -DDYNAMIC_MODULE_LIMIT=0 כשאתה בונה את השרת.
זה יחסוך RAM אשר מוקצה רק לתמוך במודולים הנטענים דינמית.