Abstract
The message passing interface (MPI) is one of the most portable high-performance computing (HPC) programming models, with platform-optimized implementations typically delivered with new HPC systems. Therefore, for distributed services requiring portable, high-performance, user-level network access, MPI promises to be an attractive alternative to custom network portability layers, platform-specific methods, or portable but less performant interfaces such as BSD sockets. In this paper, we present our experiences in using MPI as a network transport for a large-scale distributed storage system. We discuss the features of MPI that facilitate adoption as well as aspects which require various workarounds. Based on use cases, we derive a wish list for both MPI implementations and the MPI forum to facilitate the adoption of MPI by large-scale persistent services. The proposals in the wish list go beyond the sole needs of distributed services; we contend that they will benefit mainstream HPC applications at extreme scales as well.
1. Introduction
High-performance computing (HPC) distributed services, such as storage systems, are hosted in servers that span several nodes. They interact with clients that connect and disconnect on demand, and require network transports that offer high bandwidth and low latency. These services are typically written in user space and require user-space networking Application Programming Interfaces (APIs). However, for performance reasons, contemporary HPC systems typically employ custom network hardware and software. In order to reduce porting efforts, distributed services benefit from using a portable network API. The most likely low-level networking API for general-purpose programming is the ubiquitous 30-year-old Berkeley Software Distribution (BSD) socket API. While BSD sockets are often supported on HPC networks, they are not typically used because of lower bandwidth and higher latencies when compared with native networking libraries. Instead, the HPC community has seen a myriad network technologies, many of which have been short-lived. With proprietary HPC system manufacturers, in particular, there is no guarantee that an existing network API will be adopted by the next-generation supercomputer. For example, the LAPI (Banikazemi et al., 1999), DCMF (Krishnan et al., 2008) and PAMI (Kumar et al., 2012) network APIs were all released by a single company for its Scalable POWERParallel (Banikazemi et al., 1999) and Blue Gene (Krishnan et al., 2008; Kumar et al., 2012) series of supercomputers. Unfortunately, none of those network APIs is portable across all or even most systems.
However, all recent HPC systems do include an implementation of the message passing interface (MPI) as part of their software stack. Thus, MPI shields the HPC community from the aforementioned volatility in network technologies and from low-level details such as flow control and message queue management (Zounmevo and Afsahi, 2014). MPI has, in effect, become the BSD socket of HPC programming. Plus, since MPI is one of the primary ways of programming supercomputers, the bundled MPI implementation is typically well tuned and routinely delivers optimal network performance (Lu et al., 2011).
Currently though, MPI is not typically used in components of the software stack that extend beyond a single application such as distributed services. Plus, unlike application-type HPC programs which are relatively short-lived, distributed services are often persistent because they bear no concept of job completion. Persistence as required in distributed services also implies stricter fault-handling approaches, as the consequences of service abortion usually extend beyond a single job as is usually the case in computing applications.
In this work, we evaluate the use of MPI as a network portability layer for cross-application services. We first describe the challenges, workarounds and aspects of the service design that are made easy, robust or difficult by the semantics of MPI. In particular, we analyze how the service design, which is resiliency-conscious and geared towards scalability with respect to the number of allowed concurrent clients, maps to the features offered by MPI. An emphasis is put on aspects of the MPI specification which serve the needs of the service particularly well. Similarly, we provide various analyses which show aspects of MPI that can be improved to better serve some use cases backed by the service design. These analyses actually make the case for feature additions or improvements in MPI. We argue that these feature proposals, presented as a wish list, are timely and are even relevant to mainstream MPI-based HPC applications on future machines. With respect to portability, we investigate how well a number of widespread MPI implementations follow the MPI standard; and in cases where unspecified behavior is allowed for an MPI feature required by our design, we enumerate the observed outcomes.
This paper is based on an improved version of the service design used in Zounmevo et al. (2013). Additionally, the paper presents a more thorough discussion on the approach used to realize service connection–disconnection over MPI. An emphasis is put on how the absence of non-blocking versions of certain non-communicating MPI routines limits the service to the point where we have to trade scalability for safety. We also add an analysis of communication cancellation scenarios and their resulting consequences.
The rest of the paper is organized as follows. Section 2 presents the distributed storage service which is used as the basis for the MPI-related discussion. Section 3 discusses the features that a network transport should offer to ease the design of the distributed service. Section 4 evaluates our design on top of MPI. Section 5 offers a number of ideas and suggestions intended to ease the adoption of MPI by persistent distributed services. Section 6 discusses the related work. Section 7 summarizes our conclusions and discusses future work.
2. Service overview: The distributed storage
As a concrete example of distributed services, this section presents the highly available distributed storage system used as discussion support in this work. The storage system is made of input/output (I/O) servers that export a unified view of the underlying storage to a set of clients (mostly compute nodes running application software). Clients typically connect to the storage service at the start of a job, periodically issue I/O requests, and disconnect when the job ends. In our system, the storage service relies on replication to ensure that data remains available even when an I/O server fails. In addition, data is striped across multiple servers, increasing performance by providing parallel access to the underlying storage devices. The client is oblivious to striping and replication; the I/O library on the client side only exposes traditional read/write semantics. In the current version of the implementation, an I/O is either exclusively ‘read’ or exclusively ‘write’; the same I/O being ‘read/write’ is not supported.
All the I/O requests start with a remote procedure call (RPC) initiated from a client to a single server that executes the request and then sends a response back to the client. Because of striping, a single, sufficiently large I/O access can span several I/O servers. However, to limit client-side complexity and simplify failure handling, a client always contacts a single server for any I/O request. That server manages striping and replication on behalf of the client and provides a single response for client-side I/O requests. The contacted server does so by relaying the request of the client to any secondary server involved (for purposes of striping or replicas) and aggregating the response of each server before responding back to the client. The single server contacted by a client is not fixed; it is decided by a placement policy which falls outside the scope of this work. The placement policy can lead the same client to contact two distinct I/O servers for two distinct I/O requests even if both requests target the same data. For any given I/O request, we designate the contacted I/O server as primary server or server_0; all the other servers involved are referred to as secondary servers.
When the payload of an I/O is small enough, it is simply embedded in the RPC request or response respectively for write and read. For large messages, however, the protocol in Figure 1, meant for bulk data transfers, is used to allow multiple servers to one-sidedly copy chunks of the I/O payload into or from the client’s memory. The lifetime of a large-payload I/O starts with the client registering an I/O buffer in step a. A subset of the entire registered buffer is then made available (published) for remote access in step b. The distinction between buffer registration and publishing is a performance-conscious choice for network transports where a single registration cost can be amortized over multiple I/O. As a consequence, if the buffer or any of its spanning address superset was already registered, the lifetime of a large-payload I/O could start at step b and end at step s. Step t undoes registration. Ideally, registration does not grant remote access. Remote-access granting starts with publishing in step b. Then, in step c the remote access information pointed to by the publishing handle is serialized into the RPC packet meant to initiate the I/O. The access information is not tailored for a specific remote server. As a consequence, server_0 can resend it to any other servers involved in the associated I/O, so that they can seamlessly one-sidedly access the client.

