Move the compute to the data, not the data to the compute.

One idea spans two buses. Push a predicate down to where the bytes already sit, so only the rows that qualify ever cross the wire.

It pays exactly when

(1 - selectivity) x scan_size x per-byte cost > on-device penalty

Below that crossover the traditional pipeline still wins. This post hands you the crossover, not a slogan.

Near-data processing: move the compute to the data

Computational storage and eBPF share one idea. Push a predicate down to where the bytes already sit. Here is the selectivity break-even that decides when it pays.

The one idea under both computational storage and eBPF#

Most systems move data to compute. First the storage reads a block. Then the bus carries it to memory. Finally the CPU looks at it and usually throws most of it away. Near-data processing inverts that path. Instead of pulling the data up to the code, you push the code down to the data.

Because the inversion is the whole point, hold onto it. You are not shipping a smarter database or a faster network card. Instead you are relocating one small piece of logic, a predicate or a transform, to the place the bytes already live. Consequently only the bytes that survive the predicate ever travel.

A predicate pushed down, not data pulled up#

Consider a filter that keeps 1% of rows. In the classic model the drive still reads every row, the bus still carries every row, and the CPU discards 99 of every 100. However the filter itself is tiny. So the waste is not the compute. The waste is the movement of bytes that were always going to be discarded.

Near-data processing removes that movement. Because the predicate runs next to the media, the 99% never crosses the bus at all. Therefore the mental model is simple. You are trading a large amount of avoided data movement against a small amount of added on-device compute. That trade is exactly what the rest of this post makes numeric.

What actually moves the data today#

To reason about the trade, first picture the two paths side by side. The diagram below traces the same scan down both routes. On the left the traditional pipeline moves every scanned byte. On the right the near-data pipeline filters at the source and moves only the survivors.

Traditional data-to-compute path versus the near-data compute-to-data pathThe traditional path reads the full dataset, carries every byte across PCIe into host DRAM, then evaluates the predicate on the CPU and discards most rows. The near-data path evaluates the predicate on the drive controller, so only the qualifying share (for example 1.25% of rows) crosses the bus.

The traditional pipeline: read everything, discard most#

Trace the left path one hop at a time. First the controller reads the scanned blocks off the media. Then those blocks cross PCIe into host DRAM. Next the CPU decompresses them and evaluates the predicate. Because the filter runs last, every scanned byte has already paid for its transfer by the time it is discarded.

Here selectivity is the hidden multiplier. A predicate that keeps 1% of rows still forces 100% of bytes across the bus. Therefore the lower the selectivity, the more of your bus bandwidth and memory traffic is spent on rows that never mattered. That waste is invisible in a query plan, yet it dominates the wall-clock time on a large scan.

The near-data pipeline: only the qualifying bytes cross the bus#

Now trace the right path. First the controller still reads the full dataset off the media, because the media read is unavoidable. However the predicate now runs on the drive, before the bus. Consequently only the qualifying rows are handed up to PCIe. So the bus carries the result, not the raw scan.

This is where the byte accounting starts to matter. The media read did not change. Instead the bus transfer shrank by the discard rate. Because the bus and the memory hierarchy are usually the bottleneck on a filtered scan, shrinking what crosses them is what buys the time. The next sections turn that accounting into surfaces you can actually target.

The three surfaces where you can push compute down#

The same idea has three concrete homes. Each one is a place a predicate can run before the bytes travel. The table maps them so you can see the shared shape, then the sections that follow ground each one.

Three surfaces for near-data processing, one shared shape
SurfaceWhere compute runsStandard or mechanismWhat you push down
NVMe computational storageOn the drive controllerNVMe 2.x Computational Programs plus SLM command sets; SNIA CSD modelA scan, filter, or decompress program over an LBA range
eBPF on the data pathIn the kernel, verified and JITedeBPF bytecode, statically verified, compiled to nativeA filter or transform on block or packet data
XDP at the NIC driverIn driver context, before the socketXDP hook, ahead of skb allocationA per-packet predicate that drops, passes, or redirects

Read the middle column as the common thread. In every row the compute sits before a bus, not after it. Therefore the three surfaces are not three trends. Instead they are one architecture applied to storage, to the block layer, and to the network.

NVMe computational storage: compute namespaces and subsystem-local memory#

On the storage path the surface is the drive itself. A computational storage drive pairs flash with a small compute engine, often an FPGA. The NVM Express family standardizes how you talk to it. Specifically the Computational Programs command set dispatches a program, and the Subsystem Local Memory command set gives that program a place to work. You can read both in the NVM Express specification family.

