@@ -126,10 +126,31 @@ impl DownloadsCounter {
126
126
counted_downloads += count;
127
127
counted_versions += 1 ;
128
128
129
- to_insert. push ( ( version_id . eq ( * key) , downloads . eq ( count as i32 ) ) ) ;
129
+ to_insert. push ( ( * key, count) ) ;
130
130
}
131
131
132
132
if !to_insert. is_empty ( ) {
133
+ // The rows we're about to insert need to be sorted to avoid deadlocks when multiple
134
+ // instances of crates.io are running at the same time.
135
+ //
136
+ // In PostgreSQL a transaction modifying a row locks that row until the transaction is
137
+ // committed. Multiple transactions inserting rows into a table could end up
138
+ // deadlocking each other though: PostgreSQL will detect that deadlock, abort one of
139
+ // the transactions and allow the other one to continue. We don't want that to happen,
140
+ // as we'd lose the downloads from the aborted transaction.
141
+ //
142
+ // Ensuring the rows are inserted in a consistent order (in our case by sorting them by
143
+ // the version ID) will prevent deadlocks from occuring. For more information:
144
+ //
145
+ // https://www.postgresql.org/docs/11/explicit-locking.html#LOCKING-DEADLOCKS
146
+ //
147
+ to_insert. sort_by_key ( |( key, _) | * key) ;
148
+
149
+ let to_insert = to_insert
150
+ . into_iter ( )
151
+ . map ( |( key, count) | ( version_id. eq ( key) , downloads. eq ( count as i32 ) ) )
152
+ . collect :: < Vec < _ > > ( ) ;
153
+
133
154
diesel:: insert_into ( version_downloads)
134
155
. values ( & to_insert)
135
156
. on_conflict ( ( version_id, date) )
0 commit comments