Skip to content

Single large entry performance issues #327

@subcon42

Description

@subcon42

Hi,

I'm seeing a bit of a performance issue with regards to retrieval of individual entries bearing tens of thousands of values for a given attribute type, such as member on an extremely large group entry.

First off, I've ruled out server issues. Although I am testing against a little Raspberry PI running OpenLDAP on my local network, I can retrieve the entry in question from my desktop using the OpenLDAP command-line utility, as well as Apache Directory Studio GUI, within a very reasonable amount of time (ranging from 0.1 -> 0.4 seconds on average).

However, using the go-ldap package (and executing the snippet below), I'm seeing a significant delay in client-side processing time.

Here are the query parameters shared across all tests/tools:

Search base: cn=Extremely_Large_Group_of_Names,ou=Groups,dc=example,dc=com
Search scope: base (in other words, we're calling the above DN directly, and only that DN)
Search filter: (objectClass=*)
Requested attribute(s): member

Note that we're not authenticating, nor are we using transport security of any kind. This is a textbook-simple cleartext/anonymous operation against a very vanilla OpenLDAP DSA (version 2.4.58-1, which is very recent).

Using OpenLDAP CLI (ldapsearch)

$ time ldapsearch -LLLxsbase -b cn=Extremely_Large_Group_of_Names,ou=Groups,dc=example,dc=com member | grep ^member | wc -l

59999                 
real    0m0.111s
user    0m0.031s
sys     0m0.078s

So, 0.1 seconds. This is good, and what I expect.

Using go-ldap

$ time ./perf
2021/06/29 21:15:22 Got *ldap.Entry (len:59999)

real    0m16.412s
user    0m0.469s
sys     0m9.000s

Ouch. 16.4 seconds.

Here is the go code for the perf test executable:

package main

// built using:
// go build -o perf main_perf.go

import (
        "log"

        "github.com/go-ldap/ldap/v3"
)

func main() {

        // Initialize LDAP connection
        dn := `cn=Extremely_Large_Group_of_Names,ou=Groups,dc=example,dc=com`
        dsa := `ldap.example.com`
        l, err := ldap.Dial(`tcp`, dsa+`:389`)
        if err != nil {
                log.Fatal(err)
        }
        defer l.Close()

        // anonymous bind
        err = l.UnauthenticatedBind(``)
        if err != nil {
                log.Fatal(err)
        }

        attrName := `member`
        req := ldap.NewSearchRequest(dn, 0, 0, 0, 0, false, `(objectClass=*)`, []string{attrName}, nil)

        result, err := l.Search(req)
        if err != nil {
                log.Fatal(err)
        }
        ent := result.Entries[0]

        // Report
        log.Printf("Got %T (len:%d)\n", ent, len(ent.GetAttributeValues(attrName)))
}

The test entry looks like the following, though I can reproduce this with any multi-valued attribute on any entry.

dn: cn=Extremely_Large_Group_of_Names,ou=Groups,dc=example,dc=com
objectClass: top
objectClass: groupOfNames
member: uid=account00001,ou=People,dc=example,dc=com
member: uid=account00002,ou=People,dc=example,dc=com
... 59997 other member values omitted for brevity ...

On the server side, I can watch the OpenLDAP daemon logs in real-time, and I definitely see the request finished immediately with a successful result, so I know that (as far as the server is concerned) its job is done and over with. The delay seems to be entirely client-side.

I checked and I have the latest version of go-ldap, having run go get github.com/go-ldap/ldap/v3 before re-testing and filing this issue.

Thanks, please let me know if there are other materials you need, or other tests you wish me to run.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions