לדלג לתוכן

שכבות אחסון ומערכת קבצים מרוחקת

קובץ יכול להסתיר מסלול ארוך

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

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

התקן לולאה במעבדה

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

set -eu
lab=$(mktemp -d /tmp/amit-storage.XXXXXX)
truncate -s 64M "$lab/disk.img"
loop=$(sudo losetup --find --show "$lab/disk.img")
mkdir "$lab/mount"
cleanup() {
    if mountpoint -q "$lab/mount"; then sudo umount "$lab/mount"; fi
    sudo losetup -d "$loop"
}
trap cleanup EXIT
sudo mkfs.ext4 -F "$loop"
sudo mount "$loop" "$lab/mount"
sudo sh -c 'printf "generation-one\n" > "$1/state"' sh "$lab/mount"
findmnt "$lab/mount"
lsblk -o NAME,TYPE,SIZE,FSTYPE,MOUNTPOINTS "$loop"
sudo sync -f "$lab/mount/state"
sudo umount "$lab/mount"
sudo losetup -d "$loop"
trap - EXIT
printf 'image retained at %s\n' "$lab/disk.img"

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

מיפוי ותמונה בנקודת זמן

מנגנון device mapper מאפשר להגדיר מטרות שמתרגמות בקשות בלוקים. מיפוי ליניארי, הצפנה ותמונה בנקודת זמן הם דוגמאות למטרות בעלות חוזים שונים. נוכל לצפות במיפויים קיימים באורח באמצעות dmsetup ls --tree, אך לא נשנה מיפוי שהמערכת משתמשת בו לצורך האתחול.

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

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

פרסום קובץ ועמידות הן שתי נקודות

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

use std::fs::{self, File, OpenOptions};
use std::io::{self, Write};
use std::path::Path;

fn replace(directory: &Path, bytes: &[u8]) -> io::Result<()> {
    let temporary = directory.join("state.pending");
    let destination = directory.join("state");
    let mut file = OpenOptions::new().write(true).create_new(true).open(&temporary)?;
    file.write_all(bytes)?;
    file.sync_all()?;
    drop(file);
    fs::rename(&temporary, &destination)?;
    File::open(directory)?.sync_all()?;
    Ok(())
}
fn main() -> io::Result<()> {
    let path = std::env::args_os().nth(1).expect("provide an existing lab directory");
    replace(Path::new(&path), b"version=1\nvalue=42\n")
}

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

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

מטמון מרוחק אינו מטמון מקומי

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

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

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

כשל רשת משנה את שאלת ההצלחה

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

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

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