diff --git a/wallet/db.c b/wallet/db.c
index 4e9eb042bdc6..036765c0d837 100644
--- a/wallet/db.c
+++ b/wallet/db.c
@@ -2,8 +2,10 @@
 
 #include <bitcoin/script.h>
 #include <ccan/array_size/array_size.h>
+#include <ccan/build_assert/build_assert.h>
 #include <ccan/mem/mem.h>
 #include <ccan/tal/str/str.h>
+#include <common/htlc_state.h>
 #include <common/key_derive.h>
 #include <common/onionreply.h>
 #include <common/version.h>
@@ -858,6 +860,14 @@ static struct migration dbmigrations[] = {
 
     /* Issue #4887: reset the payments.id sequence after the migration above. Since this is a SELECT statement that would otherwise fail, make it an INSERT into the `vars` table.*/
     {SQL("/*PSQL*/INSERT INTO vars (name, intval) VALUES ('payment_id_reset', setval(pg_get_serial_sequence('payments', 'id'), COALESCE((SELECT MAX(id)+1 FROM payments), 1)))"), NULL},
+
+    /* Issue #4901: Partial index speeds up startup on nodes with ~1000 channels.  */
+    {&SQL("CREATE INDEX channel_htlcs_speedup_unresolved_idx"
+	 "    ON channel_htlcs(channel_id, direction)"
+	 " WHERE hstate NOT IN (9, 19);")
+	[BUILD_ASSERT_OR_ZERO( 9 == RCVD_REMOVE_ACK_REVOCATION) +
+	 BUILD_ASSERT_OR_ZERO(19 == SENT_REMOVE_ACK_REVOCATION)],
+     NULL},
 };
 
 /* Leak tracking. */
diff --git a/wallet/wallet.c b/wallet/wallet.c
index b26ec943d823..e7ad0f7f923b 100644
--- a/wallet/wallet.c
+++ b/wallet/wallet.c
@@ -2734,10 +2734,16 @@ bool wallet_htlcs_load_in_for_channel(struct wallet *wallet,
 					     " FROM channel_htlcs"
 					     " WHERE direction= ?"
 					     " AND channel_id= ?"
-					     " AND hstate != ?"));
+					     " AND hstate NOT IN (?, ?)"));
 	db_bind_int(stmt, 0, DIRECTION_INCOMING);
 	db_bind_u64(stmt, 1, chan->dbid);
-	db_bind_int(stmt, 2, SENT_REMOVE_ACK_REVOCATION);
+	/* We need to generate `hstate NOT IN (9, 19)` in order to match
+	 * the `WHERE` clause of the database index; incoming HTLCs will
+	 * never actually get the state `RCVD_REMOVE_ACK_REVOCATION`.
+	 * See https://sqlite.org/partialindex.html#queries_using_partial_indexes
+	 */
+	db_bind_int(stmt, 2, RCVD_REMOVE_ACK_REVOCATION); /* Not gonna happen.  */
+	db_bind_int(stmt, 3, SENT_REMOVE_ACK_REVOCATION);
 	db_query_prepared(stmt);
 
 	while (db_step(stmt)) {
@@ -2780,10 +2786,16 @@ bool wallet_htlcs_load_out_for_channel(struct wallet *wallet,
 					     " FROM channel_htlcs"
 					     " WHERE direction = ?"
 					     " AND channel_id = ?"
-					     " AND hstate != ?"));
+					     " AND hstate NOT IN (?, ?)"));
 	db_bind_int(stmt, 0, DIRECTION_OUTGOING);
 	db_bind_u64(stmt, 1, chan->dbid);
+	/* We need to generate `hstate NOT IN (9, 19)` in order to match
+	 * the `WHERE` clause of the database index; outgoing HTLCs will
+	 * never actually get the state `SENT_REMOVE_ACK_REVOCATION`.
+	 * See https://sqlite.org/partialindex.html#queries_using_partial_indexes
+	 */
 	db_bind_int(stmt, 2, RCVD_REMOVE_ACK_REVOCATION);
+	db_bind_int(stmt, 3, SENT_REMOVE_ACK_REVOCATION); /* Not gonna happen.  */
 	db_query_prepared(stmt);
 
 	while (db_step(stmt)) {