April 20, 2024 • 12 min read
The XVD hardware sits in a clinic and produces lung scans. The analysis runs in our cloud backend. So the scan has to get from the box to the cloud, and a single scan is over a gigabyte. We started with REST, because that's what you reach for. A 1GB scan serialised as JSON over HTTP/1.1 is slow, and you can't stream it without bolting something on. We moved to gRPC. Bandwidth dropped about 80%, which I didn't expect to be that large. The rest is the parts that took longer than the protocol switch did.
The two that mattered for us were the binary wire format and real streaming. We're pushing big files on a schedule from boxes on patchy networks, so those two cover most of what we needed. The rest of the list is nice to have.
You write the schema first, both ends generate off it, and the C# device client and the Go backend stop disagreeing about what a field means. That alone was worth the move. Here's a trimmed-down version of what we used for the imaging data:
// medical_imaging.proto
syntax = "proto3";
package medical.imaging;
// DICOM scan metadata
message ScanMetadata {
string scan_id = 1;
string patient_id = 2;
string device_id = 3;
int64 timestamp = 4;
ScanType scan_type = 5;
PatientInfo patient_info = 6;
DeviceSettings device_settings = 7;
}
// Patient information (HIPAA compliant)
message PatientInfo {
string encrypted_patient_id = 1; // Encrypted for privacy
int32 age = 2;
Gender gender = 3;
repeated string medical_conditions = 4;
}
// XVD device-specific settings
message DeviceSettings {
float breathing_protocol_duration = 1; // seconds
int32 image_resolution_x = 2;
int32 image_resolution_y = 3;
float voxel_size = 4; // mm
string calibration_data = 5;
}
// Streaming DICOM data chunks
message DicomChunk {
string scan_id = 1;
int32 chunk_sequence = 2;
bytes data = 3; // Binary DICOM data
string checksum = 4; // For integrity verification
bool is_final_chunk = 5;
}
enum ScanType {
SCAN_TYPE_UNSPECIFIED = 0;
FOUR_D_CT = 1;
INSPIRATION_HOLD = 2;
EXPIRATION_HOLD = 3;
}
enum Gender {
GENDER_UNSPECIFIED = 0;
MALE = 1;
FEMALE = 2;
OTHER = 3;
}gRPC didn't solve anything on its own. The chunking, the retries, the auth, the metrics — that's where the actual work was, and most of it you'd write whatever protocol you picked. What gRPC gave us was a sane place to put it. For moving big scans off a box on a network you don't control, I'd pick it again.