SNIA sits one layer up and names the parts. Its model defines the storage engine, the storage function, and the computational storage drive. Moreover it lists eBPF as one example programming environment for these functions. The definitions live in the SNIA computational storage overview. In short, the standards now describe a drive that filters, not just a drive that stores.

eBPF and XDP: safe programmable code in the kernel data path#

On the network path the surface is the kernel. eBPF lets you load a small program into a kernel hook, and XDP places that hook in NIC-driver context. Because the program runs before the packet becomes a socket buffer, a dropped packet costs almost nothing. The two snippets below show the shared shape. First an XDP predicate that drops a packet class. Then the storage equivalent that pushes a scan filter to the drive.

xdp_drop_udp.c · c
// xdp_drop_udp.c - a predicate that runs in NIC-driver context,
// before the packet ever becomes an skb in the regular Linux stack.
#include <linux/bpf.h>
#include <linux/if_ether.h>
#include <linux/ip.h>
#include <bpf/bpf_helpers.h>

SEC("xdp")
int drop_udp(struct xdp_md *ctx)
{
    void *data     = (void *)(long)ctx->data;
    void *data_end = (void *)(long)ctx->data_end;

    struct ethhdr *eth = data;
    if ((void *)(eth + 1) > data_end)   // a bounds check the verifier demands
        return XDP_PASS;

    if (eth->h_proto != __constant_htons(ETH_P_IP))
        return XDP_PASS;

    struct iphdr *ip = (void *)(eth + 1);
    if ((void *)(ip + 1) > data_end)
        return XDP_PASS;

    // The filter decides here. A dropped packet never allocates an skb,
    // so the byte is discarded at the earliest point the driver allows.
    if (ip->protocol == IPPROTO_UDP)
        return XDP_DROP;

    return XDP_PASS;
}

char _license[] SEC("license") = "GPL";

Notice the symmetry. On the network side the predicate returns drop or pass at the driver. On the storage side the predicate decides which rows leave the controller. Because both run before their bus, both remove bytes at the source. The kernel AF_XDP documentation covers how survivors are then redirected with zero copy.

The verifier is the price of safety in the data path#

Running your code in the kernel sounds dangerous, so the kernel does not trust it. Instead the eBPF verifier proves the program is safe before it ever runs. It walks the program as a directed graph and checks every path. Consequently a program that could loop forever or read out of bounds is rejected at load time, not at runtime.

Read that limit as a design boundary, not a flaw. Because the program must be provably safe and small, the pushed-down code suits predicates and light transforms. In contrast it does not suit a heavy multi-pass computation. That boundary is one of the honest limits this post returns to later.

Does it pay? The break-even, in bytes#

Here is the wedge the whole post turns on. The bytes you save are the bytes the predicate discards, so bytes_saved equals (1 - selectivity) times scan_size. Near-data processing pays only when the transfer cost of those saved bytes beats the on-device penalty. Therefore offload wins when (1 - selectivity) times scan_size times per-byte cost is greater than the on-device penalty.

Read the inequality slowly. Selectivity is the lever. Because a low selectivity makes (1 - selectivity) close to 1, almost the whole scan is saved and offload wins easily. However a high selectivity shrinks the saved bytes toward zero, and then the on-device compute is pure overhead. So there is a crossover selectivity above which the traditional pipeline is simply cheaper.

Worked example: POLARDB table scan at 1.25% selectivity#

This is not a thought experiment. Alibaba and ScaleFlux measured it on POLARDB with computational storage drives, and published it at USENIX FAST '20. Their scan task TS-1 selects only 1.25% of rows. Because the predicate keeps so little, the byte accounting is dramatic.

POLARDB scan TS-1 at 1.25% selectivity: host CPU on each path~3.7x less host CPU, with PCIe scan traffic almost eliminated

514%

Traditional pipeline (predicate on the host CPU)

~3.7x less

140%

Near-data pipeline (predicate on the drive)

At 1.25% selectivity roughly 98.75% of bytes never cross the bus. Latency fell from about 55 s to 39 s, host CPU from 514% to 140%, and host memory traffic dropped 5x. These are the paper's measured figures, not an Atyantik benchmark.

POLARDB scan TS-1 at 1.25% selectivity: host CPU on each path (host CPU utilization across 8 threads, TS-1 scan on 2 drives)
Optionhost CPU utilization across 8 threads, TS-1 scan on 2 drives
Traditional pipeline (predicate on the host CPU)514%
Near-data pipeline (predicate on the drive)140%

