Representative interview topic

Data engineering interview: How would you design zero-copy exchange with Arrow C Device Interface?

DataHard
Offer.cc Editorial TeamPublished Updated

Question

Two in-process data runtimes need to exchange an Arrow record batch on a GPU while minimizing host-device copies. Based on the Arrow C Device Data Interface, design export, synchronization, lifetime, device compatibility, and failure handling.

Prompt and context

Two in-process data runtimes need to exchange an Arrow record batch on a GPU while minimizing host-device copies. Based on the Arrow C Device Data Interface, design export, synchronization, lifetime, device compatibility, and failure handling.

The Arrow C Device Interface extends the C Data Interface with a device type, device identifier, and synchronization event so GPU or FPGA memory can stay on the device. The specification is currently marked experimental. It targets in-process runtime interoperability, not cross-machine transport or persistence.

What the interviewer evaluates

The interviewer looks for a clear distinction among schemas, arrays, record batches, and device buffers; correct use of ArrowDeviceArray; handling of CUDA, ROCm, Metal, and other devices; release callbacks; read-only sharing; event lifetime; ABI compatibility; and a safe CPU fallback.

Clarifying questions

Exchange boundary

Confirm whether producer and consumer share a process and device context, whether cross-process or cross-machine transfer is required, and whether the consumer implements the Arrow C Device Interface.

Performance and correctness goals

Clarify whether the goal is reducing host-device copies, device-to-device copies, or kernel waits, then define synchronization correctness, fallback cost, and batch size.

Device and memory types

Identify CUDA, ROCm, Metal, Vulkan, or another device type; define how device IDs are resolved; and decide whether unified memory, pinned host memory, or ordinary CPU buffers are allowed.

30-second answer

“I would keep schemas and arrays from the Arrow C Data Interface and add an ArrowDeviceArray carrying device type, device ID, and a synchronization event. The producer keeps the device buffer valid until the event permits the consumer to read it; both sides treat exported data as immutable and use release callbacks to define ownership. The consumer validates the device context and ABI version. Unsupported devices or events fall back to a CPU ArrowArray or an explicit copy. Because the interface is experimental, release tests must cover synchronization, cleanup, device loss, and copy regressions.”

Step-by-step solution

Step 1: Separate schema from data

ArrowSchema describes column types, fields, and metadata. ArrowArray describes length, offset, null bitmap, data buffers, and children. The device interface keeps these structures and wraps the top-level array with device information; a GPU buffer does not remove the need for a schema.

Step 2: Describe the device

The producer fills device type and device ID so the consumer can locate the correct context. The specification defines macros for CPU, CUDA, ROCm, Metal, Vulkan, and other devices. Do not turn numeric macro values into an unnegotiated business protocol; check capabilities and agreed device types.

Step 3: Define synchronization events

sync_event tells the consumer when the device memory is safe to read. The producer keeps the buffer valid until the event semantics hold; the consumer waits for or imports an equivalent event before launching a kernel. Ownership, thread safety, context association, and destruction of the event belong in the contract, not in an undocumented pointer convention.

Step 4: Make zero-copy sharing immutable

Zero-copy means sharing the device buffer directly, not passing a host pointer and hoping a runtime avoids a copy. Producer and consumer should treat exported data as immutable. If the consumer needs to write, it copies into owned storage or obtains an explicit exclusive-write lease.

Step 5: Manage lifetime

Use the C Data Interface release callback to free arrays, children, schemas, and device resources after the consumer finishes. The producer must not reuse or release buffers before the callback, and the consumer must not call it twice. Cancellation, crashes, and device reset need observable cleanup paths.

Step 6: Bound the ABI and implementation

The interface aims for a small C definition that non-C/C++ runtimes can expose through FFI. It is experimental, so pin supported versions, structure sizes, reserved-field initialization, and pointer validity. Experimental status and implementation differences remain operational risks even with a small ABI.

Step 7: Fallback and validation

When a device type, context, event import, or lifetime guarantee is unsupported, fall back to a CPU ArrowArray or an explicit copy and record the reason. Test devices, empty batches, nested columns, null bitmaps, release order, event timeout, device loss, and host/device copy baselines.

Model answer

I would first confirm that both runtimes share a process and device ecosystem, then define a schema, array, and device-buffer contract. The producer exports an ArrowDeviceArray with device type, device ID, and a synchronization event. The consumer validates its context, waits for the event, and reads immutable data. Release callbacks own arrays, children, and device resources; buffers cannot be reused before release. The interface is experimental, so deployment pins implementation versions and initializes reserved fields. Unsupported devices or events use a CPU ArrowArray or explicit copy, while telemetry measures synchronization waits, copied bytes, release errors, and device loss.

Common mistakes

  • Mistake: Treating a device pointer as a cross-process or cross-machine protocol. → Why it fails: The C Device Interface targets in-process exchange and does not provide address-space or persistence semantics. → Fix: Use an IPC or transport format across boundaries, or copy explicitly.
  • Mistake: Passing only a device type and ignoring synchronization. → Why it fails: The consumer may read while the producer is still writing. → Fix: Define event, context, wait, and destruction ownership.
  • Mistake: Allowing both sides to mutate a zero-copy buffer. → Why it fails: Shared mutable buffers create races and inconsistent columns. → Fix: Default to read-only data and use owned storage for writes.
  • Mistake: Freeing resources as soon as a release callback is visible. → Why it fails: The consumer may still be using device memory before the callback. → Fix: Let the owner release once after completion and cover cancellation and errors.

Follow-ups and responses

Why is ArrowSchema still needed?

The device interface describes memory location and synchronization, not column types. The consumer still needs the schema for format strings, children, null bitmaps, and field metadata.

Does the same device ID guarantee sharing?

No. Runtime, context, allocator, and event compatibility also matter. A device ID locates a resource; it does not replace capability negotiation.

When should you copy deliberately?

Copy when the consumer lacks device support, cannot import the event, cannot prove lifetime, or crosses a process boundary. Measure the copy cost before deciding whether a deeper interoperability layer is worthwhile.

How do you prove zero-copy is real?

Measure host-to-device and device-to-host bytes, synchronization wait, kernel-start latency, and end-to-end throughput against CPU-buffer and explicit-copy baselines. Total elapsed time alone cannot prove that hidden copies are absent.

Public sources

Related questions