protobuf

לפני קצת יותר משנה גוגל שחררו ברשיון קוד פתוח פרוייקט בשם Protocol Buffers, ובקיצור protobuf.
protobuf מגדיר שפה להגדרת מבני נתונים (IDL), ויודע לייצר קוד יעיל שקורא וכותב את אותם מבני נתונים בC++, JAVA או פייתון.
יש תמיכה חיצונית לפורמט בשפות נוספות, C#, D, רובי PHP* ועוד.
הדוגמאות שאני אתן יהיו בג'אווה, אבל השתמשתי בו גם עם C++ והוא עובד היטב גם שם.
כל מפתח מגיע מתי שהוא לשלב שבו תוכנית שהוא כותב צריכה להעביר נתונים לתוכנית אחרת (או למופע אחר של עצמה) דרך הרשת, או לשמור נתונים לקובץ כדי לקרוא אותם אחר כך.
מקובל לקודד את הנתונים בתוך הודעות, כשיש הרבה מאוד דרכים לכתוב את ההודעות, ובדרך כלל מדובר בקוד די סיזיפי שחוזר על עצמו וכתיבתו היא די משעממת (בפעם העשירית שעושים משהו כזה).
נניח שאנחנו רוצים להעביר אובייקט שמייצג אימייל על גבי הרשת (ונתעלם רגע מהפרוטוקולים המקובלים להעברת אימיילים).
לאובייקט אימייל שלנו יש שדה של שולח, אחד או יותר שדות של מקבלים, נושא, טקסט, ואפס או יותר קבצים נלווים ואפילו עדיפות.
אפשר לייצג אותו במבנה הלוגי הזה:
[code lang="java"]
package messages;
message Email
{
enum Priority
{
LOW = 1;
NORMAL = 2;
HIGH = 3;
}

required string from = 1;
repeated string to = 2;
required string subject = 3;
required string message = 5;
repeated Attachment attch = 4;
optional Priority priority = 6;
optional string date = 7;

message Attachment
{
optional string data = 1;
required string name = 2;
}
}
[/code]