Source: POLARDB Meets Computational Storage, USENIX FAST '20

Read the mechanism behind the numbers. Because the filter runs on the drive, only 1.25% of rows are handed to PCIe. Consequently the bus and the memory hierarchy stop carrying the discarded 98.75%. The paper reports latency falling from roughly 55 s to 39 s, and it describes the PCIe scan traffic as almost eliminated. You can read the full result in the FAST '20 POLARDB paper.

The counter-example in the same paper: TS-6 barely moves#

Crucially, the same authors also measured where the idea stops paying. Their scan task TS-6 uses a trivial predicate that passes almost every row. Because little is filtered out, little is saved. So the benefit collapses, exactly as the break-even predicts.

The measured gap is small. TS-6 latency moved from about 65 s to 53 s, and host CPU from 558% to 374%. Compare that to TS-1 and the lesson is clear. The technique did not get worse. Instead the selectivity got worse for offload. When the predicate keeps most rows, the bytes you save shrink, and the on-device compute stops earning its place.

Find your own crossover#

A worked example is someone else's workload. Yours has a different scan size, a different bus, and a different filter. So enter your numbers and watch the verdict flip. Drag the selectivity and the scan size, pick a real PCIe preset, and set the on-device penalty. The explorer computes the bytes on each path, the crossover selectivity, and which side wins.

Selectivity crossover explorer: does near-data processing pay for your scan?
Bus bandwidth

Bytes that cross the bus

Traditional path (move every scanned byte)64.0 GB
Near-data path (move only survivors)800 MB
Near-data winsPush the predicate down (2.06x faster)
Traditional time
16.24 s
Near-data time
7.88 s
Crossover selectivity
52.7%
Bytes kept off the bus
63.2 GB

At 1.25% selectivity, only 800 MB of the 64.0 GB scan needs to cross PCIe Gen3 x4. The 63.2 GB you keep off the bus outweighs the on-device compute penalty, so filtering on the drive returns the result sooner. You are below the crossover selectivity of 52.7%.

The two paths at your current inputs (illustrative)
PathBytes across busTransferOn-device computeTotal time
Traditional (data to compute)64.0 GB16.24 s0 ms16.24 s
Near-data (compute to data)800 MB203 ms7.68 s7.88 s

Selectivity 1.25%, scan 64.0 GB, PCIe Gen3 x4, on-device penalty 120 milliseconds per gigabyte. Traditional path moves 64.0 GB in 16.24 s. Near-data path moves 800 MB in 7.88 s. Crossover selectivity 52.7%. Verdict: Near-data wins.

An illustrative planning model, not a benchmark. It assumes the traditional path moves every scanned byte across the bus, that the near-data path moves only the qualifying share plus an on-device compute penalty over the full scan, and that transfer cost is one over the published bus bandwidth. Real numbers move with your controller, compression, predicate cost, and driver, so use the crossover to find your regime, then measure your own workload before you commit.

Set your workload and read the break-even. The tool computes bytes across the bus on each path, the transfer and compute times, the crossover selectivity, and a verdict that flips between near-data-wins and traditional-wins. The verdict, the byte totals, and the crossover are the accessible source of truth; the bars are decorative. Every figure is an illustrative planning model grounded in published PCIe bandwidths, never a benchmark.

Watch how the crossover moves. Because a wider bus lowers the per-byte cost, a faster PCIe generation makes offload harder to justify, not easier. In contrast a higher on-device penalty pulls the crossover down, so you need an even lower selectivity to win. This is planning math, so measure your own workload before you commit to a computational storage drive.

Network-path corroboration: XDP versus the regular stack#

The storage numbers are one bus. The network path tells the same story on another. Because XDP drops a packet in NIC-driver context, the discarded packet never pays for the rest of the stack. The chart below plots the measured drop rate against the fastest regular Linux paths.

