How It Works: Media Ingestion Flow#
Overview#
This document provides a comprehensive visual flow of the media ingestion process in the Multimodal DataPrep microservice, from initial upload through enrichment, embedding generation, and final storage in the vector database. It covers two media kinds:
Video — the deep-dive that follows (frame extraction → object detection → parallel batch embedding → storage), optimized for performance with parallel processing, batch operations, and the in-process embedding pipeline. See Detailed Video Ingestion Flow.
Image — a simpler, frame-less path that embeds the image directly (plus optional object-detection crops). See Image Ingestion Flow.
Both kinds converge on the same embedding model, the same shared vector collection, and the same storage/deduplication/metadata handling; records are distinguished by a content_type field (video / image). Storage and vector backends are pluggable (MinIO/local, VDMS/Milvus); the diagrams below label VDMS/MinIO as the default example.
High-Level Architecture#
---
config:
theme: dark
---
graph TB
subgraph "Entry Points"
A1[POST /media/upload<br/>Direct File Upload]
A2[POST /media/process<br/>Process from MinIO]
end
subgraph "Core Processing Pipeline"
C[Frame Extraction]
D[Object Detection]
E[Batch Creation]
F[Parallel Processing]
G[Embedding Generation]
H[Vector DB Storage]
end
subgraph "Storage Layer"
I[(VDMS Vector Database)]
J[(MinIO Object Storage)]
end
A1 --> J
A1 --> C
A2 --> J
A2 --> C
C --> D
D --> E
E --> F
F --> G
G --> H
H --> I
Detailed Video Ingestion Flow#
Stage 1: Video Upload & Initial Processing#
---
config:
theme: dark
---
flowchart TD
START([Video Upload Request]) --> ENTRY{Entry Point?}
ENTRY -->|Direct Upload| UPLOAD[POST /media/upload<br/>File: video.mp4<br/>Params: frame_interval, enable_detection]
ENTRY -->|MinIO Reference| MINIO[POST /media/process<br/>Params: bucket_name, video_id]
UPLOAD --> VALIDATE1[Validate File<br/>- Check MP4 format<br/>- Check size limit 500MB<br/>- Validate parameters]
MINIO --> VALIDATE2[Validate MinIO Path<br/>- Check bucket exists<br/>- Verify video_id directory<br/>- Resolve stored video]
VALIDATE1 --> STORE[Store Video to MinIO<br/>Path: bucket/video_id/filename.mp4]
VALIDATE2 --> DOWNLOAD[Download from MinIO<br/>Get video content]
STORE --> CONFIG[Load Configuration<br/>• frame_interval default: 15<br/>• enable_object_detection: true<br/>• detection_confidence: 0.85]
DOWNLOAD --> CONFIG
CONFIG --> FRAME_EXTRACT[Stage 2: Frame Extraction<br/>✓ In-process embeddings<br/>✓ Memory-based processing<br/>✓ OpenVINO optimized]
Key Decisions:
Entry Point Selection: Direct upload saves to MinIO first; MinIO processing retrieves from existing storage
Configuration Priority: Request params → Config file defaults → Service defaults
Performance Factors:
File validation is minimal overhead (~ms)
MinIO upload/download depends on video size and network
In-process embedding eliminates network latency
Stage 2: Frame Extraction & Metadata Creation#
---
config:
theme: dark
---
flowchart TD
START[Frame Extraction Stage] --> VIDEO_INFO[Read Video Information<br/>Using Decord VideoReader]
VIDEO_INFO --> CALC[Calculate Video Metrics<br/>• Total Frames: len vr<br/>• FPS: vr.get_avg_fps<br/>• Duration: frames / fps]
CALC --> INTERVAL[Calculate Frame Indices<br/>frame_indices = range 0, total_frames, frame_interval<br/><br/>Example: 900 frames, interval=15<br/>→ Extract 60 frames]
INTERVAL --> EXTRACT_LOOP{For each<br/>frame_index}
EXTRACT_LOOP --> SEEK[Seek to Frame<br/>vr at frame_idx]
SEEK --> CONVERT[Convert Tensor to NumPy<br/>• Handle decord NDArray<br/>• Ensure uint8 format<br/>• Verify shape H,W,C]
CONVERT --> META["Create Frame Metadata<br/>frame_metadata = <br/>• frame_id: video_id_framenum<br/>• frame_number<br/>• timestamp: frame_idx/fps<br/>• frame_type: 'full_frame'<br/>• content_type: 'video'<br/>• video_id, filename, bucket<br/>• video_url, video_rel_url<br/>• source_path (dir ingest)<br/>• tags, user metadata<br/>• fps, total_frames, duration"]
META --> STORE_FRAME[Store Frame Array<br/>frames.append frame_numpy<br/>frames_metadata.append metadata]
STORE_FRAME --> MORE{More frames?}
MORE -->|Yes| EXTRACT_LOOP
MORE -->|No| COMPLETE[Extraction Complete<br/>Result: List of numpy arrays + metadata]
COMPLETE --> DETECT_STAGE[Stage 3: Object Detection]
Optimization Highlights:
Decord Library: GPU-capable video reading (though currently using CPU context for reliability)
Memory Efficiency: Frames stored as numpy arrays, not written to disk between stages
Metadata Richness: Comprehensive frame metadata for search/retrieval
Performance Metrics:
Frame Extraction Time: Typically 0.5-2s for 60 frames from 30s video
Memory Usage: ~4MB per 1080p frame (uncompressed)
Extraction Rate: ~30-100 frames/second depending on video resolution
Stage 3: Object Detection (Optional)#
---
config:
theme: dark
---
flowchart TD
START[Object Detection Stage] --> CHECK{Object Detection<br/>Enabled?}
CHECK -->|Disabled| SKIP[Skip Detection<br/>Use full frames only<br/>frames remain unchanged]
CHECK -->|Enabled| INIT[Initialize YOLOX Detector<br/>• Model: yolox_s<br/>• Device: CPU/GPU<br/>• Confidence: 0.85<br/>• Input Size: 640x640<br/>• NMS Threshold: 0.45]
INIT --> BATCH_DETECT[Create Detection Batches<br/>detection_batch_size = 32<br/><br/>Example: 60 frames → 2 batches]
BATCH_DETECT --> PARALLEL_DETECT{Process Batches<br/>in Parallel}
PARALLEL_DETECT --> BATCH_PROC[For Each Batch]
BATCH_PROC --> FRAME_LOOP{For each frame<br/>in batch}
FRAME_LOOP --> CONVERT_PIL[Convert Frame to PIL<br/>Image.fromarray frame_numpy]
CONVERT_PIL --> DETECT[Run YOLOX Detection<br/>detector.detect frame_pil]
DETECT --> ADD_FULL[Add Full Frame<br/>frame_type: full_frame<br/>Keep original frame metadata]
ADD_FULL --> CROPS{Detections<br/>Found?}
CROPS -->|Yes| CROP_LOOP{For each<br/>detection}
CROP_LOOP --> EXTRACT_CROP[Extract Crop<br/>• Validate bbox coordinates<br/>• Crop: frame y1:y2, x1:x2<br/>• Convert to PIL Image<br/>• Min size: 10x10 pixels]
EXTRACT_CROP --> CROP_META[Create Crop Metadata<br/>crop_metadata = frame_metadata + <br/>• frame_type: detected_crop<br/>• is_detected_crop: true<br/>• crop_index<br/>• detection_confidence<br/>• crop_bbox x1 y1 x2 y2<br/>• detected_class_id<br/>• detected_label]
CROP_META --> ADD_CROP[Add Crop to Results<br/>all_images.append crop_pil<br/>all_metadata.append crop_meta]
ADD_CROP --> MORE_CROPS{More crops?}
MORE_CROPS -->|Yes| CROP_LOOP
MORE_CROPS -->|No| MORE_FRAMES
CROPS -->|No| MORE_FRAMES{More frames?}
MORE_FRAMES -->|Yes| FRAME_LOOP
MORE_FRAMES -->|No| BATCH_DONE[Batch Complete]
BATCH_DONE --> MORE_BATCHES{More batches?}
MORE_BATCHES -->|Yes| PARALLEL_DETECT
MORE_BATCHES -->|No| DETECTION_DONE[Detection Complete<br/><br/>Example Expansion:<br/>60 frames → 240 items<br/>60 full frames + 180 crops<br/>avg 3 objects/frame]
SKIP --> BATCH_CREATE
DETECTION_DONE --> BATCH_CREATE[Stage 4: Batch Creation]
Object Detection Details:
Model Specifications:
Architecture: YOLOX-S (small variant)
Framework: OpenVINO IR format
Input Resolution: 640x640 (preprocessed)
Classes: 80 COCO categories (person, car, bicycle, etc.)
Download: Auto-downloaded from GitHub on first use
Detection Process:
Preprocessing: Image resized to 640x640, normalized
Inference: OpenVINO execution on CPU/GPU
Postprocessing: NMS filtering with threshold 0.45
Crop Extraction: Bounding boxes validated and extracted
Performance Characteristics:
Detection Speed: ~50-100ms per frame (CPU), ~10-20ms (GPU)
Parallel Batches: 2-4 detection workers typical
Expansion Factor: 1.5x to 5x (avg 3 objects/frame)
Memory Impact: +20-50% for crop storage
Optimization Strategy:
Batched detection reduces overhead
Parallel processing utilizes multi-core CPUs
Global detector instance reused (no reload per request)
Crops validated (min 10x10px, valid coordinates)
Stage 4: Batch Creation for Parallel Processing#
---
config:
theme: dark
---
flowchart TD
START[Batch Creation Stage] --> INPUT[Input: List of Images + Metadata<br/><br/>After Detection:<br/>• Full frames: 60<br/>• Detected crops: 180<br/>• Total items: 240]
INPUT --> CONFIG[Get Pipeline Configuration<br/>Based on CPU cores and mode]
CONFIG --> CALC_WORKERS[Calculate Worker Count<br/><br/>OpenVINO Mode:<br/>workers = max 1, cpu_cores // 4<br/>Limited by OV_NUM_STREAMS<br/><br/>PyTorch Mode:<br/>workers = cpu_cores // 16<br/>max 8 workers]
CALC_WORKERS --> CALC_BATCH[Determine Batch Size<br/>batch_size = 32<br/>Optimal for embedding generation]
CALC_BATCH --> CREATE_BATCHES[Create Processing Batches<br/>Split items into batches<br/><br/>Example:<br/>240 items ÷ 32 = 7.5<br/>→ 8 batches<br/>7 batches of 32 items<br/>1 batch of 16 items]
CREATE_BATCHES --> BATCH_STRUCT[Batch Structure<br/>Each batch contains:<br/>• List of PIL Images<br/>• List of metadata dicts<br/>• Batch index<br/>• Total batch count]
BATCH_STRUCT --> SUBMIT[Submit to Thread Pool<br/>ThreadPoolExecutor<br/>max_workers = pipeline_count<br/><br/>Example: 8 batches → 4 workers<br/>2 batches processed simultaneously]
SUBMIT --> PARALLEL[Stage 5: Parallel Processing]
Pipeline Configuration Logic:
# Pseudo-code for worker calculation
cpu_cores = multiprocessing.cpu_count()
if use_openvino:
base_workers = max(1, cpu_cores // 4)
# Check OpenVINO environment variables
ov_limit = check_env_vars([
'OV_PERFORMANCE_HINT_NUM_REQUESTS',
'PERFORMANCE_HINT_NUM_REQUESTS',
'OV_NUM_STREAMS'
])
if ov_limit:
workers = min(base_workers, ov_limit)
else:
workers = base_workers
else:
# PyTorch is more CPU-intensive
workers = min(max(1, cpu_cores // 16), 8)
batch_size = 32 # Fixed optimal size
Configuration Examples:
CPU Cores |
OpenVINO Mode |
PyTorch Mode |
|---|---|---|
8 cores |
2 workers |
1 worker |
16 cores |
4 workers |
1 worker |
32 cores |
8 workers |
2 workers |
64 cores |
16 workers |
4 workers |
96 cores |
24 workers |
6 workers |
Batch Size Considerations:
32 items per batch: Optimal balance for embedding models
Smaller batches: Lower memory, higher overhead
Larger batches: Higher memory, better throughput
Dynamic adjustment: Future enhancement based on available memory
Stage 5: Parallel Batch Processing Pipeline#
---
config:
theme: dark
---
flowchart TD
START[Parallel Processing Stage] --> POOL[Thread Pool Executor<br/>max_workers = pipeline_count<br/><br/>Example: 4 workers<br/>Processing 8 batches]
POOL --> SUBMIT_ALL[Submit All Batches<br/>All batches queued<br/>Workers pick up jobs dynamically]
SUBMIT_ALL --> WORKER1[Worker 1<br/>Process Batch]
SUBMIT_ALL --> WORKER2[Worker 2<br/>Process Batch]
SUBMIT_ALL --> WORKER3[Worker 3<br/>Process Batch]
SUBMIT_ALL --> WORKER4[Worker 4<br/>Process Batch]
WORKER1 --> BATCH_PROC1[Single Batch Processing]
WORKER2 --> BATCH_PROC1
WORKER3 --> BATCH_PROC1
WORKER4 --> BATCH_PROC1
subgraph "Single Batch Processing Pipeline"
BATCH_PROC1[Start Batch Processing<br/>Input: 32 images + metadata]
BATCH_PROC1 --> STEP1[Step 1: Validation<br/>Check model supports images<br/>Skip if text-only model]
STEP1 --> STEP2[Step 2: Generate Embeddings<br/>In-process function call]
STEP2 --> EMB_DETAIL[Embedding Generation Details]
subgraph "Embedding Generation"
EMB_DETAIL --> EMB_LOCAL_PROC[In-process Embedding<br/>• Use global embedding client<br/>• Thread-safe infer_new_request<br/>• No HTTP overhead<br/>• OpenVINO optimized<br/>• Batch processing inside model]
EMB_LOCAL_PROC --> EMB_RESULT[Embedding Results<br/>Vector dimensions: 512/768/1024<br/>Format: List of float arrays]
end
EMB_RESULT --> STEP3[Step 3: Validate Embeddings<br/>Filter out null/failed embeddings<br/>Match embeddings to metadata]
STEP3 --> STEP4[Step 4: Store to Vector DB<br/>Immediate batch storage<br/>Prevents OutOfJournalSpace]
STEP4 --> STORAGE_DETAIL[VDMS Storage Details]
end
subgraph "VDMS Vector DB Storage"
STORAGE_DETAIL --> VDB_PREP[Prepare Storage Request<br/>• Embeddings: List of vectors<br/>• Metadata: List of dicts<br/>• Collection: MM_DATAPREP_DB_COLLECTION]
VDB_PREP --> VDB_BULK[Bulk Insert Operation<br/>AddEntity with AddDescriptor<br/>Batch operation more efficient]
VDB_BULK --> VDB_INDEX[Vector Index Update<br/>VDMS updates HNSW index<br/>Enables similarity search]
VDB_INDEX --> VDB_RETURN[Return Stored IDs<br/>List of entity IDs<br/>Used for verification]
end
VDB_RETURN --> BATCH_DONE[Batch Complete<br/>Return results:<br/>• embeddings_count<br/>• stored_ids<br/>• processing_time<br/>• detection_time<br/>• embedding_time<br/>• storage_time]
BATCH_DONE --> COLLECTOR[Results Collector<br/>as_completed future]
COLLECTOR --> MORE{More batches<br/>pending?}
MORE -->|Yes| COLLECTOR
MORE -->|No| AGGREGATE[Aggregate All Results<br/>Sum embeddings<br/>Combine stored_ids<br/>Calculate statistics]
AGGREGATE --> FINAL[Stage 6: Final Results]
Parallel Processing Characteristics:
Thread Pool Execution:
Dynamic Work Distribution: Batches processed as workers become available
True Parallelism: Multiple batches processed simultaneously
Completion Order: Batches complete in any order (not sequential)
Timeout Protection: 300s per batch maximum
Embedding Generation Characteristics:
Aspect |
In-process pipeline |
|---|---|
Method |
Direct function call |
Network |
None |
Serialization |
None (PIL objects) |
Latency |
~50-200ms/batch |
Memory |
Shared with service |
Optimization |
OpenVINO compiled when enabled |
Vector DB Storage Strategy:
Why Immediate Batch Storage:
Prevents Memory Overflow: Storing after each batch prevents accumulation
Protects Against Failures: Partial results saved even if pipeline fails
Avoids VDMS Issues: Prevents OutOfJournalSpace errors
Progress Tracking: Stored IDs returned incrementally
Bulk Insert Benefits:
Reduced Overhead: Single VDMS transaction per batch
Index Efficiency: VDMS can optimize index updates
Faster Than Individual: ~10x faster than per-item inserts
Stage 6: Results Aggregation & Performance Metrics#
---
config:
theme: dark
---
flowchart TD
START[Results Aggregation] --> COLLECT[Collect All Batch Results<br/>From ThreadPoolExecutor.as_completed]
COLLECT --> AGGREGATE[Aggregate Statistics]
subgraph "Statistics Calculation"
AGGREGATE --> COUNT[Total Embeddings<br/>Sum all batch counts<br/><br/>Example: 8 batches<br/>7×32 + 1×16 = 240 embeddings]
COUNT --> IDS[Stored IDs<br/>Concatenate all ID lists<br/>Verify no duplicates]
IDS --> TIMES[Processing Times<br/>Calculate per-stage statistics]
TIMES --> TIME_DETAIL[Time Breakdown]
subgraph "Time Statistics"
TIME_DETAIL --> EXTRACT_TIME[Frame Extraction<br/>Total time: e.g., 1.2s<br/>Frames extracted: 60]
EXTRACT_TIME --> DETECT_TIME["Object Detection<br/>Avg per batch: e.g., 0.8s<br/>Max per batch: e.g., 1.2s<br/>% of batch time: e.g., 25%"]
DETECT_TIME --> EMBED_TIME["Embedding Generation<br/>Avg per batch: e.g., 2.1s<br/>Max per batch: e.g., 3.5s<br/>% of batch time: e.g., 65%"]
EMBED_TIME --> STORE_TIME["Vector DB Storage<br/>Avg per batch: e.g., 0.3s<br/>Max per batch: e.g., 0.5s<br/>% of batch time: e.g., 10%"]
STORE_TIME --> TOTAL_TIME[Total Pipeline Time<br/>Wall clock time: e.g., 8.5s<br/>Sequential would be: ~28s<br/>Speedup: 3.3x]
end
end
TIME_DETAIL --> FRAME_COUNTS[Frame Count Summary]
subgraph "Frame Flow Tracking"
FRAME_COUNTS --> INPUT_FRAMES[Input Frames<br/>Extracted from video: 60]
INPUT_FRAMES --> POST_DETECT[Post-Detection Items<br/>After object detection: 240<br/>Expansion: 4x]
POST_DETECT --> STORED_EMBS["Stored Embeddings<br/>Successfully stored: 240<br/>Success rate: 100%"]
end
STORED_EMBS --> BATCH_STATS[Batch Statistics]
subgraph "Batch Performance"
BATCH_STATS --> BATCH_COUNT[Batches Processed<br/>Total: 8 batches]
BATCH_COUNT --> AVG_BATCH[Average Batch Time<br/>e.g., 3.5s per batch]
AVG_BATCH --> MAX_BATCH[Max Batch Time<br/>e.g., 4.2s slowest batch]
MAX_BATCH --> EFFICIENCY["Processing Efficiency<br/>Parallel overhead: ~15%<br/>Resource utilization: 85%"]
end
EFFICIENCY --> RETURN_RESULT[Return Final Result]
subgraph "API Response"
RETURN_RESULT --> RESPONSE[HTTP Response<br/>Status: 201 CREATED<br/>Message: Embeddings created successfully]
RESPONSE --> RESPONSE_BODY[Response Body Example]
RESPONSE_BODY --> JSON[JSON Response:<br/>status: success<br/>message: Embeddings created<br/>total_embeddings: 240<br/>total_frames_processed: 60<br/>frame_interval: 15<br/>timing: extraction, parallel, storage<br/>frame_counts: extracted, detected, stored]
end
JSON --> COMPLETE([Processing Complete])
Performance Metrics Explained:
Time Statistics:
Frame Extraction Time: Time to read video and extract frames
Depends on: Video size, resolution, codec, storage speed
Typical: 0.5-3s for 60 frames
Detection Time per Batch: Time for object detection per batch
Depends on: Frame resolution, object count, CPU/GPU speed
Typical: 0.5-2s per batch of 32 items
Embedding Time per Batch: Time to generate embeddings
Depends on: Model size, device, batch size
Typical: 1-3s per batch
Storage Time per Batch: Time to store in VDMS
Depends on: Batch size, VDMS load, network latency
Typical: 0.2-0.8s per batch
Pipeline Wall Time: Total end-to-end time
Benefits from parallelization
Typical speedup: 2-4x vs sequential
Frame Flow Tracking:
The system tracks three critical counts:
Extracted Frames: Original frames from video (e.g., 60)
Post-Detection Items: After adding crops (e.g., 240 = 60 + 180 crops)
Stored Embeddings: Successfully stored in vector DB (e.g., 240)
Efficiency Calculations:
Expansion Factor = Post-Detection Items / Extracted Frames
= 240 / 60 = 4x
Success Rate = Stored Embeddings / Post-Detection Items
= 240 / 240 = 100%
Theoretical Sequential Time = Batches × Max Batch Time
= 8 × 4.2s = 33.6s
Actual Parallel Time = 8.5s
Speedup = 33.6s / 8.5s = 3.95x
Efficiency = (Theoretical Sequential / Workers) / Actual
= (33.6s / 4) / 8.5s = 8.4s / 8.5s = 98.8%
Image Ingestion Flow#
Images take a deliberately frame-less path: there is no decode loop and no
frame sampling. A single image is embedded once, optionally augmented with
object-detection crops, and stored in the same shared vector collection as video
records — tagged with content_type="image".
Entry points and transports#
Endpoint |
Content type |
Image source |
|---|---|---|
|
|
binary file bytes |
|
|
inline base64 ( |
|
|
list of base64/URL image sources (async job) |
|
|
image already stored in object storage |
For base64/URL inputs the decoded bytes are the trust boundary: the real
format is sniffed from the bytes to derive the stored extension (the
client-declared type is never trusted for the stored file), and the size is
capped before any processing.
Stage flow#
---
config:
theme: dark
---
graph TB
subgraph "Entry"
U1[POST /media/upload<br/>multipart bytes]
U2[POST /media/ingest<br/>base64 / URL]
end
subgraph "Resolve & Validate"
R1[Decode base64 / download URL]
R2[Sniff real format<br/>derive extension]
R3[Enforce size cap]
R4{Dedup enabled?}
R5[SHA-256 + hash marker<br/>409 on duplicate]
end
subgraph "Persist Asset"
S1[(Object Storage<br/>MinIO / local)]
end
subgraph "Embed"
E1[Full-image embed<br/>frame_type=full_frame]
E2{Object detection?}
E3[Per-crop embed]
end
subgraph "Store Vectors"
V1[Attach metadata:<br/>content_type=image,<br/>download URL, tags]
V2[(Vector DB<br/>VDMS / Milvus)]
end
U1 --> R2
U2 --> R1 --> R2
R2 --> R3 --> R4
R4 -- yes --> R5 --> S1
R4 -- no --> S1
S1 --> E1
E1 --> E2
E2 -- enabled --> E3 --> V1
E2 -- disabled --> V1
E1 --> V1
V1 --> V2
How it differs from the video flow#
Aspect |
Video |
Image |
|---|---|---|
Decode / frame sampling |
Yes — every Nth frame ( |
None — single image |
Records produced |
1 full frame + N crops per sampled frame |
1 full image + optional crops |
Timestamp metadata |
Per-frame timestamp |
Single (upload-time) reference |
|
|
|
Base64 / URL transport |
No |
Yes ( |
Parallel batch pipeline |
Per-frame fan-out |
Batched across images in a job |
Object detection |
Optional, per frame |
Optional, per image |
Storage / dedup / download |
Shared |
Shared (identical) |
Because images reuse the same embedding model and vector contract as video frames, image and video records are directly comparable in a single similarity search, enabling cross-modal retrieval.
Complete End-to-End Flow Visualization#
---
config:
theme: dark
---
graph TB
subgraph "Stage 1: Video Upload"
A[Video Upload<br/>POST /media/upload or /media/process] --> C[Memory Processing]
end
subgraph "Stage 2: Frame Extraction"
C --> E[Extract Frames<br/>Decord VideoReader]
E --> F[Frame List<br/>60 frames @ interval=15<br/>Time: 1.2s]
end
subgraph "Stage 3: Object Detection Optional"
F --> G{Object Detection<br/>Enabled?}
G -->|Yes| H[YOLOX Detection<br/>Parallel batches<br/>Time: 2.4s total]
G -->|No| I[Skip Detection]
H --> J[Expanded List<br/>240 items<br/>60 frames + 180 crops]
I --> K[Original List<br/>60 items]
end
subgraph "Stage 4: Batch Creation"
J --> L[Create Batches<br/>8 batches of 32 items<br/>batch_size=32]
K --> L
L --> M[Calculate Workers<br/>4 parallel workers<br/>OpenVINO optimized]
end
subgraph "Stage 5: Parallel Processing"
M --> N[Submit to ThreadPool<br/>Process batches in parallel]
N --> O1[Worker 1: Batch 1<br/>32 items → embeddings → store]
N --> O2[Worker 2: Batch 2<br/>32 items → embeddings → store]
N --> O3[Worker 3: Batch 3<br/>32 items → embeddings → store]
N --> O4[Worker 4: Batch 4<br/>32 items → embeddings → store]
O1 --> P[Collect Results<br/>as batches complete]
O2 --> P
O3 --> P
O4 --> P
end
subgraph "Stage 6: Storage & Results"
P --> Q[Aggregate Statistics<br/>240 embeddings stored<br/>Pipeline time: 8.5s]
Q --> R[(VDMS Vector DB<br/>240 vectors indexed)]
Q --> S[Return Response<br/>Status: 201 CREATED<br/>Details: timing + counts]
end
Performance Optimization Summary#
Critical Optimizations#
Parallel Processing
Implementation: ThreadPoolExecutor with dynamic worker count
Impact: 3-4x speedup vs sequential processing
Configuration: Auto-calculated based on CPU cores and runtime backend
Batch Storage
Implementation: Store after each batch instead of all at end
Impact: Prevents memory overflow and VDMS journal errors
Benefit: Fault tolerance - partial results saved
In-process embedding
Implementation: Direct function calls to the embedding package
Impact: Eliminates HTTP/network overhead
Object Detection Optimization
Implementation: Global detector instance, parallel detection batches
Impact: Avoids model reload, utilizes multi-core CPUs
Caching: Detector initialized once, reused across requests
Memory-Based Processing
Implementation: Process video directly from bytes in memory
Impact: Eliminates disk I/O overhead
Benefit: Lower latency, reduced disk wear
Configuration Parameters#
Parameter |
Default |
Impact |
Tuning Guide |
|---|---|---|---|
|
15 |
Frame extraction density |
Lower = more frames (slower, more detail) |
|
32 |
Items per embedding batch |
Fixed optimal value |
|
auto |
Parallel workers |
CPU cores ÷ 4 (OpenVINO) or ÷ 16 (PyTorch) |
|
0.85 |
Object detection threshold |
Higher = fewer detections (faster) |
|
true |
Crop extraction |
Disable for 4x fewer embeddings (faster) |
Performance Expectations#
Example Video: 30 seconds, 30fps (900 frames), 1920x1080
Configuration |
Frames Extracted |
Items After Detection |
Total Time |
Speedup |
|---|---|---|---|---|
interval=15, detection=ON, in-process |
60 |
240 (avg 3 crops/frame) |
8.5s |
3.95x |
interval=15, detection=OFF, in-process |
60 |
60 |
4.2s |
4.2x |
interval=30, detection=ON, in-process |
30 |
120 |
4.8s |
3.8x |
Timing Breakdown (Detection ON):
Frame Extraction: 1.2s (14%)
Object Detection: 2.4s (28%)
Embedding Generation: 4.2s (49%)
Vector DB Storage: 0.7s (8%)
Total Pipeline: 8.5s (100%)
System Architecture Context#
Component Interaction#
---
config:
theme: dark
---
graph LR
subgraph "Multimodal DataPrep Microservice"
A[FastAPI Endpoints] --> B[Video Processing]
B --> C[Frame Extraction]
C --> D[Object Detection<br/>YOLOX]
D --> E[Embedding Helper]
end
subgraph "Embedding Package"
G[In-process Direct Functions]
end
subgraph "Storage Layer"
H[(VDMS Vector DB)]
I[(MinIO Object Storage)]
end
E --> G
G --> H
A --> I
Service Dependencies#
VDMS Vector Database
Stores embeddings with metadata
Provides similarity search
HNSW index for fast retrieval
MinIO Object Storage
Stores original videos
Bucket-based organization
Provides video download URLs
Multimodal Embedding Package
Generates embeddings from images
OpenVINO or PyTorch backend
YOLOX Object Detection
OpenVINO IR format
CPU/GPU inference
Auto-downloads model files
Troubleshooting & Monitoring#
Key Metrics to Monitor#
Frame Extraction Rate: Frames/second during extraction
Target: >30 frames/second
Alert: <10 frames/second indicates I/O bottleneck
Object Detection Time: Average seconds per batch
Target: <2s per batch of 32 items
Alert: >5s indicates CPU/GPU overload
Embedding Generation Time: Average seconds per batch
Target: <3s per batch
Alert: >10s indicates model/network issues
Storage Time: Average seconds per batch
Target: <1s per batch
Alert: >3s indicates VDMS performance issues
Success Rate: Stored embeddings / Expected embeddings
Target: >95%
Alert: <90% indicates embedding failures
Common Performance Issues#
Issue |
Symptom |
Cause |
Solution |
|---|---|---|---|
Slow extraction |
High frame_extraction_time |
Large video, slow storage |
Use faster storage, reduce resolution |
Detection bottleneck |
High detection_time |
CPU overload |
Enable GPU, reduce confidence threshold |
Embedding slowdown |
High embedding_time |
Model overload |
Increase workers, enable OpenVINO |
Storage delays |
High storage_time |
VDMS overload |
Check VDMS resources, reduce batch size |
Memory errors |
Process killed |
Too many workers |
Reduce pipeline_count |
Logs to Check#
# Frame extraction progress
"Extracting X frames with interval Y"
"Frame extraction completed in Xs: Y frames"
# Object detection status
"Processing object detection in batches of X"
"Detection batch processed: Y frames → Z items"
# Parallel processing progress
"Processing X frames with Y maximum parallel workers"
"Batch N/M completed: X embeddings stored"
# Final results
"Embedding generation pipeline completed in Xs: Y embeddings across Z batches"
"Frame flow summary: extracted=X -> after_detection=Y -> stored=Z"
Conclusion#
The Multimodal DataPrep ingestion pipeline is a highly optimized system that efficiently processes both video and images for semantic search. The video path (detailed above) applies frame extraction and parallel batch embedding; the image path embeds directly. Both converge on the same embedding model and shared vector collection. Key achievements:
Parallel Processing: 3-4x speedup through multi-threaded execution
Batch Storage: Prevents memory overflow with incremental saves
In-process Optimization: Eliminates HTTP overhead
Object Detection: Expands search coverage with detected crops
Memory Efficiency: Direct memory processing between stages
Comprehensive Metrics: Detailed timing and statistics for optimization
Total Processing Time: 8-10 seconds for a 30-second video (detection ON)
Scalability: Handles videos up to 500MB, auto-configures workers based on available resources
References#
Source Code:
/home/sdp/workbench/integration/edge-ai-libraries-mme-v2/microservices/visual-data-preparation-for-retrieval/multimodal-dataprep/Configuration:
src/config.yamlAPI Endpoints:
src/endpoints/video_processing/Core Processing:
src/core/embedding/embedding_helper.pyObject Detection:
src/core/object_detection/detector.pyVideo Utils:
src/core/utils/video_utils.py