Skip to content

listpack: add lpFindInteger() to avoid string conversion in set integ…#3410

Open
liveprasad wants to merge 4 commits into
valkey-io:unstablefrom
liveprasad:fix-3408-add-lpFindInteger
Open

listpack: add lpFindInteger() to avoid string conversion in set integ…#3410
liveprasad wants to merge 4 commits into
valkey-io:unstablefrom
liveprasad:fix-3408-add-lpFindInteger

Conversation

@liveprasad

Copy link
Copy Markdown

Summary

Add lpFindInteger() to listpack.c to avoid an integer→string→integer
round-trip when searching for integers in listpack-encoded sets.

Previously, setTypeAdd() converted integers to strings to call lpFind(),
which then called string2ll() internally to convert back. This resolves
the existing TODO comment at src/t_set.c:168.

Changes

  • src/listpack.c — add lpFindInteger(lp, p, v, skip)
  • src/listpack.h — declare it
  • src/t_set.c — use it when str == tmpbuf; remove TODO comment
  • src/unit/test_listpack.cpp — add listpackLpFindInteger test

Fixes #3408

…er search

When a set command like SADD receives an integer value and the set is
listpack-encoded, the previous code converted the integer to a string to
call lpFind(), which then converted it back to an integer internally.

Add lpFindInteger() that accepts a long long directly and compares against
integer-encoded listpack entries without any string conversion. Use it in
setTypeAdd() when the value arrived as an integer (str == tmpbuf).

Fixes valkey-io#3408

Signed-off-by: liveprasad <prasad.shetkar093@gmail.com>
@codecov

codecov Bot commented Mar 30, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 85.71429% with 5 lines in your changes missing coverage. Please review.
✅ Project coverage is 76.52%. Comparing base (b83209d) to head (7d2e056).
⚠️ Report is 1 commits behind head on unstable.

Files with missing lines Patch % Lines
src/listpack.c 73.68% 5 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##           unstable    #3410      +/-   ##
============================================
- Coverage     76.72%   76.52%   -0.20%     
============================================
  Files           159      159              
  Lines         79681    79715      +34     
============================================
- Hits          61135    61003     -132     
- Misses        18546    18712     +166     
Files with missing lines Coverage Δ
src/t_set.c 98.06% <100.00%> (+<0.01%) ⬆️
src/unit/test_listpack.cpp 79.76% <100.00%> (+0.32%) ⬆️
src/listpack.c 93.02% <73.68%> (-0.10%) ⬇️

... and 22 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@dvkashapov dvkashapov left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not an expert in listpacks, just took a look at lpFind() for comparison and have a couple of questions.

Comment thread src/listpack.c Outdated
p += skip_entry_size;
}
/* Safety: ensure we don't walk past the end */
if (p >= lp + lp_bytes) break;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lpFind() has this guard after every add to p:

if (p + 8 >= lp + lp_bytes)
    lpAssertValidEntry(lp, lp_bytes, p);
else
    assert(p >= lp + LP_HDR_SIZE && p < lp + lp_bytes);
if (unlikely(p[0] == LP_EOF)) {
    /* EOF must only appear at the end of a listpack. */
    assert(p + 1 == lp + lp_bytes);
    break;
}

Do we also need LP_EOF check in lpFindInteger()?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks and fixed

Comment thread src/listpack.c Outdated
skipcnt--;
uint64_t skip_entry_size;
lpGetWithSize(p, &ll, NULL, &skip_entry_size);
p += skip_entry_size;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lpFind() uses lpSkip(p) with the explicit comment:

/* Move to next entry, avoid use `lpNext` due to `lpAssertValidEntry` in
 * `lpNext` will call `lpBytes`, will cause performance degradation */
p = lpSkip(p);

Do we need to do the same here to not do a full get? Looks like lpSkip() reads only the encoding to determine entry length.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

lpFree(lp);
}

TEST_F(ListpackTest, listpackLpFindInteger) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what happens if we try to add "42" (as string) and 42 as an integer. In existing behaviour, "42" should be read as 42 integer as well. Can we add a test so that "42" and 42 are not treated as separate elements?

Image

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added the test case

- Use lpSkip() in the skip branch instead of lpGetWithSize() to avoid
  unnecessary entry decoding when skipping entries
- Add LP_EOF check matching lpFind() pattern for safe listpack traversal
- Add test case verifying string "42" and integer 42 find the same entry

Fixes valkey-io#3408

Signed-off-by: liveprasad <prasad.shetkar093@gmail.com>

@liveprasad liveprasad left a comment

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Comment thread src/t_set.c
Comment on lines +162 to +163
if (str == tmpbuf) {
p = lpFindInteger(lp, p, llval, 0);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When we lookup by integer, can we even avoid converting to string above?

        /* Convert int to string. */
        len = ll2string(tmpbuf, sizeof tmpbuf, llval);

I don't know how to structur this in a nice way. Maybe move the listpack-with-integer handlig to the if (!str) { ... } block above?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. To avoid ll2string() entirely for the listpack path,
we'd need to handle listpack inside the if (!str) block before
line 137. Would you prefer that restructuring(Not completely trivial though), or is the current
approach (avoiding the re-conversion inside lpFind) acceptable?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey any thoughts?

Signed-off-by: liveprasad <prasad.shetkar093@gmail.com>
@liveprasad liveprasad requested a review from zuiderkwast April 19, 2026 04:46

@sarthakaggarwal97 sarthakaggarwal97 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! @liveprasad

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add lpFindInteger() to avoid string conversion when searching integers in listpack-encoded sets

4 participants