ייצוא נתונים מיישומי אינטרנט: הטמעה במפתח סגור
על פי סעיף 20 של ה-GDPR, לכל נושא נתונים יש זכות לקבל את נתוניו האישיים בפורמט מובנה, נפוץ וקריא במכונה.
אבל בפועל, ייצוא של 500,000 שורות לעיתים קרובות מפיל את השרת: פסק זמן של 30 שניות ו-502 Bad Gateway. הסיבה היא ארכיטקטורה סינכרונית ומגבלות PHP. אנו פותרים זאת ברמת התשתית: יצירה אסינכרונית באמצעות תורים, כתיבה בזרימה ל-S3, ו-כתובות URL חתומות זמניות. הדרישות לפי GDPR (סעיף 20) מחייבות מתן נתוני משתמש אישיים בפורמט קריא במכונה — אנו מיישמים ייצוא במפתח סגור. צרו קשר להערכת הפרויקט שלכם.
בעיות שאנו פותרים
-
שאילתות N+1 במהלך ייצוא נתונים: בעת ייצוא הזמנות עם פריטים, כל שאילתת מסד נתונים יוצרת שאילתות חדשות — עבור 10,000 הזמנות זה 10,001 שאילתות. אנו משתמשים בטעינה עצלה באמצעות
lazy(200)וטעינה מוקדמת של קשרים. -
פסקי זמן ודלדול זיכרון עם ייצוא סינכרוני של יותר מ-100,000 שורות: סקריפט ה-PHP נתקל ב-
memory_limitוב-max_execution_time. הפתרון שלנו הוא עיבוד אסינכרוני באמצעות תורים עם פסק זמן של 600 שניות. - אי-התאמה בהידרציה במהלך SSR: אם נתוני ייצוא מוטמעים בצד הלקוח, React/Nuxt עלולים לאבד סנכרון. אנו מייצאים הכל דרך API עם מזהים ייחודיים.
מדוע ייצוא אסינכרוני עדיף לנתונים גדולים
ייצוא סינכרוני עובד רק לנפחים קטנים (<10k שורות). ככל שהנתונים גדלים, זה מוביל לחסימת ממשק ולניתוקי חיבור. תכנית אסינכרונית עם תור והתראות מסירה עומס מעובד ה-HTTP ומתרחבת אופקית. כאשר לקוח אחד מייצר דוח של 200k שורות, אחרים ממשיכים לעבוד ללא עיכובים.
דוגמה טיפוסית: לקוח ביקש ייצוא של 150,000 הזמנות ל-Excel. בתחילה, המפתח כתב סקריפט סינכרוני — ב-40 שניות, PHP קרס עקב memory_limit. לאחר מעבר לתור, הייצור ארך 2 דקות, והמשתמש קיבל דוא"ל עם קישור ללא כל חסימה.
| קריטריון | ייצוא סינכרוני | ייצוא אסינכרוני |
|---|---|---|
| נפח מקסימלי | עד 10,000 שורות | ללא הגבלה |
| חסימת עובד | כן | לא |
| פסק זמן | 30–60 שניות | 600+ שניות |
| התראה למשתמש | לא (המתנה) | דוא"ל / websocket |
| שילוב S3 | לא | כן (העלאה בזרימה) |
[User request] → [Create ExportJob record] → [Queue Worker] ↓
[Generate files] ↓
[Upload to S3 (zip)] ↓
[Email with download link] (signed URL, 7 days TTL) איך אנחנו עושים את זה: הטמעת Laravel
אנו משתמשים ב-Laravel עם PHP 8.3+ לשרת. התבנית המרכזית היא [User request] → [Create ExportJob record] → [Queue Worker] ↓ [Generate files] ↓ [Upload to S3 (zip)] ↓ [Email with download link] (signed URL, 7 days TTL) , שמכניסה משימה לתור. עובד ה-DataExportService מייצר קבצי פרופיל (JSON), הזמנות (CSV עם BOM), והודעות (JSON), ואז אורז אותם ל-ZIP להעלאה ל-S3.
class DataExportService {
public function exportUserData(User $user): Export {
$export = Export::create([
'user_id' => $user->id,
'status' => 'pending',
'expires_at' => now()->addDays(7),
]);
ExportUserDataJob::dispatch($user, $export);
return $export;
}
}
class ExportUserDataJob implements ShouldQueue {
public int $timeout = 600;
public function __construct(private User $user, private Export $export) {}
public function handle(): void {
$this->export->update(['status' => 'processing']);
$tempDir = sys_get_temp_dir() . '/export_' . $this->export->id;
mkdir($tempDir, 0777, true);
try {
// Профиль
file_put_contents(
"$tempDir/profile.json",
json_encode($this->exportProfile(), JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT)
);
// Заказы
$this->exportOrders("$tempDir/orders.csv");
// Сообщения
$this->exportMessages("$tempDir/messages.json");
// Создать ZIP
$zipPath = sys_get_temp_dir() . "/export_{$this->export->id}.zip";
$zip = new ZipArchive();
$zip->open($zipPath, ZipArchive::CREATE);
foreach (glob("$tempDir/*") as $file) {
$zip->addFile($file, basename($file));
}
$zip->close();
// Загрузить в S3
$s3Key = "exports/{$this->user->id}/{$this->export->id}/data.zip";
Storage::disk('s3')->put($s3Key, file_get_contents($zipPath));
$this->export->update([
'status' => 'ready',
's3_key' => $s3Key,
]);
$this->user->notify(new ExportReadyNotification($this->export));
} finally {
exec("rm -rf {$tempDir}");
if (file_exists($zipPath ?? '')) unlink($zipPath);
}
}
private function exportProfile(): array {
return [
'id' => $this->user->id,
'name' => $this->user->name,
'email' => $this->user->email,
'created_at' => $this->user->created_at->toIso8601String(),
'profile' => $this->user->profile?->toArray(),
];
}
private function exportOrders(string $path): void {
$handle = fopen($path, 'w');
fprintf($handle, chr(0xEF) . chr(0xBB) . chr(0xBF)); // UTF-8 BOM
fputcsv($handle, ['Номер', 'Дата', 'Сумма', 'Статус', 'Позиции'], ';');
$this->user->orders()->with('items')->lazy(200)->each(function (Order $order) use ($handle) {
$items = $order->items->map(fn($i) => "{$i->name} x{$i->quantity}")->join(', ');
fputcsv($handle, [
$order->number,
$order->created_at->format('d.m.Y H:i'),
number_format($order->total, 2),
$order->status,
$items,
], ';');
});
fclose($handle);
}
private function exportMessages(string $path): void {
$messages = $this->user->messages()
->select('id', 'subject', 'body', 'created_at')
->get()
->map(fn($m) => [
'id' => $m->id,
'subject' => $m->subject,
'body' => strip_tags($m->body),
'date' => $m->created_at->toIso8601String(),
]);
file_put_contents($path, json_encode($messages, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT));
}
public function failed(\Throwable $e): void {
$this->export->update(['status' => 'failed']);
Log::error('Export failed', ['export_id' => $this->export->id, 'error' => $e->getMessage()]);
}
} איך כתובות URL חתומות עובדות להורדה
כדי להימנע מאחסון ארכיונים ללא הגבלת זמן, אנו משתמשים בקישורי S3 זמניים עם TTL של 15 דקות. המשתמש מקבל דוא"ל עם קישור ייחודי שמפנה ישירות ל-S3 — תוך עקיפת השרת שלנו.
public function download(Export $export): RedirectResponse
{
$this->authorize('download', $export);
if ($export->status !== 'ready' || $export->expires_at->isPast()) {
abort(410, 'Экспорт недоступен или устарел');
}
$url = Storage::disk('s3')->temporaryUrl($export->s3_key, now()->addMinutes(15));
return redirect()->away($url);
} בחירת פורמט הייצוא הנכון
הפורמט תלוי במטרה: JSON ל-API ולחילוץ GDPR, CSV עם BOM לטבלאות ול-Excel, XLSX עם עיצוב לחשבונאות, XML לאינטגרציות B2B. אם יש צורך במספר קבצים, אנו משתמשים ב-ZIP.
| פורמט | מקרה שימוש | כלי |
|---|---|---|
| JSON | API, חילוץ GDPR | PHP json_encode, Node JSON.stringify |
| CSV | טבלאות, Excel | fputcsv, csv-writer |
| XLSX | חשבונאות, אנליטיקה | PhpSpreadsheet, ExcelJS |
| XML | אינטגרציות B2B | SimpleXML, xmlbuilder2 |
| ZIP | ארכיון של מספר קבצים | ZipArchive, archiver (Node) |
התהליך שלנו
- ניתוח — אנו לומדים את מבנה הנתונים, הנפחים ודרישות הפורמט.
- עיצוב — בחירת מחסנית טכנולוגית (תור, אחסון), עיצוב סכמת מסד נתונים לייצוא.
- פיתוח — כתיבת שירותים, עובדים, בקרים ובדיקות.
- בדיקות — בדיקות עומס עם נפחים אמיתיים, בדיקות פסק זמן.
- פריסה — הגדרת תורים (Redis, SQS), S3, משימות ניקוי תקופתיות.
- תיעוד — מסירת מסמכי ארכיטקטורה והוראות.
מה כלול
- תיעוד ארכיטקטוני עם תיאורי זרימת נתונים.
- קוד מקור עם הערות וטיפוסים קפדניים.
- הוראות פריסה עם דוגמאות להגדרת תור ו-S3.
- הכשרה לצוות תמיכה.
- אחריות ל-3 חודשים לפעולה ללא הפרעות.
לוח זמנים ותמחור
ייצוא נתוני משתמש בסיסי (JSON + CSV ב-ZIP): 2–3 ימים. עם תור, S3 והתראת דוא"ל: 3–4 ימים. ייצוא תואם GDPR של כל הנתונים האישיים: 4–5 ימים. התמחור נקבע באופן אישי לפי נפחי נתונים ומספר הפורמטים. לצוות שלנו ניסיון של למעלה מ-10 שנים וביצע 40+ פרויקטי ייצוא נתונים. הזמינו ייצוא לפרויקט שלכם — קבלו ייעוץ חינמי והערכה תוך יום אחד.
צרו קשר כדי לדון בפרויקט שלכם ולקבל הערכה מדויקת.







