wallet: simplify payments lookup so sqlite3 uses index.

Filtering by status is rare, so we can do it in the caller; just let sqlite3
filter by payment_hash.

With ~650,000 payments in db:

Before:
```
129/300000 complete 5.60/sec (33078 invs, 169 pays, 0 retries) in 30 seconds. 19 hours-14 hours remaining.
201/300000 complete 7.20/sec (43519 invs, 241 pays, 0 retries) in 40 seconds. 16 hours-11 hours remaining.
257/300000 complete 5.60/sec (54568 invs, 289 pays, 0 retries) in 50 seconds. 16 hours-14 hours remaining.
305/300000 complete 4.80/sec (65772 invs, 337 pays, 0 retries) in 60 seconds. 16 hours-17 hours remaining.
361/300000 complete 5.60/sec (75875 invs, 401 pays, 0 retries) in 70 seconds. 16 hours-14 hours remaining.
```

After:
```
760/300000 complete 40.00/sec (19955 invs, 824 pays, 0 retries) in 20 seconds. 2 hours-2 hours remaining.
1176/300000 complete 41.60/sec (30082 invs, 1224 pays, 0 retries) in 30 seconds. 2 hours-119 minutes remaining.
1584/300000 complete 40.80/sec (40224 invs, 1640 pays, 0 retries) in 40 seconds. 2 hours-2 hours remaining.
1984/300000 complete 40.00/sec (49938 invs, 2048 pays, 0 retries) in 50 seconds. 2 hours-2 hours remaining.
```

Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
This commit is contained in:
Rusty Russell 2022-09-19 10:19:53 +09:30 committed by Christian Decker
parent 2a5660b3bc
commit 2022e4a7a9
4 changed files with 91 additions and 72 deletions

View file