I/O transfer protocol.
The I/O request sent by the client in step d is caught by the RPC listener of server_0 in step e. The server reproduces the memory access information in step f, and in step g, determines all the other (secondary) servers that must be seamlessly involved in fulfilling the I/O. Server_0 involves the secondary servers by dispatching the new intermediate RPC requests (step h). Then in steps i1, j1 and k1, server_0 transfers its chunk of I/O payload to or from the client. In parallel, the secondary servers fulfill steps i2 to m2 to transfer their respective partitions of the I/O payload. Server_0 gets blocked in step o1 until it receives a completion acknowledgement from each of the secondary servers involved. Server_0 sends a final RPC response to the client in step p.
For certain network transports, the completion of step q at the client side could guarantee that the whole I/O payload has been transferred. However, since the one-sided payload transfer of the secondary servers and the RPC response which contains their completion acknowledgment have different destinations, they might complete out of order; leading to the final reply from server_0 to the client potentially completing before some payload transfers. For network transports where such an out-of-order completion might occur, a custom safe completion subprotocol can be implemented by sending the right control data in the RPC response in steps p and q and by checking it in step r. Step r is blocking. After the memory region is unpublished in step s, it is expected to become inaccessible to remote servers.
The I/O protocol presented in Figure 1 reduces the client-side protocol complexity since the client is not logically involved in the transfers. As the client is oblivious to all the secondary servers, the protocol simplifies the handling of client and server failure. It also defers flow control to the I/O server and eases the burden of high ratio of compute nodes to I/O nodes in current and future HPC systems. The work on Mercury (Soumagne et al., 2013), which is centered around RPC over the same protocol, provides in-depth comparisons with other existing RPC frameworks and protocols.
3. Transport requirements
The set of network features required by the service are as follows.
4. MPI as a network API
When running over MPI, the storage server is an MPI job created in
A dedicated connection thread that listens on a connection channel and executes the procedure required for first contacts from soon-to-be clients.
A dedicated listener thread that receives all the requests from already existing clients and transforms them into a work item that is enqueued for a threadpool to handle. This thread fulfils the RPC listener.
A threadpool made of non-dedicated worker threads that handle the potentially heavy tasks from the work queue. Unless otherwise specified in a configuration file, the size of the threadpool on any node is exactly n − 2 if the node can run n hardware threads simultaneously. Oversubscribing is thus avoided.
The two dedicated threads do very little except for receiving connection and RPC requests, the goal being to be as reactive as possible to concurrent requests. We present observations of certain limitations of MPI or MPI implementations in this section. The executions are done with Open MPI-1.6.5, MPICH-3.0.4 and MVAPICH2-1.9. MPICH is the only implementation that can run the fully fledged server, as none of the other two MPI distributions simultaneously provides a working implementation of MPI-3.0 one-sided, a support for
4.1 Connection/disconnection
At initialization time, the storage server opens a port with
The client-side connection routines operate over a communicator encompassing all the processes in a client group. The routine executes
At the server side, the connection thread gets blocked inside
The disconnection procedures essentially undo what the connection procedures did. However, since they contain steps that we analyze in depth later on, we present their pseudo-codes in support of the upcoming discussion. The client-side disconnection executes the procedure shown in Listing 1;
Client-side disconnection pseudo-code.
At the server side, after it receives the disconnection request, the RPC listener removes the connection object concerned from the list of endpoints to listen on, and enqueues a threadpool work item with the handler shown in Listing 2. With the exception of
Server-side disconnection pseudo-code.
4.2 The RPC listener
In each server node, the RPC listener is a dedicated thread that listens to RPC requests coming from either the clients or from the other servers. Since each connection object has its own I/O communicator (
4.3 I/O life cycles
4.3.1 RPC and MPI two-sided communications
RPCs are the main service-level primitive and are implemented on top of MPI two-sided communications. Each RPC is a round-trip communication made of a request and a response. A request is internally directly mapped to an
4.3.2 Bulk data transfer and MPI one-sided communications
The MPI implementation of the memory registration/deregistration of the bulk data transfer protocol (steps a and t in Figure 1) are noop. Only memory publishing/unpublishing (steps b and s) is implemented, via
The design of the I/O protocol predates the availability of the MPI-3.0 specification. As a result, we had previous implementations based on MPI-2.2 RMA and on two-sided emulation of the one-sided communications (Soumagne et al., 2013). The performance results of this RPC-based I/O protocol over two-sided emulation of MPI one-sided routines are available for Cray interconnect and InfiniBand in Soumagne et al. (2013).
4.4. Cancellation
No RPC, and consequently no I/O, is allowed to be blocked forever. As a result, all RPCs bear a timeout after which they must expire. As a reminder, a client-side expired RPC is retried, possibly to another server decided by the placement policy. We emphasize that the protocol of Figure 1 is for a single I/O. As a result, when a cancellation occurs, the client must retry the whole protocol. No aspect of the previously cancelled RPC can influence the new retried RPC. A server does not retry expired RPCs; it relies on clients to re-initiate the whole operation which leads to the server issuing the RPC in the first place.
4.4.1 MPI_Cancel and cancellation outcomes
The RPC cancellation implementation is over MPI’s own cancellation mechanism (
When an RPC is cancelled by its initiator, both the request which maps to
Cancellation can then lead to two kinds of undesirable consequence, namely zombie receives and partially modified buffers. Persistent partially modified buffers are very infrequent. As long as a successful client-side retry ends up happening for an RPC which was cancelled, the final buffer meant to be written into, or at least an equivalent of that buffer, ends up getting exactly the expected data. For a large-payload read, the client-side buffer could be left partially modified by cancelled RPC between the servers. Transfers between two peers is still subject to the all-or-nothing pattern but all servers might not transfer their partitions of the payload if some of them perform any kind of effective cancellation. In this case however, the client buffer ends up being overwritten by the full payload upon the first successful retry of the initial requester. Then, the buffer remains unchanged even if it is overwritten by portions of the same data by any pending cancelled I/O. For a write, a retry can involve replicas, in which case the same server-side buffer is not modified but its equivalent in a replica server is, leading to the last stored data being the right one.
4.4.2 Dealing with zombie receives
A zombie receive is essentially an MPI-level unexpected message queue (UMQ) item (Zounmevo and Afsahi, 2014) which is abandoned forever. It could also be sitting inside the network device, waiting to be extracted by MPI. The tag management guarantees for a given client that any response to a cancelled RPC will no longer be matched inside MPI for approximately
It is challenging to safely spot zombie receives from legitimate ones as long as the client is still issuing RPCs. However, right after the disconnection request is received by the servers and the reply reaches the client, each end has the guarantee that no more MPI-level two-sided communication will arrive over the connection object about to be disconnected.
In a loop, each zombie receive is discovered via

Zombie receive cleanup latency.
4.4.3 Concluding remarks on MPI adequacy for service-level cancellation
Cancellation as provided by MPI for two-sided communications is adequate to fulfil the needs of the service. It is worth mentioning that
Unfortunately, the situation is different for one-sided communications. Once an I/O request times out and its associated RPC gets cancelled, the protocol (Figure 1) requires the access to the client buffer to be revoked for the cancelled I/O. Service-level buffer access removal maps to
4.5 Client-server and failure behavior
The storage server always runs with
4.5.1 Server failures
Ideally, a server or an I/O node that fails should not bring down the service. We would like such a server to rejoin the storage system when fixed. The observations are as follows:
Behavior in case of isolated server abortion.
4.5.2 Client failures
The experiments in this subsection have been done only with Open MPI and MPICH because we have not been able to successfully use port and connection–disconnection functions in MVAPICH. We run three servers on three nodes and a single client on a fourth node. As a reminder, each client shares an intercommunicator
Storage job behavior after client job failure.
In both Open MPI and MPICH, the storage job survives client job failure (crashes or abortions). No communication attempt with a deceased client brings down the storage job. The communication behaviors are similar for both the intercommunicator and the intracommunicator. In general, in Open MPI, except for Eager sends, the storage processes get trapped in any explicitly or implicitly communicating routine (including
4.5.3 Summary of failure behavior for typical RPCs
The observations in Table 1 and Table 2 lead to the following conclusions. With Open MPI, a failed server terminates the whole storage service. A failed client does not terminate the storage system but could halt its activity because worker threads get trapped in failed communications. As enough worker threads get trapped, the storage system could become completely irresponsive. With MPICH, an aborted server or a failed client do not prevent the storage system from operating as long as all the in-progress and subsequent RPCs are only related to small payload I/O, that is, only Eager two-sided communications are performed. RPCs leading to large payload I/O or disconnection requests could make the storage system irresponsive. No conclusion can be made for MVAPICH because of the impossibility of observing client-server behavior.
4.6 Object limits
Each client group leads to the creation of two explicit communicators in each server process. Depending on the implementation, a third implicit communicator might be created for the one-sided communication window. Furthermore, in large systems, servers will have to maintain a very large number of handles to support non-blocking two-sided operations. The same is true for derived datatypes (DDTs). Noncontiguous I/O operations require unique hindexed types for each I/O. In fact, an I/O transfer from a server has to resort to two separate DDTs because the noncontiguity layout at the source is different from that of the target. In summary, we estimate that a server needs three communicators (including the implicit one-sided window one if required), one one-sided window, three DDTs and two pending non-blocking point-to-point communications (NBPtP) to service a single instance of any kind of I/O to a client. We verify through emulation tests whether a server can service a million-process client job by creating 3,000,000 communicators, 1,000,000 one-sided windows, 2,000,000 non-blocking posted NBPtP and 3,000,000 hindexed DDTs. Each category of objects is created in a separate test. The results are presented in Table 3. To detect resource exhaustion limits, we run each test on both systems C1 and C2, each time with a single rank per node, even for the client job.
Object limits.
In addition to the observed limits, we report how each MPI implementation behaves after the limit is reached. For the NBPtP tests, the limit is determined by whichever of
All three implementations have a hard limit for the number of communicators (Table 3). The limit seems to be a design choice because it does not depend on the amount of memory available on the node. For one-sided windows, Open MPI succeeds in creating all the required objects on C2 but hits a limit on C1. One can notice that Open MPI and MVAPICH can create more one-sided windows than communicators. DDTs and NBPtP seem to be limited only by available memory. Open MPI tends to get blocked forever in both by-design and resource-imposed limits. MVAPICH either continues gracefully or crashes after returning an error code. MPICH has successfully created all the DDTs and non-blocking communications required to service a million clients. However, in order to observe its behavior in situations of resource exhaustion, we substantially increased the number of objects. We observe that it behaves similarly to MVAPICH when resources are exhausted. Resource-exhaustion-induced limits are not an issue per se; it is how the MPI implementation reacts to them that can make a difference, especially for the required persistence of the service.
The object limit observations lead to the following conclusions. When the storage server runs out of communicators or RMA windows, Open MPI will halt the whole system, without killing it. MPICH and MVAPICH keep the storage system running but subsequent client connections fail. Then when resource exhaustion occurs for DDTs, Open MPI would kill the storage system while MPICH and MVAPICH would keep it running after generating error messages. Finally, when resource exhaustion occurs for non-blocking two-sided communications, the storage system would simply terminate for all three MPI implementations.
5. Observations and wish list
In this section, we highlight a number of problem areas and formulate some recommendations for MPI implementors and the MPI forum. While some of these recommendations follow from our desire to use MPI in a non-traditional setting, this does not preclude the usefulness of our recommendations for more mainstream MPI applications. Furthermore, we believe that as these applications evolve to support the fundamentally different environment presented by future exascale systems, some of the features described in this section will be required for all HPC software domains.
5.1 Fault-tolerance
5.1.1 Enforcing MPI_ERRORS_RETURN
For most contemporary HPC applications, the reasonable action in the case of failures is to restart the job. However, when failed components can be reconstructed (for example from a replica), restarting is not always the best solution because other applications might depend on the same service. The issue of MPI jobs surviving the crash of a subset of
5.1.2 A plea for a more cancellation-friendly MPI
From crash-resilient jobs (Graham and Dongarra, 2004; Hursey et al., 2011; Bland et al., 2012) to MPI rank replication on spare hardware (Ferreira et al., 2011), fault-tolerance in MPI has gained more momentum recently. Checkpoint recovery (Hursey et al., 2007), which is probably the most widespread fault-tolerance approach in MPI, is even offered in many MPI implementations. However, as stated in Gropp and Lusk (2004), the optimal fault-tolerance strategy at extreme scales is still not clear. In fact, even if MPI were to become fault-tolerant in the most idealistic way, programs and libraries built with MPI might still have their own additional requirements for resiliency. On top of any integrated fault-tolerance mechanism, the approach of MPI features as resiliency primitives should be encouraged to allow MPI applications, libraries and frameworks to easily realize their own fault-tolerance needs. The second of the three fault-management concepts advocated in Bland et al. (2012) aligns with this suggestion.
Cancellation could be a very useful resiliency primitive. Most large-scale filesystems such as PVFS2 (Latham et al., 2006) or Lustre (Knepper et al., 2012) support request timeout. The same goes for most network technologies that could be used as transport for those systems. For instance, BSD sockets and InfiniBand support communications with timeout. With respect to MPI, the third of the three fault-management concepts advocated in Bland et al. (2012) recommends the absence of indefinite time deadlock or wait in case of failure. While timeout-enabled MPI communications will certainly find substantial adoption, cancellation could be even more useful. Cancellation is an even more fundamental primitive than timeout, and as such, it gives a bit more flexibility. First, timeout can easily be built on top of cancellation. Second, cancellation does allow a timed communication to be killed before any associated timeout. An example of use case is the situation where two redundant resources are solicited for the same operation, and the first completed operation renders the second one useless. Cancellation is a crucial mechanism for reclaiming resources when faults or other exceptional circumstances arise.
While MPI-3.0 extended the use of request objects (for example for non-blocking collectives), it is unfortunately still erroneous to issue
5.2 Generalization of non-blocking operations
The current connection–disconnection handling at the server side is non-scalable (Listing 1, Listing 2). The issue is the blocking nature of the non-communicating MPI collective routines involved. Currently, connection and disconnection must not just be strictly serialized; they must occur in the exact same order on each and every server node to guarantee safety in all scenarios. That order, in the case of connections, is imposed by the root process specified in
To understand why we have to resort to serialization and ordering of the connections and disconnections, let us assume the absence of those constraints and analyze how the service as a whole behaves as a consequence. We define:
n as the number of distinct clients simultaneously asking for disconnection;
wi as the maximum number of disconnection work items that could be simultaneously handled by the server i;
ti as the number of CPU cores on the server i; we avoid oversubscribing the servers, so the upcoming analysis assumes that each server can run more than two hardware threads simultaneously;
S as the set of servers;
Di as the set of connection objects being simultaneously disconnected by the server node i.
With the blocking routines offered by the current MPI specification for the disconnection procedures, since two threads are dedicated to connection and RPC listening, we obviously have
The situation modeled by equation (2) results in the complete deadlock of the service as a whole, along with all the clients which had already started their disconnection procedure. |Di ∩ Dj| = 0 simply means that servers i and j are waiting for each other in collective calls over sets of mutually exclusive service-level disconnections. In concrete terms, they all get blocked in the respective first collective call encountered in the disconnection procedure (line 5 of Listing 2). Then (|Di| = wi) ∧ (|Dj| = wj) means that all the threads of each of the servers are already occupied by the blocking server-level disconnection work items. There is no worker left for any of the two servers to, by chance, pick a work item that could transform |Di∩Dj| = 0 into |Di∩Dj| > 0:
The aforementioned deadlock is guaranteed to be avoided only if the number of simultaneous disconnection requests is strictly less than the sum of the number of work items that any two servers can handle in parallel (equation (3)):
Equation (3) states that a standstill will always eventually be broken as long as we guarantee
One can notice that no amount of CPU cores per server node can solve the problem created by equation (2). In fact, as per equation (1), equation (4) leads to equation (5). The opposite of equation (5) is expressed by equation (6) which is impossible to overcome. In fact, assuming that equation (6) is already true, every time ti or tj is increased by 1, one just needs to increase n by 1 to maintain the deadlock risk:
The single-point-of-entry approach automatically guarantees, via the unique contact server, that the simultaneous number of disconnections does not go beyond the safe limit. If the deadlock situation must be deterministically avoided while not resorting to a single point of entry, the servers must coordinate among themselves to guarantee that the sum of all the disconnections that are currently activated does not go beyond the safe limit. This coordination requires a storage-wide integer token that each server must acquire and modify atomically before propagating a disconnection request to the other servers. The global token must also be atomically decremented when a disconnection request is done being fulfilled. Servers do not necessarily know if the token has already reached the maximum that guarantees safety. As a result, they must still perform the acquisition sometime just to realize that the update is not possible. Finally, the comparison to test if the safe limit of simultaneous disconnections is reached is an inequality test; as a result, a regular
Assuming the existence of non-blocking versions of some of the functions involved in the disconnection procedures, consider how the hypothetical approach of Listing 3 voids all the aforementioned problems. The disconnection work could be executed by resorting to the non-blocking collective cleanup functions on lines 6, 7, 8 of Listing 3. Then, on lines 9 to 11, a completion work is enqueued with the non-blocking handler at lines 13 to 24 of Listing 3. We emphasize that by the time the disconnection is initiated, the clients associated with the connection object must have already completed all their I/O, meaning that no one-sided communication is still in flight. As a result,
Server-side non-blocking disconnection pseudo-code.
In general, blocking routines can be a hindrance to both performance and scalability. In some cases, concurrent requests are required in order to extract maximum hardware efficiency. At the same time, not all large HPC systems support the creation of an unlimited number of threads, and on systems that do, thread resource consumption typically prohibits creating a large number of threads. Having a thousand blocking routines pending requires no less than a thousand threads. In comparison, a threadpool of just a very few threads can do the same job if non-blocking routines are available. In fact, it is useful to re-emphasize that, as shown by the previous disconnection deadlock hazard analysis, a higher degree of parallelism is not an alternative to the provision of non-blocking routines and vice versa. Both concepts do offer overlapping benefits but are not interchangeable.
Furthermore, any blocking operation can be made non-blocking as long as consistency constraints are respected. Plus, by keeping blocking versions of potentially problematic non-blocking operations, users always have a safe alternative API handy to accomplish the same task in situations where consistency can be difficult to reason about. Similarly, a user can elect to use the blocking version of a functionality if a fast reaction is required.
Non-blocking operations seem to be a necessary condition for efficient cancellation as well. If cancellation was possible at all with blocking operations, two threads would be required for it to be effective on any single routine: one for the blocking call, and a second one to issue the cancellation. The
5.3 Object limits and resource exhaustion
HPC compute jobs rarely need thousands of communicators or RMA windows. The situation could be very different for persistent distributed services. Similarly, DDTs and non-blocking operations might exist in large quantities in large compute jobs but their numbers are usually reasonable in each process of the job. As shown by our design, any single process in persistent services can manage very large quantities of these objects at large scales. As a consequence, by-design limits should be seriously lifted up to allow a broader set of use cases and to get MPI implementations ready for extreme-scale uses. Even at extreme scales, most programs will be naturally bound in many respects by architecture limits, such as word or pointer sizes in C. Thus, more natural by-design limits could be
Furthermore, resource exhaustion is one of the most trivial expectations in very large programs. As a result, it is planned for and is therefore rarely an unmanageable issue. In fact, our storage service has a robust flow control policy that makes clients wait and retry in situations of resource exhaustion. Such a policy which delays selected clients without disrupting the service for everybody is much better than a sudden crash which renders the service totally unavailable. Ideally, MPI should provide upper layers with non-fatal methods of discovering resource exhaustion, so that these upper layers can decide and trigger workarounds if possible.
6. Related work
The widespread adoption of MPI has attracted upper-level programming models such as PGAS (Bonachea and Duell, 2004) and MapReduce (Lu et al., 2011). The study in Latham et al. (2006) stated that MPI could be used for monitoring daemons, a class of persistent services. MPI has also been used for file-staging and parallel shell design (Desai et al., 2004). The I/O delegate proposal (Nisar et al., 2008) allows an MPI job to transit its I/O requests through another MPI job linked to the target filesystem. The work resorts to dynamic process management, but it is not a pure client-server design, as presented in our experiment.
From a purely storage point of view, the most widespread use of MPI is via its MPI-IO parallel file manipulation features (Ching et al., 2003). MPI-IO has been used over GPFS (Blas et al., 2010), Lustre (Logan and Dickens, 2008), NFS (Calderón et al., 2002), PVFS2 (Huaiming et al., 2011) and OrangeFS (Tanimura et al., 2013). MPI-IO is used at filesystem level; that is, on top of an existing storage system. In comparison, our experimentation uses MPI below the storage system.
7. Conclusion and future work
Portability and high performance are two attractive traits of MPI. As MPI runs on top of the network fabric of its hosts, the aforementioned traits allow programmers to uniformly and efficiently target a very disparate set of supercomputers, each with its own architecture and network API. With the same concerns of portability and performance, we implemented in MPI the network layer of a resilient, scalability-conscious distributed HPC storage system.
Many MPI-3.0 features such as dynamic windows and request-based RMA communications greatly facilitate the design. However, in certain areas, workarounds and design concessions were needed. The service exhibits use cases where non-blocking versions of non-communicating MPI routines would make a sound difference for performance, scalability and even safety. Another scalability issue created by the adoption of MPI resides in the runtime limit imposed on certain objects such as communicators. Furthermore, unlike regular MPI jobs, services are persistent and less tolerant to unplanned termination. As a consequence, the fault-handling of MPI was also a major issue about which we provided analyses and recommendations. Finally, we provided an analysis of the suitability of
Our list of MPI-directed suggestions is primarily meant to widen MPI adoption to include more service-oriented HPC software. Nevertheless, we believe that even within the HPC community, there is a trend toward a more modular, service-oriented architecture. One example of this is in situ analysis or covisualization. Therefore, many of the recommendations made in this paper are likely to have a broader impact. As future work, we intend to collaborate with both the MPI forum and implementers to ensure that MPI remains a driving force for future software and hardware architectures. We are also working on non-blocking designs of non-communicating MPI routines such as the connection/disconnection functions.
Footnotes
Acknowledgements
We thank the anonymous reviewers for their insightful comments.
Funding
This work was supported in part by the Natural Sciences and Engineering Research Council of Canada (grant #RGPIN/238964-2011), by the Canada Foundation for Innovation and by the Ontario Innovation Trust (grant #7154). This work was also supported in part by the Office of Advanced Scientific Computing Research, Office of Science, U.S. Department of Energy (contract DE-AC02-06CH11357).
Author biographies
Judicael A Zounmevo is currently a postdoctoral appointee in the Mathematics and Computer Science Department of Argonne National Laboratory, IL. He received his PhD at Queen’s University in Kingston, ON, Canada, in 2014 under the supervision of Ahmad Afsahi. He is interested in HPC communication runtimes such as MPI and in HPC-targeted operating systems.
Dries Kimpe is an assistant computer scientist at Argonne National Laboratory, IL. He obtained his Master’s from the University of Ghent and his PhD from the Catholic University of Leuven. His main research topics are parallel I/O, distributed file systems and parallel programming models.
Robert Ross is a pioneer in the design of parallel file systems and high-performance interfaces for managing large datasets. He led the development of the parallel virtual file system (PVFS) used in many academic, industry and laboratory settings, including the nation’s leadership-class computing facilities. He leads storage research in the DOE SciDAC Enabling Technology Center for Scientific Data Management and is Associate Director of the SciDAC Institute for Ultra-Scale Visualization, where he is developing tools to help researchers address challenges in storage, retrieval and the extraction of meaning from very large scientific datasets. Ross is a Fellow of the University of Chicago/Argonne Computation Institute.
Ahmad Afsahi is currently an Associate Professor in the Department of Electrical and Computer Engineering at Queen’s University in Kingston, ON, Canada. He received his PhD degree in Electrical Engineering from the University of Victoria, BC, in 2000. His research activities are in the areas of parallel and distributed processing, network-based high-performance computing, high-speed networks and power-aware high-performance computing. He has mainly been focused on improving the performance and scalability of communication subsystems, messaging layers and runtime systems for high-performance computing and data centers.
