We introduce canonical sequentialization, a new approach to verifying unbounded, asynchronous, messagepassing programs at compile-time. Our approach builds upon the following observation: due the combinatorial explosion in complexity, programmers do not reason about their systems by case-splitting over all the possible execution orders. Instead, correct programs tend to be well-structured so that the programmer can reason about a small number of representative executions, which we call the program's canonical sequentialization. We have implemented our approach in a tool called Brisk that synthesizes canonical sequentializations for programs written in Haskell, and evaluated it on a wide variety of distributed systems including benchmarks from the literature and implementations of MapReduce, two-phase commit, and a version of the Disco distributed file-system. Brisk verifies unbounded versions of the benchmarks in tens of milliseconds, yielding the first concurrency verification tool that is fast enough to be integrated into a design-implement-check cycle. is limited to finite systems: when the number of processes is unbounded, e.g., if processes are spawned based on input parameters, model checking cannot guarantee correctness as the state space, and hence, the number of executions, is infinite. Consequently, the distributed systems developer is bereft of development-and compile-time tools that guarantee the absence of concurrency errors.Our Approach In this paper, we propose canonical sequentialization, a new approach to verifying concurrency properties of unbounded, asynchronous, message-passing programs at compile-time. Our approach builds upon the following observation: due the combinatorial explosion in complexity, programmers do not reason about their systems by case-splitting over all the possible execution orders. Instead, correct programs tend to be well-structured so that the programmer can reason about a small number of representative executions which we call the program's canonical sequentialization.Example: Two-Phase Commit Consider the classic two-phase commit protocol [Lampson and Sturgis 1976], which consists of a leader process trying to commit a transaction to a number of database nodes. The protocol proceeds in two phases. In the first phase, the leader issues a tentative transaction. The leader then waits for answers from the nodes who may decide to either accept or abort the transaction. This initiates the second phase: if all nodes agreed, the leader sends them a commit message; otherwise, it sends an abort message. Finally, each node sends its acknowledgement of the final decision.Even though leader and database nodes execute in parallel, the concurrency is well-structured. First, due to the lack of a shared memory, most actions executed by different nodes are independent of each other, and thus commute. For example, a tentative proposal sent to one of the database nodes is independent of the messages other database nodes might send or receive. Thus, in the spirit of Lipton's movers [Lipto...