@ -285,7 +285,7 @@ static struct command_result *prev_payment(struct command *cmd,
bool prev_paid = false; bool prev_paid = false;
assert(!invreq->payer_info); assert(!invreq->payer_info);
payments = wallet_payment_list(cmd, cmd->ld->wallet, NULL, NULL); payments = wallet_payment_list(cmd, cmd->ld->wallet, NULL);
for (size_t i = 0; i < tal_count(payments); i++) { for (size_t i = 0; i < tal_count(payments); i++) {
const struct tlv_invoice *inv; const struct tlv_invoice *inv;

View file

@ -37,15 +37,16 @@ struct sendpay_command {
struct command *cmd; struct command *cmd;
}; };
static bool string_to_payment_status(const char *status_str, enum wallet_payment_status *status) static bool string_to_payment_status(const char *status_str, size_t len,
enum wallet_payment_status *status)
{ {
if (streq(status_str, "complete")) { if (memeqstr(status_str, len, "complete")) {
*status = PAYMENT_COMPLETE; *status = PAYMENT_COMPLETE;
return true; return true;
} else if (streq(status_str, "pending")) { } else if (memeqstr(status_str, len, "pending")) {
*status = PAYMENT_PENDING; *status = PAYMENT_PENDING;
return true; return true;
} else if (streq(status_str, "failed")) { } else if (memeqstr(status_str, len, "failed")) {
*status = PAYMENT_FAILED; *status = PAYMENT_FAILED;
return true; return true;
} }
@ -883,7 +884,7 @@ send_payment_core(struct lightningd *ld,
bool have_complete = false; bool have_complete = false;
/* Now, do we already have one or more payments? */ /* Now, do we already have one or more payments? */
payments = wallet_payment_list(tmpctx, ld->wallet, rhash, NULL); payments = wallet_payment_list(tmpctx, ld->wallet, rhash);
for (size_t i = 0; i < tal_count(payments); i++) { for (size_t i = 0; i < tal_count(payments); i++) {
log_debug(ld->log, "Payment %zu/%zu: %s %s", log_debug(ld->log, "Payment %zu/%zu: %s %s",
i, tal_count(payments), i, tal_count(payments),
@ -1545,6 +1546,22 @@ static const struct json_command waitsendpay_command = {
}; };
AUTODATA(json_command, &waitsendpay_command); AUTODATA(json_command, &waitsendpay_command);
static struct command_result *param_payment_status(struct command *cmd,
const char *name,
const char *buffer,
const jsmntok_t *tok,
enum wallet_payment_status **status)
{
*status = tal(cmd, enum wallet_payment_status);
if (string_to_payment_status(buffer + tok->start,
tok->end - tok->start,
*status))
return NULL;
return command_fail_badparam(cmd, name, buffer, tok,
"should be an invoice status");
}
static struct command_result *json_listsendpays(struct command *cmd, static struct command_result *json_listsendpays(struct command *cmd,
const char *buffer, const char *buffer,
const jsmntok_t *obj UNNEEDED, const jsmntok_t *obj UNNEEDED,
@ -1553,13 +1570,14 @@ static struct command_result *json_listsendpays(struct command *cmd,
const struct wallet_payment **payments; const struct wallet_payment **payments;
struct json_stream *response; struct json_stream *response;
struct sha256 *rhash; struct sha256 *rhash;
const char *invstring, *status_str; const char *invstring;
enum wallet_payment_status *status;
if (!param(cmd, buffer, params, if (!param(cmd, buffer, params,
/* FIXME: parameter should be invstring now */ /* FIXME: parameter should be invstring now */
p_opt("bolt11", param_string, &invstring), p_opt("bolt11", param_string, &invstring),
p_opt("payment_hash", param_sha256, &rhash), p_opt("payment_hash", param_sha256, &rhash),
p_opt("status", param_string, &status_str), p_opt("status", param_payment_status, &status),
NULL)) NULL))
return command_param_failed(); return command_param_failed();
@ -1591,19 +1609,13 @@ static struct command_result *json_listsendpays(struct command *cmd,
} }
} }
if (status_str) { payments = wallet_payment_list(cmd, cmd->ld->wallet, rhash);
enum wallet_payment_status status;
if (!string_to_payment_status(status_str, &status))
return command_fail(cmd, JSONRPC2_INVALID_PARAMS, "Unrecognized status: %s", status_str);
payments = wallet_payment_list(cmd, cmd->ld->wallet, rhash, &status);
} else
payments = wallet_payment_list(cmd, cmd->ld->wallet, rhash, NULL);
response = json_stream_success(cmd); response = json_stream_success(cmd);
json_array_start(response, "payments"); json_array_start(response, "payments");
for (size_t i = 0; i < tal_count(payments); i++) { for (size_t i = 0; i < tal_count(payments); i++) {
if (status && payments[i]->status != *status)
continue;
json_object_start(response, NULL); json_object_start(response, NULL);
json_add_payment_fields(response, payments[i]); json_add_payment_fields(response, payments[i]);
json_object_end(response); json_object_end(response);
@ -1629,40 +1641,36 @@ static struct command_result *json_delpay(struct command *cmd,
{ {
struct json_stream *response; struct json_stream *response;
const struct wallet_payment **payments; const struct wallet_payment **payments;
const char *status_str; enum wallet_payment_status *status;
enum wallet_payment_status status;
struct sha256 *payment_hash; struct sha256 *payment_hash;
if (!param(cmd, buffer, params, if (!param(cmd, buffer, params,
p_req("payment_hash", param_sha256, &payment_hash), p_req("payment_hash", param_sha256, &payment_hash),
p_req("status", param_string, &status_str), p_req("status", param_payment_status, &status),
NULL)) NULL))
return command_param_failed(); return command_param_failed();
if (!string_to_payment_status(status_str, &status)) switch (*status) {
return command_fail(cmd, JSONRPC2_INVALID_PARAMS, "Unrecognized status: %s", status_str);
switch(status){
case PAYMENT_COMPLETE: case PAYMENT_COMPLETE:
case PAYMENT_FAILED: case PAYMENT_FAILED:
break; break;
case PAYMENT_PENDING: case PAYMENT_PENDING:
return command_fail(cmd, JSONRPC2_INVALID_PARAMS, "Invalid status: %s", return command_fail(cmd, JSONRPC2_INVALID_PARAMS, "Invalid status: %s",
payment_status_to_string(status)); payment_status_to_string(*status));
} }
payments = wallet_payment_list(cmd, cmd->ld->wallet, payment_hash, NULL); payments = wallet_payment_list(cmd, cmd->ld->wallet, payment_hash);
if (tal_count(payments) == 0) if (tal_count(payments) == 0)
return command_fail(cmd, PAY_NO_SUCH_PAYMENT, "Unknown payment with payment_hash: %s", return command_fail(cmd, PAY_NO_SUCH_PAYMENT, "Unknown payment with payment_hash: %s",
type_to_string(tmpctx, struct sha256, payment_hash)); type_to_string(tmpctx, struct sha256, payment_hash));
for (int i = 0; i < tal_count(payments); i++) { for (int i = 0; i < tal_count(payments); i++) {
if (payments[i]->status != status) { if (payments[i]->status != *status) {
return command_fail(cmd, PAY_STATUS_UNEXPECTED, "Payment with hash %s has %s status but it should be %s", return command_fail(cmd, PAY_STATUS_UNEXPECTED, "Payment with hash %s has %s status but it should be %s",
type_to_string(tmpctx, struct sha256, payment_hash), type_to_string(tmpctx, struct sha256, payment_hash),
payment_status_to_string(payments[i]->status), payment_status_to_string(payments[i]->status),
payment_status_to_string(status)); payment_status_to_string(*status));
} }
} }

View file

@ -3518,8 +3518,7 @@ void wallet_payment_set_failinfo(struct wallet *wallet,
const struct wallet_payment ** const struct wallet_payment **
wallet_payment_list(const tal_t *ctx, wallet_payment_list(const tal_t *ctx,
struct wallet *wallet, struct wallet *wallet,
const struct sha256 *payment_hash, const struct sha256 *payment_hash)
enum wallet_payment_status *status)
{ {
const struct wallet_payment **payments; const struct wallet_payment **payments;
struct db_stmt *stmt; struct db_stmt *stmt;
@ -3528,9 +3527,7 @@ wallet_payment_list(const tal_t *ctx,
payments = tal_arr(ctx, const struct wallet_payment *, 0); payments = tal_arr(ctx, const struct wallet_payment *, 0);
u8 enable_payment_hash = payment_hash != NULL ? 0 : 1; if (payment_hash) {
u8 enable_status = status != NULL ? 0 : 1;
stmt = db_prepare_v2(wallet->db, SQL("SELECT" stmt = db_prepare_v2(wallet->db, SQL("SELECT"
" id" " id"
", status" ", status"
@ -3554,21 +3551,34 @@ wallet_payment_list(const tal_t *ctx,
", completed_at" ", completed_at"
" FROM payments" " FROM payments"
" WHERE" " WHERE"
" (payment_hash = ? OR 1=?) AND" " payment_hash = ?"
" (status = ? OR 1=?)"
" ORDER BY id;")); " ORDER BY id;"));
if (payment_hash)
db_bind_sha256(stmt, 0, payment_hash); db_bind_sha256(stmt, 0, payment_hash);
else } else {
db_bind_null(stmt, 0); stmt = db_prepare_v2(wallet->db, SQL("SELECT"
db_bind_int(stmt, 1, enable_payment_hash); " id"
if (status) ", status"
db_bind_int(stmt, 2, wallet_payment_status_in_db(*status)); ", destination"
else ", msatoshi"
db_bind_null(stmt, 2); ", payment_hash"
db_bind_int(stmt, 3, enable_status); ", timestamp"
", payment_preimage"
", path_secrets"
", route_nodes"
", route_channels"
", msatoshi_sent"
", description"
", bolt11"
", paydescription"
", failonionreply"
", total_msat"
", partid"
", local_offer_id"
", groupid"
", completed_at"
" FROM payments"
" ORDER BY id;"));
}
db_query_prepared(stmt); db_query_prepared(stmt);
for (i = 0; db_step(stmt); i++) { for (i = 0; db_step(stmt); i++) {

View file

@ -1183,8 +1183,9 @@ void wallet_payment_set_failinfo(struct wallet *wallet,
*/ */
const struct wallet_payment **wallet_payment_list(const tal_t *ctx, const struct wallet_payment **wallet_payment_list(const tal_t *ctx,
struct wallet *wallet, struct wallet *wallet,
const struct sha256 *payment_hash, const struct sha256 *payment_hash)
enum wallet_payment_status *status); NON_NULL_ARGS(2);
/** /**
* wallet_payments_by_offer - Retrieve a list of payments for this local_offer_id * wallet_payments_by_offer - Retrieve a list of payments for this local_offer_id