איך נבדוק האם קובץ כבר קיים ב-S3?
כך למדתי על metadata של קבצי zip כשניסיתי לחסוך תעבורה ל-s3.
למה בכלל חשוב לי לבדוק אם קובץ קיים כבר ב-S3?
יש לי פרויקט שרץ ב-AWS עם הרבה למבדות. במהלך ה-CD של הפרויקט אני מעדכן את הלמבדות בשינויים בקוד. כדי לעדכן את הלמבדה אני מעלה את הקוד ל-S3 ואז פונה ללמבדה עם הגרסה החדשה של הקובץ מ-S3. אם אני יודע שהקוד לא השתנה, אני יכול לחסוך כמה דקות של עדכון כל הלמבדות שלי ובנוסף חוסך תעבורה ל-S3 שעולה (ממש מעט) כסף .
מנגנון ה-ETag
לכל אובייקט ב-S3 יש ETag שהוא מזהה ייחודי של תוכן האובייקט. מנגנון היצירה של ה-ETag תלוי בצורה שבה הועלה הקובץ ל-S3. אם מעלים דרך ה-sdk עם הגדרות ברירת המחדל, ה-ETag יחושב על ידי MD5. אם הקובץ גדול מ-16 MB, לכל חתיכה של 16 MB מחושב ואז שוב מחושב על כל החתיכות ביחד, עם מספר נוסף שמסמן את מספר החתיכות.
1const chunk = 1024 * 1024 * 16; // 16MB
2
3const md5 = (data: Buffer) => crypto.createHash('md5').update(data).digest('hex');
4
5async function getEtagOfFile(stream: Buffer) {
6 if (stream.length <= chunk) {
7 return md5(stream);
8 }
9 const md5Chunks: string[] = [];
10 const chunksNumber = Math.ceil(stream.length / chunk);
11 for (let i = 0; i < chunksNumber; i += 1) {
12 const chunkStream = Uint8Array.prototype.slice.call(stream, i * chunk, (i + 1) * chunk);
13 md5Chunks.push(md5(chunkStream));
14 }
15
16 return `${md5(Buffer.from(md5Chunks.join(''), 'hex'))}-${chunksNumber}`;
17}
18
איך אני יודע? כתבתי את זה באינטרנט מזמן אז בטוח זה נכון.
אז הכל טוב?
הקוד למעלה עבד מצוין על המחשב שלי, אבל כשהרצתי אותו ב-Github Actions הוא נתן תוצאות שונות בכל פעם בכל פעם שבדקתי את ה-hash של קובץ ה-zip קיבלתי תוצאה שונה, למרות שהקוד נשאר זהה לחלוטין. כשניסיתי לשחזר את הבעיה מקומית, גיליתי (בעזרתו האדיבה של ChatGPT) שהבעיה נובעת מכך שתיקיית ה-dist נוצרת מחדש בכל build, וכך משתנים נתוני ה-metadata של הקבצים. אז גם אם הקוד זהה לחלוטין בקובץ ה-zip יש metadata של כל הקבצים שכולל את תאריך היצירה שלהם. מכיוון שבכל build תיקיית ה-dist נוצרת מחדש, אז יש בעייה שקובץ ה-zip יהיה תמיד שונה.
כדי לראות את את ה-metadata אפשר ליצור קובץ zip ואז לראות את ה-metadata באמצעות unzip -l
1zip -rq9 target.zip target_directory
2unzip -l target.zip > metadata.txt
3
אם ניצור מחדש את target_directory, אפילו אם הקוד יהיה זהה לחלוטין, ה-metadata ישתנה. בעיה נוספת שיכולה לצוץ היא סדר הקבצים בתוך ה-zip. אפילו אם הקוד זהה לחלוטין אבל משום מה ב-zip הם מסודרים שונה בכל פעם, ה-hash ישתנה בין הריצות/ אז מה עושים?
כך ניצור קובץ zip עם metadata זהה לחלוטין בכל ריצה
1# קודם נעבור על כל הקבצים ונקבע שתאריך היצירה שלהם הוא תאריך שרירותי כלשהוא
2find target_directory -exec touch -t 202401010000 {} + && \
3# במקום שם התיקיה אני משתמש בפקודה שממיינת את תוכן התיקיה בסדר אלפאנומרי, כך הסדר של הקבצים יישאר זהה תמיד
4zip -rq9 target.zip $(find target_directory -type f | sort)
5
בצורה הזו, כל עוד הקוד שלנו נשאר זהה, ה-metadata של ה-zip לא יפריע לנו. בצורה הזו אנחנו חוסכים זמן, עלויות קטנות, ובעיקר מבטיחים שה-build שלנו יהיה צפוי ויציב