===== שפצר את המנורה שלך - Optimize Your LAMP stack ===== == כתריאל טראום == [[katriel@penguin.org.il]] == 31.03.2007 == ^ היסטוריית גירסאות ^^^ ^ גירסא 1.0 ^ 03-04-2007 ^ כתריאל טראום ^ | גירסא ראשונה ||| ^ גירסה 0.1 ^ 31-03-2007 ^ כתריאל טראום ^ | טיוטאת שלד ||| ==== סיכום ==== מדריך זה ידבר על הרכיבים הנפוצים בהרבה מאתרי האינטרנט היום: Linux, Apache, MySQL & PHP וכיצד ניתן למטב אותם כדי לענות לעומסים גדולים יותר. ==== מבוא ==== רובנו, כאשר אנו מקימים אתר LAMP, עושים זאת בצורה של "שגר ושכח": מתקינים, מקנפגים בצורה בסיסית כדי שהרכיבים "ידברו" ומתחילים לעבוד על האפליקציה\תוכן שלנו. מדריך זה בא להראות שלפני התוכן, ואחרי ההתקנה, שווה לעבוד עוד קצת, כדי להוציא את המקסימום מהאפליקציות והחומרה שיש לנו. במדריך זה אעבור רכיב רכיב, ואראה דרכים לשיפור הביצועים שלו, כמובן שה"שיפצורים" יהיו מוכוונים לשרת שהולך להיות שרת Web. ==== זכויות יוצרים ורשיון ==== כל הזכויות שמורות (c) 2007, כתריאל טראום, הרשות ניתנת להעתיק, לשנות ולהפיץ מדריך זה תחת התנאים של רשיון ה-GFDL Linux הוא סימן מסחרי רשום של Linus Torvalds. ==== הסרת אחריות ==== הכותב אינו נושא באחריות עבור שימוש ברעיונות, דוגמאות ומידע שבמדריך. השימוש הוא באחריות הקורא בלבד. המדריך עשוי להחיל טעויות ופרטים לא נכונים, שהשימוש בהם עשוי להיות מזיק למחשבך. למרות הסבירות הנמוכה, הכותב אינו לוקח כל אחריות ==== משוב ==== תגובות, תלונות, הערות והארות לכתובת בראשית הדף ==== תכנון מראש ==== בעולם שכולו טוב, משיקים אתר חדש, ובתוך יום כבר היה לנו מליוני כניסות יחודיות. בעולם פחות מושלם, צריך להתחיל בקטן, ולקוות שנגדל יום אחד. תנאי חשוב לגידול קל של המערכת שלנו הוא תכנון טוב מראש. \\ כבר בשלב התיכנון כדאי לתכנן את רכיבי החומרה והתוכנה שיהיו קלים להרחבה בעתיד. מומלץ תמיד להשתמש ברכיבים טובים יותר (ניתן לקרוא גם "יקרים יותר") שיוכלו לשאת ביותר עומס ממה שהמערכת מתוכננת מראש. הפרמטרים החשובים ביותר כאשר מקימים אתר LAMP הם זכרון, דיסק ומעבד (איזה הפתעה). זכרון עבוד Caching, דיסק מהיר בשביל קריאה התוכן כאשר אין אוביקט בזיכרון ומעבד בעיקר עם יש לכם אפליקציה דינאמית. ==== הרכיבים, מלמטה למעלה ==== === Linux === == IO Scheduler == החל מקרנל 2.6, נתן לכוונן כיצד מערכת ההפעלה תקרא ותכתוב מהדיסק. ישנן שיטות "קיבוץ" פעולות I/O שונות אשר נותנות ביצועים טובים במצבים שונים. השיטות הן: * Anticipatory - מערכת התזמון שנטענת כברירת מחדל. המתזמן מספק מיזוג בקשות, ומחכה מעט אחרי פעולת קריאה מהדיסק אם הוא חושב שהמשתמש עומד לבקש עוד מידע. שיטה זאת מספקת תזוזת ראש דיסק ושיפור בביצועי הקריאה. * Complete Fair Queueing - מתזמן בקשות זה מנסה להקציב לכל מבקש זמן I/O שווה בפרק זמן נתון. הוא טוב בעיקר בשביל מערכות מרובות משתמשים * Deadline - מערכת תיזמון זו אוכפת זמן קצוב לכל קריאה כדי למנוע Resource Starvation. מתזמן זה הוא העדיף למערכות Database שבהן מתבצעת קריאה רבה מהדיסק. * NOOP - לא מבצע שום תזמון מיוחד של בקשות, רק קיבוץ של מספר בקשות בפעם. כדי לקבוע את סוג המתזמן (Scheduler) יש להשתמש ב-sysfs ע"י כתיבת שם המתזמן; as, cfq, deadline או noop לתוך הקובץ: /sys/block//queue/scheduler עבור שרת Apache עמוס, ה-Anticipatory Scheduler אמור לספק שיפור ביצועים באתר אשר יש בו קריאה רבה מהדיסק. == מערכת קבצים == גם בנושא זה, האפשרויות מגוונות, לכל מערכת קבצים חוזק אחר. * ext3 - מערכת All Around בעלת Journaling, התנהגות טובה ברוב המקרים, מושתתת על מערכת הקבצים הותיקה ext2 ולכן יציבה מאוד. * XFS - מערכת קבצים אשר "מתמחית" בניהול קבצים ונפחים גדולים. חסרונה העיקרי הוא בטיפול בקבצים רבים, אז היא מתחילה לאבד ממהירותה. * ReiserFS - מערכת קבצים אשר מטפלת בצורה טובה במספר רב של קבצים קטנים. כלל אצבע הוא שרוב הקבצים לא יהיו גדולים מ-4K. בנושא הזה אין "כלל אצבע", עליכם להכיר את האתר והתוכן שאתם מתכוונים לשרת, ולהחליט על בסיס המידע הזה איזו מערכת קבצים תתאים לכם. עצתי היא בכל מערכת קבצים שלא תבחרו, השוו תוצאות של כלי בדיקת עומסים תחת מצבים קרובים ביותר לתוכן האתר שלכם. מומלץ להשתמש בתוכנות bonnie++ ו- iozone תוך דימוי קרוב ביותר לאתרכם מול כמה מערכות קבצים שונות. == Networking == צד אחר של מערכת ווב פעילה, הוא ה-Networking. גם כאן נוכל לבצע מספר שינויים בכדי לשפר את ה-Throughput של השרת שלנו. את הערכים הבאים אפשר להוסיף לקובץ sysctl.conf או דרך סקריפט אחר שמבצע את הכיוונים האלו: fs.file-max=5049800 net.ipv4.tcp_keepalive_time = 300 net.ipv4.tcp_max_orphans=1000 sys.net.core.rmem_default=262144 sys.net.core.rmem_max=262144 * הערך fs-max יגדיל את מספר הקבצים הפתוחים שיכולים להיות במערכת. כאשר השרת עמוס, כל חיבור רשת, כל קובץ שנשלח למשתמש וכל חיבור ל-DB נחשבים כקובץ פתוח. כדי למנוע מצב של Starvation, כדאי להגדיל את המספר. * tcp_keepalive_time - פרק זמן ברירת המחדל לשמור חיבור TCP פתוח הוא שעתיים. בכך שאנו מורידים אותו ל-5 דקות, אנחנו גורמים לחיבורים מתים או לא פעילים להיסגר מהר יותר, וכך לשחרר את המשאבים שבשימוש מהר יותר. * tcp_max_orphans - ערך זה דואג לא להשאיר יותר מ-1000 פרוססים שמחכים לסגירת ה- TCP Connection, ויתחיל לסגור את החיבורים שפתוחים זמן רב אחרי שיעבור את רף ה-1000 חיבורים פעילים. * שני הערכים האחרונים מגדילים את ה-Buffer של כרטיס הרשת ומאפשרים לו לקבל ולשמור ב-queue יותר תעבורה, וכך לטפל ביותר קריאות לפני שהמערכת מתחילה לדחות חיבורים נכנסים. === Apache === == MPM == MPM או Multi-Processing Modules הם מודולים שונים שמנהלים את אפאצ'י והתהליכים שלו (Processes) בדרכים שונות. 2 ה-MPM-ים העיקריים הם prefork ו-worker. ה-MPM ברירת המחדל ברוב המערכות היום הוא prefork, וכך גם בנויים לרוב המודולים הנלווים ל- Apache בהפצות לינוקס. * prefork - מודול זה מריץ מראש מספר מסויים של תהליכי Apache, כאשר כל תהליך כזה יכול לשרת חיבור אחד בכל זמן. Apache מעלה גם מספר תהליכים שמוגדרים כ-Spare מעבר ל"צריכת החיבורים" הנוכחית כדי שחיבורים נכנסים חדשים לא יצטרכו לחכות ש-Apache יריץ עוד תהליך. ב-MPM זה תהליך אב אחד אחראי להריץ מספר מסויים של תהליכים ולשמור על רמת Spares כפי שהוגדר בקונפיגורציה. האופציות ששולטות ב-MPM זה הן StartServers, MinSpareServers, MaxSpareServer ו- MaxClients: StartServers 8 MinSpareServers 2 MaxSpareServers 20 ServerLimit 256 MaxClients 256 MaxRequestsPerChild 4000 * StartServers - כמות התהליכים של Apache שיש להריץ בזמן עליית המערכת. * MinSpareServers - כמות התהליכים שיש לנסות לשמור בצורה מינימאלית כדי לספק מענה לקפיצות בכמות הדרישות. * MaxSpareServers - הכמות המקסימאלית של תהליכים אשר מוגדרים כ-Spare אשר יכולים להיות במערכת. * MaxClients -הכמות המקסימאלית של תהליכי Apache שניתן להריץ במערכת. Apache יוסיף תהליכים מעבר ל-StartServers לפי דרישה, ככל שהעומס יעלה, יריץ Apache יותר תהליכים. * MaxRequestsPerChild - ערך זה מגדיר את כמות הבקשות שתהליך בודד אחד יענה להן לפני שהתהליך יהרג. אופציה זו באה למנוע מצב של זליגת זכרון. היא רלוונטת בעיקר שאנו מריצים אתר בעל תוכן דינאמי. * worker - בדומה ל-Prefork, גם worker מריץ בעת העליה מספר מסויים של תהליכים, אך כל תהליך הוא בעצם Multi-Threaded. דבר זה מאפשר לטפל ביותר בקשות, עם פחות Processes וכך לחסוך במשאבים. הבעיה הגדולה איתו היא שלא תמיד הספריות והאפליקציות ש-Apache עובד איתן הן "Thread Safe" ולכן לא ניתן לעבוד איתו תמיד. מודול זה נשלט ע"י האופציות הבאות: StartServers 2 MaxClients 150 MinSpareThreads 25 MaxSpareThreads 75 ThreadsPerChild 25 MaxRequestsPerChild 0 * StartServers - כמות התהליכים (לא ה Threads) להריץ. * MaxClients - כמות ה-Threads להריץ סה"כ במערכת. * MinSpareThreads - הכמות המינימאלית של Spare threads שיש להחזיק במערכת כדי לענות על קפיצות ברמת הפניות. * MaxSpareThreds - כמות המקסימאלית של Spare threads. * ThreadsPerChild - כמות ה-Threads שכל תהליך (Process) מריץ השימוש ב-Prefork יתרום לרוב במערכות בעלות מעבד אחד או-2, והוא יותר עמיד בפני נפילות של תהליכים ומודולים בעיתיים. Worker MPM מתאים יותר למערכות מרובות מעבדים אשר יוכלו לנצל טוב יותר את האופי ה-Multithreaded שלו ואת ניצול הזכרון הנמוך יחסית. מצד שני, מכיוון שהוא Threaded ברגע שתהליך אחד עף, כל ה-threads שהוא מנהל ימותו גם כן. * הערכות גודל - בהערכה גסה, תוכן סטטי אומר שכל תהליך (Process) של Apache יצרוך כ-2 עד 3 מגה, ואילו תהליך שמריץ תוכן דינאמי (PHP\Perl) יצרוך עד כ-20M. כמובן שהערכים ישתנו מאתר לאתר ומשרת לשרת, זהו ערך שצריך לעקוב אחריו כדי לתכנן את כמות התהליכים\נימים(Threads) המקסימאלית שמערכת תוכל לנהל. כמות התהליכים המקסימאלית צריכה להיות בערך: MaxClients = / Process Size לא רצוי לעלות מעבר לכמות ה-RAM שיש לשרת, כי אז יתחיל תהליך של swapping וביצועי המערכת יפגעו קשות. כרגיל, אין כאן כלל אצבע, שניהם צריכים להיבחן לצרכים שלכם. == File access Method == * sendfile()- כאשר מדובר בתוכן סטאטי בעיקרו, מהירות העברת המידע מהדיסק לרשת היא נושא קריטי כדי להשגת ביצועים טובים. Apache מסוגל להשתמש בקריאת מערכת (System call) אשר נקראת "sendfile()". קריאה זו חוסכת את העבודה של פתיחת הקובץ, הקצאת חוצץ (Buffer) לקריאתו ושליחתו למשתמש. קריאה זו גורמת לקרנל לשלוח את הקובץ ישירת, ללא תיווך וקריאה של תוכן הקובץ. ישנם כמה מצבים שלא כדאי להשתמש ב-sendfile , וכאשר השימוש עשוי אף לפגוע בביצועים: * כאשר הקבצים הנשלחים למשתמש נמצאים מעל Networked Filesystem כמו NFS או smb/cifs. * ישנן מערכות הפעלה אשר היישום של sendfile הוא "שבור" משהו, ולא מומלץ להשתמש בו (לא ממש יישים לגבי לינוקס) * ישנן לפעמים בעיות עם שילוב של sendfile ושימוש ב- IPv6. כדי לאפשר אופציה זאת יש לאפשר את האופציה Sendfile: EnableSendfile On * mmap - כאשר Apache כן צריך להשתמש בתוכן של קובץ (למשל דרך mod_include), מיפוי הקובץ לזכרון ייתן גישה יותר מהירה למידע ויאפשר עיבוד מהיר יותר של התוכן. גם כאן, ישנם מצבים שלא כדאי להשתמש באופציה זו: * כאשר התוכן הוא מעל NFS/SMB, אפאצ'י יקרוס אם קובץ שפתוח ב mmap() ימחק או ישונה בזמן שהוא פתוח ע"י Apache. * ישנן מערכות SMP ששימוש ב mmap יאט את המערכת. == Configurations == * **AllowOverride** - במידה ו AllowOverride לא יהיה None, אפאצ'י יחפש בכל ספריה עד ספריית היעד את הקובץ .htaccess, לדוגמא: AllowOverride all יחפש את הקובץ .htaccess בספריות: * /srv/.htaccess * /srv/www/.htaccess * /srv/www/html/.htaccess במידה והקובץ .htaccess לא נחוץ, קריאה נוספת לדיסק בעבור כל ספריה היא מעמסה מיותר על מערכת ה-I\O. * **FollowSymlinks/SymLinksIfOwnerMatch.** - האופציה FollowSymlinks תעקוב אחרי symbolic links בתוך ספריה, ואילו האופציה FollowSymlinksIfOwnerMatch תעקורב אחרי symbolic links רק אם תיקיית היעד היא בבעלות אותו הקובץ כמו הלינק אליה. בדיקה זו מצריכה קריאות מערכת נוספות כדי "לפענח" למי שייכת ספריית היעד. השתמשו ב FollowSymlinks בכל מקום, אך הפעילו את FollowSymlinksIfOwnerMatch רק בספריות שהכרחי לעשות כך. == Caching == Caching של מידע נעשה ברמות שונות בזמן מעבר התוכן מהשרת ללקוח: ב-RAM של השרת, Proxy שיושב בתווך, הדפדפן של המשתמש ועוד. שכבת Cache נוספת שעשויה לשפר את הביצועים של שרת Apache היא לבצע caching של תוכן שמשרת שרת ה-Apache על דיסקים מהירים או בזכרון. החל מ-Apache 2.0 ועם שיפורים ניכרים ב Apache 2.2 נוסף מודול שנקרא mod_cache אשר מסוגל לבצע caching של תוכן בזכרון המכונה או על דיסקים מהירים יותר ולשרת כך תוכן נפוץ בצורה מהירה יותר. מידע נוסף ניתן למצוא בקישור http://httpd.apache.org/docs/2.0/mod/mod_cache.html == Content Compression == ב- Apache 1.3 קראו למודול mod_gzip והוא הגיע כמודול חיצוני, החל מ- Apache 2.0 הוא מגיע כחלק מהחבילה ונקרא mod_deflate. מודול זה מאפשר לכווץ את המידע לפני שהוא נשלח ללקוח הקצה, כמובן במידה והלקוח תומך בתוכן מכווץ. אופציה זו מאפשרת זמני חיבור קצרים יותר מצד הלקוחות, וכך בעצם מאפשרת ל-Apache לשרת יותר לקוחות. בונוס נוסף הוא כמובן חיסכון ברוחב פס. כדי לאפשר את mod_deflate, נשים את הקונפיגורציה הבאה: AddOutputFilterByType DEFLATE text/html text/plain text/xml BrowserMatch ^Mozilla/4 gzip-only-text/html BrowserMatch ^Mozilla/4\.0[678] no-gzip BrowserMatch \bMSI[E] !no-gzip !gzip-only-text/html Header append Vary User-Agent env=!dont-vary אופציה זו אומרת ל-Apache לכווץ תוכן טקסטואלי בלבד (אין טעם לכווץ תמונות שבד"כ באות גם ככה בפורמט מכווץ כל שהוא), ומוסיפה כמה תנאים כדי לפתור בעיות עם דפדפנים ישנים יותר. ניתן לשים את האופציה בקונפיגורציה הכללית או בתוך הגדרה של Virtual Server. נושא אחד שיש לשים לב אליו הוא שכיווץ לא בא ללא מחיר. המחיר הוא שימוש גדול יותר ב- CPU לצורך כיווץ המידע. לכן גם פה, אין כלל אצבע, ויש לעקוב אחרי ההשפעה של הפעלת mod_deflate על ביצועי השרת. === MySQL === שרת ה-SQL בכל אתר תוכן דינאמי יכול להיות צוואר בקבוק במידה ולא נטפל בו כמו שצריך. העיצה הכי טובה שאני יכול לתת היא, במידה ואפשר, לשים את שרת ה-MySQL על מחשב משלו על מנת שלא יצטרך לחלוק ולהתחרות על משאבים עם שרותים אחרים על המחשב (בעיקר Apache שהוא צרכן זכרון ו-I/O לא קטן). כמו שאר הרכיבים במדריך זה, ההשפעה הגדולה ביותר על ביצועי MySQL היא כמות הזיכרון אשר נקצה ל-Caches & Buffers . ל-MySQL יש 2 מנועי מידע ראשיים ונפוצים: MyISAM ו- InnoDB. לכל אחד התנהגות שונה וחוזקים שונים. להשוואה קצרה בין ה"מנועים" עברו לקישור הבא: http://dev.mysql.com/tech-resources/articles/storage-engine/part_3.html == MyISAM == מנוע זריז, מתאים בעיקר לאתרים שצריכים לבצע כמות גדולה של "SELECT" ולאו דווקא לעדכן הרבה את תוכן הטבלה. MyISAM משתמש ב-Cache של מערכת ההפעלה כדי לבצע Table Caching, לכן רוב הכיוונונים שיש לעשות לו נוגעים ל Query Caching ו- Index Caching. 2 המשתנים שמשפיעים על הנ"ל הם "key_buffer_size" אשר שולט על גודל ה Index Cache ו-"query_cache_size\query_cache_limit" אשר שולטים על ה Query Caching buffer. * **key_buffer_size** - שולט על גודל ה Index buffer. בכל שאילתא אשר מסתמכת על index מסויים בטבלה, מתבצע שימוש ב index של אותה טבלה. במידה וה-index לא ב-cache כרגע, צריכה להתבצע פעולה "יקרה" של גישה לדיסק. ככל שנגדיל את ה key_buffer_size, כך נבצע פחות פעולות קריאה לדיסק, וכמובן, נשפר ביצועים. ערך ברירת המחדל הוא 88M, כאשר מומלץ להגדיל את הערך (במידה והשרת הוא שרת MySQL יעודי) עד לרבע (1/4) מה- RAM של המערכת. * **query_cache_size\query_cache_limit** - ערכים אלו שולטים על גודל ה-buffer שיוקצה על מנת לבצע caching לשאילתות שנשלחות לשרת בצורה חוזרת. query_cache_size שולט על גודל ה-buffer והערך השני query_cache_limit שולט על הגודל המקסימאלי של שאילתא שתיכנס ל-cache. גם כאן, יש להיזהר בכמות ה-RAM שמקצים, מכיוון שיותר מדי זכרון יעלה גם את הכמות הזמן שהשרת צריך לנהל את ה-cache ולנקות אותו משאילתות שאינן רלוונטיות יותר. שילוב של הערכים ב-/etc/my.cnf יראה כך: [mysqld] key_buffer_size = 128M query_cache_size = 32M query_cache_limit = 2M כדי לעקוב אחרי השפעת הפרמטרים נשתמש בפקודה SHOW STATUS בתוך mysql prompt: mysql> SHOW STATUS LIKE 'key_read%'; +-------------------+-------+ | Variable_name | Value | +-------------------+-------+ | Key_read_requests | 837 | | Key_reads | 103 | +-------------------+-------+ 2 rows in set (0.00 sec) הערך key_read_requests הוא כמות הקריאות ל Table Indexes שהתבצעה מאז עליית המערכת. הערך key_reads הוא כמות השאילתות שבוצעו וניגשו לדיסק (בניגוד ל-Cache) כדי לשלוף את הנתונים. ככל שנעלה את ערך ה-cache, אמור להשתפר היחס של קריאות מה-RAM מול קריאות מהדיסק. mysql> SHOW STATUS LIKE 'qcache%'; +-------------------------+----------+ | Variable_name | Value | +-------------------------+----------+ | Qcache_free_blocks | 50 | | Qcache_free_memory | 32106552 | | Qcache_hits | 8854 | | Qcache_inserts | 1816 | | Qcache_lowmem_prunes | 0 | | Qcache_not_cached | 65 | | Qcache_queries_in_cache | 631 | | Qcache_total_blocks | 1426 | +-------------------------+----------+ 8 rows in set (0.00 sec) * qcache_free_memory - המקום הפנוי שנשאר ב- query cache. * qcache_inserts - מספר השאילתות שהיו במערכת. * qcache_not_cached - מספר השאילתות שלא נכנסו ל-cache. * Qcache_queries_in_cache - מספר השאילתות שנמצאות כרגע ב-cache. * Qcache_hits הוא מספר הפעמים שהמערכת השתמשה בשאילתות מתוך ה-cache. הערך שעליו יש להסתכל עבור אופטימיזציה עתידית הוא Qcache_lowmem_prunes, אשר אומר מתי היה צריך MySQL למחוק שאילתות מה-cache מכיוון שנגמר לו המקום והיה צריך לפנות שאילתות ישנות לטובת חדשות. עם הערך הנ"ל עולה במהירות, סימן שלא הוקצה מספיק זכרון וסביר שנצטרך להגדיל את הערך query_cache_size. מידע נוסף על הנושא ניתן למצוא בקישור http://www.databasejournal.com/features/mysql/article.php/3110171 == InnoDB == בניגוד ל-MyISAM, המנוע InnoDB עושה Caching לא רק לאינדקסים אלא גם למידע מתוך טבלאות, לכן מומלץ להקצות כמות גדולה יותר של RAM ל-MySQL. הערך ששולט על כמות ה-RAM נקרא innodb_buffer_pool_size. בשרת יעודי ל-MySQL מומלץ להקצות עד 70-80 אחוז מה-RAM של המערכת: [mysqld] innodb_buffer_pool_size = 256M כדי לעקוב אחרי השימוש של המנוע ב-RAM נשתמש בפקודה: SHOW INNODB STATUS\G == ערכים נוספים == ערכים נוספים שכדאי לבדוק אך לא ארחיב עליהם במדריך: * Open Tables * Sort\Join Buffers === PHP === == opcode cache == בכל ריצה של סקריפט PHP, התוכן של הסקריפט מפוענח (Interpeted) ומקומפל ל [[http://en.wikipedia.org/wiki/Opcode|opcodes]], פקודות ברמה נמוכה. \\ ברגע שהסקריפט הודר (Compiled) פעם אחת, במידה והוא לא שונה, אין טעם לקמפל אותו שוב. Opcode cachers תופסים את אותו קוד מקומפל ומריצים אותו במקום לקמפל שוב את הסקריפט. ישנם כמה פתרונות, מסחריים וחופשיים אשר מבצעים את העבודה. לא נרחיב על הנושא עוד במדריך זה. להלן רשימה חלקית של PHP Opcode cachers: * [[http://trac.lighttpd.net/xcache/|XCache]] * [[http://eaccelerator.net/|eAccelerator]] * [[http://il.php.net/apc|Alternative PHP Cache]] ==== מידע נוסף ==== גוגל הוא החבר הטוב ביותר שלכם בנושא זה. במדריך זה ניסיתי לרכז מעט מהדברים אשר עשיתי במהלך השנים, תוך הסתמכות והרחבה ע"י מאמרים באינטרנט. ==== תורמים למדריך \ תודות ==== * יריב גרף {{tag>mysql php apache optimization latest}}