Show data table
Packet drop rate per core: XDP versus the regular Linux stack (CoNEXT '18)
Item Drop rate per core
XDP (driver context) 24 Mpps/core
iptables raw 4.8 Mpps/core
conntrack 1.8 Mpps/core

XDP drops 24 million packets per second per core, about 41.6 ns per packet, roughly 5x the fastest iptables raw path and over 13x conntrack. The gain comes from filtering before the packet becomes an skb, the same 'discard at the source' principle as computational storage. Figures are from the XDP paper, CoNEXT '18.

Figure Packet drop rate per core: XDP versus the regular Linux stack (CoNEXT '18) Hoiland-Jorgensen et al., The eXpress Data Path, CoNEXT '18 (ACM DL 10.1145/3281411.3281443)

Read the bars as the same break-even in a different unit. Here selectivity is the keep-rate of packets. Because a drop workload keeps almost nothing, the saved work is enormous and XDP wins by a wide margin. The measurements are from the eXpress Data Path paper. This is the same idea you saw on the storage bus, which is a useful reminder of how a request traverses compute and storage layers today.

A decision table for your workload#

Bring the two buses together into one rule of thumb. The table maps common workload shapes to a verdict. Read it as a starting point, then confirm with the explorer and your own measurement.

When near-data processing wins, by workload shape
WorkloadSelectivityDataset sizeBus headroomVerdict
Filtered analytical scanVery low (1 to 5%)LargeSaturatedNear-data wins
Point lookups by keyTiny result alreadyAnyFineTraditional (nothing to push)
Low-filter scan (keeps most rows)High (over 60%)LargeSaturatedTraditional wins
Compute-bound per-row transformAnySmallFineTraditional wins
Line-rate packet drop / DDoSVery low keep-rateStreamNIC saturatedNear-data via XDP wins

Notice the pattern across the rows. Near-data processing wins when selectivity is low, the dataset is large, and the bus is the bottleneck. In contrast it loses when the data is small, the filter keeps most of it, or the per-row compute dominates. That is the same inequality, read as a lookup table.

When near-data processing does NOT pay#

Every failure mode here is a position the explorer above already draws for you. Each one is a workload that sits on the wrong side of the crossover, or one where the crossover never applies at all. So name them in the explorer's own terms rather than as generic cautions.

The first is a high-selectivity filter. Drag the selectivity slider up and the near-data bar climbs until it is nearly the full scan, the saved bytes shrink toward zero, and the on-device penalty becomes pure overhead. The second is a small dataset. Shrink the scan size and both paths finish so quickly that the transfer you avoid is not worth an added compute hop. The third is a compute-bound per-row transform, which the explorer deliberately does not model, because its cost is set by the on-device engine rather than the bus, so relocating it can run slower rather than faster.

Two costs sit outside the byte accounting entirely. One is the portability tax. A vendor computational storage program is not portable across drives, and the verifier caps the complexity you can safely push into the kernel path. The other is a misread bottleneck. If your storage is under-provisioned or your query is CPU-bound on the host, no pushdown rescues it, because the crossover measures bus traffic while your time is being spent elsewhere. That is the same class of trap as chasing the same I/O bottlenecks that push teams toward in-storage compute without first measuring where the time actually goes. Near-data processing earns its place only when avoided data movement clearly outweighs added compute. Until the numbers clear that bar, the traditional pipeline is the honest default.

Where the standards actually are#

Maturity matters as much as mechanism. So weigh where these standards sit before you build on them. The timeline below traces the arc from architecture to shipping hardware.

  1. 2016

    XDP merges into Linux

    The eXpress Data Path lands in the kernel, putting a verified program in NIC-driver context. The network path matures first.

  2. Aug 2022

    SNIA architecture v1.0

    The Computational Storage Architecture and Programming Model v1.0 defines the engine, function, and drive, and names eBPF as an example environment.

  3. NVMe 2.x

    Command sets standardize

    The Computational Programs and Subsystem Local Memory command sets give a vendor-neutral way to dispatch a program to the drive.

  4. Now

    Hardware ships, portability lags

    Vendor computational storage drives are real and measured, but a program is still tied to its drive. Budget for the vendor tax.

Read the arc honestly. The network path is production-grade today, and XDP is widely deployed. However the storage path is younger. So the mechanism is proven and the standards exist, yet cross-vendor portability is still maturing. Plan for that gap rather than assuming it away.

How to decide#

The decision reduces to four steps you can run this week. First, measure your selectivity, because that single number dominates the verdict. Second, estimate the bytes you would keep off the bus, which is (1 - selectivity) times your scan size. Third, weigh that saved transfer against the on-device penalty using the explorer above. Only then, consult the decision table and pick a surface.

If the numbers do not clear the crossover, the answer is to do nothing, and that is a real answer. Near-data processing is a scalpel for one class of workload, not a general upgrade. For the layers underneath this decision, it helps to understand why data locality beats data movement at the cache layer and the low-level systems mechanics most software engineers never see. Measure first, then move the compute only where the bytes prove it pays.

Talk to us about a data-movement bottleneck

Keep reading