מה שיש לנו פה זה הגדרת הודעה תקינה בשפת הIDL של protobuf.
השפה תומכת בהגדרת חבילה (שמתורגמת לpackage בג'אווה ולnamespace בC++), וכן בהגדרות מקוננות של הודעות.
המספרים אחרי כל שורה נקראים טאגים ומשמשים לזיהוי של השדות בפרוטוקול הבינארי, ולכן אחרי שהם נקבעים אי אפשר לשנות אותם.
אפשר לראות שהשפה תומכת בהגדרה של enum, וכן ברשימות של אובייקטים (שיכולים בעצמם להכיל אובייקטים וכן הלאה).
חוץ מטיפוסים שאתם מגדירים, השפה תומכת גם בטיפוסים פנימיים למשל מחרוזת, משתנה בוליאני, מספר ברוחב קבוע (למשל ארבעה בתים), מספר ברוחב משתנה ועוד.
המספרים ברוחב משתנה מקודדים בצורה דומה קצת לקידוד של UTF8, אבל בצורה קצת יותר פשוטה:

הביט השמאלי (MSB) בכל בייט מוגדר כך: 1 אם המספר המועבר כולל בייטים נוספים, או 0 אם זה סוף המספר.
7 הביטים האחרים בכל בייט משמשים להעברת 7 ביטים של המספר עצמו. בצורה כזו, מספרים קטנים מ128 יתפסו בייט אחד בלבד, מספרים קטנים מ65000 בקירוב יתפסו שני בתים וכן הלאה. אם התוכנית שלכם מעבירה מספרים קטנים שיכולים להיות גדולים לעיתים נדירות – הקידוד הזה הוא אידיאלי כי בדרך כלל המספרים לא יתפסו הרבה מקום, בניגוד לקידוד ברוחב קבוע שבו כל מספר תופס למשל ארבעה בתים.

חלק מהטיפוסים הנתמכים בשפה הם ברוחב קבוע, למשל מספרים בנקודה צפה (Floating point) ושלמים שמוגדרים כFIXED, למשל FIXED64 יהיה תמיד 64 ביטים או 8 בתים.
עוד על השפה אפשר לקרוא פה.

את הקובץ שמכיל את הגדרת ההודעות "מקמפלים" עם protoc, שמייצר ממנו קוד בג'אווה C++ או פייתון:
[CODE]
$ protoc messages.proto –java_out src/
[/CODE]

הנה דוגמא לקוד ג'אווה שמייצר הודעה, כותב אותה לקובץ, קורא אותה ומדפיס אותה בפורמט טקסטואלי:
[CODE LANG="JAVA"]
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

import test.Messages.Email;
import test.Messages.Email.Attachment;
import test.Messages.Email.Builder;

class Test
{
public static void main (String args[]) throws FileNotFoundException, IOException
{
Builder b = Email.newBuilder();
b.addTo("test@abc.com");
b.addTo("test@loogle.com");
b.setFrom("someone@there.com");
Attachment aa = Attachment.newBuilder().setName("viruts.exe").build();
b.addAttch(aa);
b.setSubject("A present for you");
b.setMessage("Please open the attached Virus");
Email email = b.build();
System.out.println(email.toString());
FileOutputStream out = new FileOutputStream("email.dat");
email.writeTo(out);
out.close();

System.out.println(Email.parseFrom(new FileInputStream("email.dat")));
}
}
[/CODE]
נקמפל ונריץ:
[code lang="bash"]
$ javac -cp lib/protobuf-java-2.2.0.jar src/Test.java src/test/Messages.java
$ java -cp lib/protobuf-java-2.2.0.jar:src Test
from: "someone@there.com"
to: "test@abc.com"
to: "test@loogle.com"
subject: "A present for you"
attch {
name: "viruts.exe"
}
message: "Please open the attached Virus"
[/code]

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

* PHP : הספריה לתמיכה בPHP היא pb4php, והיא לא מאוד מוצלחת. למרות שיתכן שבקרוב הפרוייקט יקבל PATCH משמעותי שהופך את הספריה ליותר שמישה (לפחות לצרכים שלי).

Java singleton – the next generation

סינגלטון הוא אחד התבניות הנפוצות בתוכנה.
בגדול, סינגלטון הוא אובייקט יחיד מסוג מסויים במערכת, שכולל פונקציה סטטית שמאפשרת גישה נוחה לאובייקט מכל מקום במערכת.
למשל, אם יש אובייקט שמטפל בשליחת אימיילים, סביר שהוא יהיה סינגלטון כי אין ברוב התוכנות משמעות לכמה אובייקטים שיטפלו בשליחת אימיילים.
בדרך כלל כותבים סינגלטון ככה:
[code lang="java"]
public class EmailSender
{
private static EmailSender s_instance;

private EmailSender(){} // private constructor

public static synchronized EmailSender getInstance()
{
if (s_instance == null)
s_instance = new EmailSender();
return s_instance;
}

public void sendEmail(String recepient, String subject, String text)
{
// …
}
}
[/code]
כדי להשתמש בסינגלטון ככה:
[code lang="java"]
EmailSender.getInstance().sendEmail(…);
[/code]
כמה דברים לשים אליהם לב:
* יש בנאי פרטי, כדי למנוע מכל מני גורמים "לא מוסמכים" לאתחל עותק משל עצמם של הסינגלטון.
* הפונקציה getInstance צריכה להיות מסונכרנת כדי למנוע מצב שבו שני ת'רדים שונים ניגשים במקביל לפונקציה וגורמים לאיתחול של שני אובייקטים במקום אחד.

עד כאן, טרויאלי לכל מי שכתב תוכנה בג'אווה.
הפוסט הזה הוא על דרך אלטרנטיבית לכתיבת סינגלטון, והרי היא:
[code lang="java"]
public enum EmailSender
{
instance;

public void sendEmail(String recepient, String subject, String text)
{
// …
}
}
[/code]
אפשר להשתמש בenum עם קבוע בודד, ולמרבה ההפתעה אפשר להכניס פונקציות ומשתנים בהגדרה של הenum.
מותר גם (אבל לא חייבים) להגדיר בנאי פרטי.
השפה מונעת את בעיית הגישה מכמה ת'רדים, וכן אין בכלל קונספט של איתחול יזום של enum.
כדי להשתמש בסינגלטון ככה:
[code lang="java"]
EmailSender.instance.sendEmail(…);
[/code]
אלגנטי, לא?

Google Wave

גוגל וויב (Wave) הוא מיקס מגניב של אימייל, צ'אט, וויקי ועוד.
סיכמתי פה כמה נקודות מהווידאו, אבל כדאי לראות את הווידאו ולהשתכנע בעצמכם.

וויב

  • אפשרות לפצל שיחה לכמה קווי שיחה בקלות (Threads)
  • צ'אט חי, כמו בימי הבבסים (רואים מה האדם השני מקליד בזמן שהוא מקליד).
  • אפשרות לחבר אדם נוסף לשיחה בדיעבד על ידי הוספה שלו למשתתפים, הוא יוכל לראות את השיחה מההתחלה, כולל באמצעות מצב ניגון שמנגן את השיחה בשבילו צעד אחר צעד כאילו הוא השתתף בה מההתחלה.
  • אפשרות להגביל גישה להודעות למשתתפים מסויימים (מסרים פרטיים)
  • תמיכה בגרור וזרוק כדי להצמיד תמונות (וקבצים באופן כללי) להודעות, נכון לכרגע זה דורש גוגל gears, אבל הכוונה היא לדחוף את היכולת הזו בהצעה לHTML 5.
  • אפשרות לשתול שיחה בקלות בתוך בלוג (Embed), מכיוון שהשיחה מאוכסנת בשרת מרכזי זה לא משנה אם מגיבים מהבלוג אם מתוך Wave, היא תשאר שיחה אחת.
  • כל שיחה יכול להיות בעצם מסמך משותף שכל המשתתפים תורמים לו, בדומה לוויקי, כולל פיצול ואיחוד של גרסאות (Branch and merge), כולל עריכה חיה (כמו הצ'אט החי) על ידי כמה משתתפים במקביל.

הרחבות

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

פרוטוקול

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

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

מחכה בקוצר רוח שהדבר הזה יבשיל, יהיה טעים.

שימוש חכם בזכרון כדי להאיץ גישה לנתונים

קחו את המצב הבא:
יש שתי טבלאות עם כחצי מילארד רשומות בכל אחת.
באופן חד פעמי – אתם צריכים לעבד את טבלא א' בבלוקים של כמה אלפים בכל פעם.
עבור כל בלוק, אתם צריכים לגשת לטבלא ב' ולהשלים משם נתונים.
אתם צריכים לגשת רק לחלק מהעמודות בטבלא ב', אבל הביצועים חרבנה.
שליפה של כמה אלפי רשומות מטבלא ב' במכה עובד בקצב של כמה מאות בשניה:
[code lang="sql"]
SELECT a,b,c FROM table WHERE id IN (….)
[/code]

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

הרעיון הבא שלי היה לשמור את הנתונים המעניינים על הדיסק בצורה בינרית, ממויינים לפי שדה הid, כאשר הפורמט הוא פשוט רשומה אחרי רשומה.
ברגע שכל הנתונים בקובץ, אפשר לטעון אותו לזכרון פעם אחת, ולבצע עליו חיפוש בינארי זריז כדי למצוא כל רשומה.
רעיון פשוט, אבל המימוש נתקל בכמה קשיים:
נפח הנתונים המעניינים במקרה שלי הוא כ14.4 ג'יגה בייטים, כל רשומה שוקלת 24 בתים, ויש 600 מליון רשומות כאלו.
אין הרבה מחשבים שמסוגלים להעלות כזו כמות של מידע לזכרון, בהתחלה חשבתי שנתונים ישקלו "רק" 12 ג'יגה, ולכן היה ישים לטעון את כולם לזכרון של שרת עם 16GB זכרון, אבל 14.4 ג'יגה זה כבר גבולי מדי.
בעיה נוספת היא שג'אווה, השפה בה אני מממש את העניין – לא תומכת במערכים גדולים מכ2 מילארד איברים (גם לא בJVM של 64 ביט), ולכן מערך הבייטים הגדול ביותר שאני יכול ליצור לא מספיק גדול בשביל להחזיק את הקובץ בזכרון.

כדי לפתור את הבעיה של המערך הקטן מדי, חילקתי את המערך לכמה מערכים של 2GB, וכשלב מקדים לחיפוש הבינארי בתוך המערך – אני עובר על המערכים בזריזות כדי להבין באיזה מהם נמצא הערך שאני מחפש (רק מביט באיבר הראשון והאחרון כדי לראות אם הID שאני מחפש נמצא ביניהם).
כדי לפתור את הבעיה השניה, כתבתי שכבה פשוטה של שרת לקוח, כאשר אני יכול להריץ כמה שרתים שאני רוצה על כמה מכונות שצריך, כשכל אחד מהם מעלה חלק אחר מהקובץ לזכרון.
לקוח פשוט פונה לכל השרתים ושואל ומבקש מהם על גבי חיבור TCP לחפש עבורו את הנתונים שמתאימים לרשימת מזהים.

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

ניצחון.

midp-build

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

The midp-build system is a generic build system for mobile java applications developed in Eclipse. it uses the Eclipse configuration files (.classpath files) to determine project dependency, build order and build classpath automatically.

it can build any MIDlet with very little configuration.

  • The system supports complex builds with multiple outputs, for example – it's possible to build for several products, configurations and devices in a single run.
  • The build fully support preprocessing of source code based on symbols that are defined per devices, products and configurations (or almost any combination of those).
  • The build allow fine customization of the properties contained in the JAD for each JAR+JAD pair generated.
  • The build supports signing of generated MIDlets automatically
  • The build supports including classpath resources in the generated JAR(s), and/or copying specific resources
  • The build supports local builds, based on files on the workspace (without changing them in any way).
  • The build fully supports CVS and have some untested support for SVN
  • The build supports multiple build configurations per Eclipse project (see conf.prefix)

Swush

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

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

ניקח לדוגמא את פיסת הXML הבאה (קוד אמיתי מההגדרות של טומקט):

[code lang="xml"]
factory
org.apache.catalina.users.MemoryUserDatabaseFactory
pathname
conf/tomcat-users.xml

[/code]

הרבה מלל כדי להגיד שיש דבר כזה UserDatabase, ויש בתוכו שני זוגות Key-value, לא?
אפשר גם לציין את אותו מידע כך:
[code lang="java"]
ResourceParams{
UserDatabase{
factory : org.apache.catalina.users.MemoryUserDatabaseFactory
pathname : conf/tomcat-users.xml
}
}
[/code]
יותר קומפקטי ופחות פלצני.
השפה שבה השתמשתי בפעם השניה היא Swush, שפה שהגדרתי ומימשתי בסופ"ש.
Swush (סוו'ש בעברית, נראה לי שאני אדבוק באנגלית), תומכת בהערות בסגנון C/C++ וbash:
[code]
// C++ single line comment
/* C multi-
line comment*/
# Bash style comments
[/code]

בנוסף, סטרינגים בSwush לא מחייבים גרשיים, אבל אם משתמשים בגרשיים אפשר להגדיר סטרינגים עם רווחים ואפילו שורה-חדשה בתוכם.
[code]
string1

"another string"

"multi
line
string"
[/code]

המימוש שלי לSwush הוא בשפת ג'אווה, אבל בהחלט אפשרי שיהיו מימושים בשפות נוספות (בין אם שלי או של אנשים אחרים).
Swush תומך ברשימה פשוטה של מפתח-ערך, למשל:
[code lang="java"]
host : "locahost"
port : 8080
[/code]
ככה שאפשר בקלות רבה להחליף אותו קבצי properties של ג'אווה.

מבחינת הקוד, השתדלתי שהוא יהיה מה שיותר פשו לשימוש.
אפשר ליצור אובייקט Swush בכמה צורות:
[code lang="java"]
Swush fromFile = new Swush(new File(filename));

Swush fromString = Swush.constructFromString("swush:rocks","UTF-8");

Swush usingCode = new Swush();
usingCode.add(new Swush("key","value"));
usingCode.add(new Swush("node"));
usingCode.getChildren(1).add(new Swush("key2", "value2"));
[/code]

אחת הפונקציות המעניינות של המימוש שלי היא פונקציית select, שמחזירה רשימה של צמתים שמתאימים לקריטריות שצויין, לדוגמא – אם יש לנו קובץ Swush כזה:
[code lang="java"]
addressbook
{
max_size : 30

item
{
phone : 123
name : "The dude"
address : "Multi
Line address"
}

item
{
phone : 3454
name : "Another dude"
address : unknown
}
}
[/code]
ואנחנו רוצים לגשת לצמתים של הItem, נוכל לעשות משהו כזה:
[code lang="java"]
Swush swush = new Swush(new File(filename));
List matchs = swush.select("addressbook.item");
[/code]
הרשימה שחוזרת תכיל את שני הצמתים המתאימים (מסוג item שנמצאים בתוך addressbook).
Swush ממומשת עם Antlr, וההגדרה שלה מאוד פשוטה, אפשר להעביר אותה בכמה דיאגרמות:



Swush משוחררת ברשיון BSD, ואפשר להוריד אותה מפה.

Ajax Terminal

לשרת שאני כותב יש שני ממשקי ניהול, ממשק וובי וממשק טלנט.
הממשק הוובי יותר ידידותי כמובן, אבל ממשק הטלנט מהיר יותר, גמיש יותר וקל יותר להוסיף לו פקודות.
חשבתי לתומי שיהיה ממש נחמד לאפשר גישה לממשק הטלנט דרך הממשק הוובי.
הייתי בטוח שיש משהו כזה כבר, בדקתי כל מני פרוייקטים כמו anyterm,
ajaxterm ועוד כמה, אבל כולם כללו רכיב שרת והיה קשה לחלץ מהם את הקוד של צד הלקוח ולממשק אותו לשרת שלי.
בסוף הבזבוז זמן, החלטתי לנסות לממש כזה, ובעזרתו האדיבה של עמיתי לעבודה שגיא מעוז, כתבנו את הפלאגין Ajax terminal, פלאגין-JQuery פשוט במיוחד לשימוש ולהטמעה.
הרעיון הוא פשוט, הלקוח שולח שורה לשרת, השרת עושה מה שעושה ושולח תשובה ללקוח. הלקוח לוקח את התשובה ודוחף אותה בטרמינל.
הטרמינל תוכנן להראות בדיוק כמו טרמינל לינוקס, כדי שיהיה קולי במיוחד.
להמשיך לקרוא Ajax Terminal

הבאג שנלכד

מאז שעברתי לשרת החדש, היו לי בעיות עם שרת האפאצ'י: לפעמים הוא פשוט הפסיק לענות לבקשות, וחזר לעצמו רק אחרי restart.
בהתחלה לא היה לי ברור מה גורם לזה. חקרתי איך אפשר לראות מה עושה האפאצ'י בזמן שהוא רץ, וגיליתי את mod-status, שמאפשר לדעת מה עושה כל Worker באפאצ'י, ועוד כמה דברים שימושיים.
חיכיתי בסבלנות שהבעיה תופיעה שוב, וכשהיא קרתה אחרי כמה ימים – גיליתי כמובן שאני לא יכול לגשת לURL של mod-status בגלל הבעיה.
הצעד הבא היה לכתוב סקריפט קטן שידגום את הstatus של האפאצ'י כל שתי דקות וישמור את התשובה לקובץ.
אחרי כמה ימים הבעיה שוב קרתה והפעם ראיתי שרוב הWorkerים בשרת היו עסוקים בלנסות לדבר עם trac, שלא ממש ענה בגלל איזו שהיא בעיה עם בסיס הנתונים שלו.
פניתי לקבוצת הדיון של מפתחי trac, והם הציעו שאני אשדרג את sqlite (שהוא בסיס הנתונים שtrac משתמש בו ברוב ההתקנות), וגם ביקשו דברים שדורשים די הרבה זמן – כמו לקמפל את mod-wsgi עם מידע דיבאג, ולהתחבר אליו עם gdb ברגע שהעסק תקול כדי לראות מה הוא עושה.
שידרגתי, וכמובן שהבעיה לא נפתרה – אבל לא מצאתי זמן לעשות את מה שהם ביקשו.
בשלב הזה כבר הייתי די מעוצבן מזה שכל כמה ימים נופל לי האפאצ'י עד שאני בועט בו, והתקנתי את Monit, שמנתר את השרת ויכול להפעיל אותו מחדש אוטומטית אם הוא נופל (עוד על Monit בקרוב).
Monit איתחל את השרת תוך 4-5 דקות מרגע שהבעיה הופיע, וזה שינה את הסימפטומים מהמצב החמור של "השרת לא עונה לכמה שעות כל כמה ימים" למצב הנסבל של "השרת לא עונה לכמה דקות כל כמה ימים".
נסבל, וכדרכם של עצלנים, סבלתי עד שלפני כמה ימים Monit פישל ולא עבד משום מה, והשרת שוב היה למטה חצי לילה.
פניתי שוב לקבוצת הדיון של trac – הפעם מצויד בכל מידע הדיבאג שהם ביקשו, וסיפרתי להם שלמרות ששידרגתי הבעיה ממשיכה.
בינתיים ניסתי לראות איך אני משחזר את הבעיה – חיפשתי כלי לStress test לשרתי HTTP, משהו פשוט שיאפשר לי להפציץ URL בודד.
מצאתי את Siege, כלי פשוט שנותן בדיוק את זה.
בשימוש פשוט בSiege, גיליתי שאני מצליח להפיל את השרת אם אני מפעיל 15 משתמשים מול הURL של קו הזמן של trac, אחד הדפים הדורשניים ביותר במערכת.

במקביל פניתי לערוץ הIRC של טראק, ודיברתי עם osimons – אחד המפתחים של trac – על הבעיה.
גילינו שכשאני מריץ את Siege, טראק פותח את קובץ בסיס הנתונים מאות פעמים, מה שלא בדיוק הסתדר עם זה שיש בטראק Connection pooling.
המשכתי לחקור את העניין לבד, ובסוף גיליתי שהConnection pooling בכלל מכובה עבור חיבורי SQLite במכונות שלא מריצות… חלונות NT.
הגיוני? לא יודע. הפעלתי מחדש את העסק, ומאז נפתרה הבעיה.

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

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

sqlfairy

הסיטואציה הבאה תהיה מוכרת לכמה מפתחים:

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

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

איך מעבירים את השינויים האלו למערכת הפרודקשן?

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

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

בגדול, כדי לעבור מסכימה אחת לשניה, מה שצריך לעשות זה להוריד את המבנה של שני הסכימות לקובץ עם mysqldump, למשל:
[code]
source db.conf
mysqldump –no-data -h $HOST -u $USER –password=$PASS $DB_NAME $TABLES | gzip > struct.sql.gz
[/code]

כאשר הקובץ db.conf מכיל את המשתנים הדרושים להתחבר:
[code]
HOST=server_host (usually localhost)
USER=username
PASS=password
DB_NAME=database_name
TABLES="table1 table2 table3"
[/code]

ברגע ששיש לנו את שתי הסכימות ביד, ואחרי שהתקנו את SQLFairy כמובן (apt-get install sqlfairy על דביאן לני, אם אתם על Etch תתקינו ידנית מהקוד, הגרסא שבEtch לא עובדת טוב), אפשר לקבל הוראות מעבר מסכימה אחת לשניה כך:
[code]
sqlt-diff current.sql=MySQL new.sql=MySQL 2> /dev/null > diff.sql
[/code]

זה יפיק הוראות מעבר (CREATE , ALTER, DROP) שיהיה אפשר להריץ על בסיס הנתונים כדי לישר קו בין הסכימות.
(אני שולח את stderr ל/dev/null כי יש אזהרות חסרות חשיבות מהסקריפט שנובעות מגרסת perl יותר חדשה מזו שהסקיפט פוחת עליה)
אני משתמש בסקריפט דומה לזה כדי להפוך את התהליך ליותר קל (הקובץ prod-struct.sql.gz מכיל את מבנה הסכימה שאני רוצה לקבל) :
[code]
#! /bin/bash
source db.conf
mysqldump –no-data -h $HOST -u $USER –password=$PASS $DB_NAME $TABLES > current.sql
zcat prod-struct.sql.gz > new.sql
sqlt-diff current.sql=MySQL new.sql=MySQL 2> /dev/null > diff.sql
cat diff.sql
echo "Type yes to apply changes"
read RESP
[[ $RESP == "yes" ]] || { rm -f current.sql diff.sql new.sql;exit; }
echo "updating database structure"
cat diff.sql | mysql -h $HOST -u $USER –password=$PASS $DB_NAME
rm current.sql new.sql diff.sql
[/code]

IP2C 2.0.0

שחררתי גרסא חדשה של IP2C, מהירה מתמיד וגם תומכת בבסיס הנתונים של Software77.

[code]
All tests done on an Intel(R) Core(TM)2 Duo CPU E8400 @ 3.00GH
PHP:

* No cache: 5000 searches/sec (Fast startup, slow query)
* Memory cache: : 5700 searches/sec (Slow startup, fastest query)

Java:

* No cache: 13900 searches/second. (Fast startup, slow query)
* Memory mapped file: 265k searches/second. (Fast startup, fast query)
* Memory cache: 281k searches/second. (Slow startup, fastest query)
[/code]