[GH-ISSUE #1272] [FEATURE REQUEST] Run hasher on service thread #446

Closed
opened 2026-02-27 08:17:20 +03:00 by kerem · 6 comments
Owner

Originally created by @thielj on GitHub (Sep 4, 2025).
Original GitHub issue: https://github.com/lldap/lldap/issues/1272

Motivation
argon2 hashing blocks the single server thread for a significant duration.

Describe the solution you'd like
Run the hasher on a background thread and await crypto tasks.

Describe alternatives you've considered
Multiple LDAP server worker threads - but the background worker is a better choice as it limits CPU and memory usage and at least mitigates DOS attacks.

Additional context
Code is here and I have this in use already. Let me know if you want a PR.

https://github.com/thielj/lldap/blob/main/crates/sql-backend-handler/src/password_service.rs
Plus a couple lines in sql_(backend|opaque)_handlers.rs

Originally created by @thielj on GitHub (Sep 4, 2025). Original GitHub issue: https://github.com/lldap/lldap/issues/1272 **Motivation** argon2 hashing blocks the single server thread for a significant duration. **Describe the solution you'd like** Run the hasher on a background thread and await crypto tasks. **Describe alternatives you've considered** Multiple LDAP server worker threads - but the background worker is a better choice as it limits CPU and memory usage and at least mitigates DOS attacks. **Additional context** Code is here and I have this in use already. Let me know if you want a PR. https://github.com/thielj/lldap/blob/main/crates/sql-backend-handler/src/password_service.rs Plus a couple lines in sql_(backend|opaque)_handlers.rs
kerem 2026-02-27 08:17:20 +03:00
Author
Owner

@nitnelave commented on GitHub (Sep 4, 2025):

It seems it would be simpler to run more than one worker thread. Maybe 3? And yes, I am aware that it would still stall requests if there are 3+ hashing in parallel, but that's an acceptable tradeoff, I think (at least for the goal of this project)

<!-- gh-comment-id:3255417798 --> @nitnelave commented on GitHub (Sep 4, 2025): It seems it would be simpler to run more than one worker thread. Maybe 3? And yes, I am aware that it would still stall requests if there are 3+ hashing in parallel, but that's an acceptable tradeoff, I think (at least for the goal of this project)
Author
Owner

@thielj commented on GitHub (Sep 5, 2025):

It's still blocking already processing requests during hashing - no matter
how many server worker threads you create 🤷

You wouldn't run a 3ms synchronous DB query either. Why do you insist on
running the ~300ms hashing synchronously?

On Thu, Sep 4, 2025, 21:05 nitnelave @.***> wrote:

nitnelave left a comment (lldap/lldap#1272)
https://github.com/lldap/lldap/issues/1272#issuecomment-3255417798

It seems it would be simpler to run more than one worker thread. Maybe 3?
And yes, I am aware that it would still stall requests if there are 3+
hashing in parallel, but that's an acceptable tradeoff, I think (at least
for the goal of this project)


Reply to this email directly, view it on GitHub
https://github.com/lldap/lldap/issues/1272#issuecomment-3255417798, or
unsubscribe
https://github.com/notifications/unsubscribe-auth/AA6CUVJ7TBIFMYVVCPVF5XL3RCLRLAVCNFSM6AAAAACFUU7LYSVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZTENJVGQYTONZZHA
.
You are receiving this because you authored the thread.Message ID:
@.***>

<!-- gh-comment-id:3256832430 --> @thielj commented on GitHub (Sep 5, 2025): It's still blocking already processing requests during hashing - no matter how many server worker threads you create 🤷 You wouldn't run a 3ms synchronous DB query either. Why do you insist on running the ~300ms hashing synchronously? On Thu, Sep 4, 2025, 21:05 nitnelave ***@***.***> wrote: > *nitnelave* left a comment (lldap/lldap#1272) > <https://github.com/lldap/lldap/issues/1272#issuecomment-3255417798> > > It seems it would be simpler to run more than one worker thread. Maybe 3? > And yes, I am aware that it would still stall requests if there are 3+ > hashing in parallel, but that's an acceptable tradeoff, I think (at least > for the goal of this project) > > — > Reply to this email directly, view it on GitHub > <https://github.com/lldap/lldap/issues/1272#issuecomment-3255417798>, or > unsubscribe > <https://github.com/notifications/unsubscribe-auth/AA6CUVJ7TBIFMYVVCPVF5XL3RCLRLAVCNFSM6AAAAACFUU7LYSVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZTENJVGQYTONZZHA> > . > You are receiving this because you authored the thread.Message ID: > ***@***.***> >
Author
Owner

@nitnelave commented on GitHub (Sep 5, 2025):

I don't agree with you: if you have several workers, you can have one or more of them hashing passwords while the others handle the rest of the requests, that's independent.

Hashing is different from a DB request: DB requests are I/O where the bulk of the work is outside the process (network), so the thread has nothing to do while waiting for the response. It is free to pick up the rest of the requests where they are waiting. Note that it's also perfectly reasonable to do synchronous DB requests if you create one thread per user request (modulo parallelization). Not that this is what happens in LLDAP.

If you have a single background thread for hashing and 3 worker threads, when 2 login requests come in at the same time they'll be queued one after the other. If the hashing is done synchronously, both login attempts will be done in parallel.

In the busier case, with 10 login attempts and 100 other requests, the background hashing service would process the 10 login attempts serially and the 100 requests very fast. If everything is done synchronously instead, each query will be processed in the order they come in, up to 3 in parallel. That increases throughput at the cost of non-login latency.

<!-- gh-comment-id:3257180134 --> @nitnelave commented on GitHub (Sep 5, 2025): I don't agree with you: if you have several workers, you can have one or more of them hashing passwords while the others handle the rest of the requests, that's independent. Hashing is different from a DB request: DB requests are I/O where the bulk of the work is outside the process (network), so the thread has nothing to do while waiting for the response. It is free to pick up the rest of the requests where they are waiting. Note that it's also perfectly reasonable to do synchronous DB requests if you create one thread per user request (modulo parallelization). Not that this is what happens in LLDAP. If you have a single background thread for hashing and 3 worker threads, when 2 login requests come in at the same time they'll be queued one after the other. If the hashing is done synchronously, both login attempts will be done in parallel. In the busier case, with 10 login attempts and 100 other requests, the background hashing service would process the 10 login attempts serially and the 100 requests very fast. If everything is done synchronously instead, each query will be processed in the order they come in, up to 3 in parallel. That increases throughput at the cost of non-login latency.
Author
Owner

@thielj commented on GitHub (Sep 5, 2025):

I wouldn't want 10 login attempts to be processed in parallel unless the machine running LLDAP has 10+ cores and sufficient RAM. If I wanted to allocate more CPU and RAM resources to hashing, I would add additional hasher threads to match the VM specs.

On a typical 1 vCPU VM, processing 10 hashes in parallel makes every client wait 3000ms and allocates > 500MB of additional RAM. Processing them sequentially (with one hasher) makes the first client wait 300ms, the second 600ms, etc. On average, clients wait ~1500ms. And it uses just 50MB.

Overwhelming a server with binds is the perfect DOS when all worker threads are hashing. Hasher threads give you at least some form of rate-control and DOS mitigation.

<!-- gh-comment-id:3257896821 --> @thielj commented on GitHub (Sep 5, 2025): I wouldn't want 10 login attempts to be processed in parallel unless the machine running LLDAP has 10+ cores and sufficient RAM. If I wanted to allocate more CPU and RAM resources to hashing, I would add additional hasher threads to match the VM specs. On a typical 1 vCPU VM, processing 10 hashes in parallel makes every client wait 3000ms and allocates > 500MB of additional RAM. Processing them sequentially (with one hasher) makes the first client wait 300ms, the second 600ms, etc. On average, clients wait ~1500ms. And it uses just 50MB. Overwhelming a server with binds is the perfect DOS when all worker threads are hashing. Hasher threads give you at least some form of rate-control and DOS mitigation.
Author
Owner

@nitnelave commented on GitHub (Sep 5, 2025):

If you expect 10 login attempts in parallel, I hope you'll get a machine with more than 1 CPU!

But to the broader point, I'll have to remind you that having the best performance under high load is not one of the main objectives of the projects. A different LDAP server with better attention to scale might serve you better. You can check out Kanidm for instance, or there's always openLDAP.

My main concern with LLDAP, given the little time I have to dedicate to it, is to keep it maintainable (so simple) and easy to set up and use for the target users (think homelabs not Enterprise)

<!-- gh-comment-id:3257970602 --> @nitnelave commented on GitHub (Sep 5, 2025): If you expect 10 login attempts in parallel, I hope you'll get a machine with more than 1 CPU! But to the broader point, I'll have to remind you that having the best performance under high load is not one of the main objectives of the projects. A different LDAP server with better attention to scale might serve you better. You can check out Kanidm for instance, or there's always openLDAP. My main concern with LLDAP, given the little time I have to dedicate to it, is to keep it maintainable (so simple) and easy to set up and use for the target users (think homelabs not Enterprise)
Author
Owner

@thielj commented on GitHub (Sep 5, 2025):

I don't expect 10 login attempts - I followed up on your "In the busier case, with 10 login attempts" scenario.

I do think that my suggestion is maintainable as it encapsulates all login and password verification logic for all services (LDAP, web frontend - both OPAQUE and cleartext) in a simple unit, with an async API that handles the necessary conversions and is unlikely to change often. I would have preferred to move it to the auth crate, but it currently has some lldap specific types and I wanted to keep the changes to a minimum.

It doesn't require configuration: for most LLDAP use cases (homelabs and small groups) a single hasher thread is ideal and provides automatic rate- and resource-limiting (what you consider the inferior "sequential processing"). If you really needed to scale up LLDAP in an enterprise, you would do so by spinning up additional load-balanced instances during the morning peak instead of running a beefy machine 24x7.

When you talk about "Enterprise": their use case for LLDAP isn't authenticating 10k users or services, but more often smaller island solutions or maybe external facing services. For proxying an existing AD or "corporate" LDAP, there's GLAuth, too. However, they still need things to be redundant, probably through a trusted replicated DB, and with multiple additional LLDAP instances running locally in "de-facto read-only mode" without GUI.

There's even plenty of homelab users looking for redundancy when it comes to authentication and authorization. I have looked into rqlite for that. Sadly, the SQLx driver isn't up-to-date and would need some work.

Anyway, you know where my code is. I've closed this issue and want to close the discussion as well.

<!-- gh-comment-id:3258255920 --> @thielj commented on GitHub (Sep 5, 2025): I don't expect 10 login attempts - I followed up on your "In the busier case, with 10 login attempts" scenario. I do think that my suggestion is maintainable as it encapsulates all login and password verification logic for all services (LDAP, web frontend - both OPAQUE and cleartext) in a simple unit, with an async API that handles the necessary conversions and is unlikely to change often. I would have preferred to move it to the auth crate, but it currently has some lldap specific types and I wanted to keep the changes to a minimum. It doesn't require configuration: for most LLDAP use cases (homelabs and small groups) a single hasher thread is ideal and provides automatic rate- and resource-limiting (what you consider the inferior "sequential processing"). If you really needed to scale up LLDAP in an enterprise, you would do so by spinning up additional load-balanced instances during the morning peak instead of running a beefy machine 24x7. When you talk about "Enterprise": their use case for LLDAP isn't authenticating 10k users or services, but more often smaller island solutions or maybe external facing services. For proxying an existing AD or "corporate" LDAP, there's GLAuth, too. However, they still need things to be redundant, probably through a trusted replicated DB, and with multiple additional LLDAP instances running locally in "de-facto read-only mode" without GUI. There's even plenty of homelab users looking for redundancy when it comes to authentication and authorization. I have looked into rqlite for that. Sadly, the SQLx driver isn't up-to-date and would need some work. Anyway, you know where my code is. I've closed this issue and want to close the discussion as well.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
starred/lldap-lldap#446
No description provided.