Configure Virtual List View Indexes in 389 Directory Server

Configure 389 Directory Server Virtual List View indexes with dsconf backend vlv-index, test server-side sorting and VLV controls with ldapsearch, and verify vlvUses and access-log usage.

Published

Updated

Read time 15 min read

Reviewed byDeepak Prasad

389 Directory Server Virtual List View browsing index returning a sorted LDAP result window through dsconf backend vlv-index

A directory browser that scrolls through thousands of sorted users should not download the entire result on every page. Virtual List View (VLV) lets an LDAP client ask for a small window around a position in a server-sorted result set. Server-side sorting defines the order; the VLV control defines which slice of that ordered list is returned.

This guide covers the full VLV and server-side sorting workflow in 389 Directory Server:

  • How VLV differs from ordinary attribute indexes
  • When a browsing index is worth creating
  • VLV request windows and ldapsearch syntax
  • Creating vlvSearch and vlvIndex entries with dsconf
  • Building, rebuilding, and monitoring vlvUses
  • Exact search matching requirements
  • VLV feature ACIs and multi-server configuration
  • Removing obsolete definitions and troubleshooting mismatches

Before you start:

Tested on: Rocky Linux 10.2; 389 Directory Server 3.2.0.

The lab instance is ldap1 on ldap1.example.com with LDAP port 389. The primary backend is userroot for suffix dc=example,dc=com.


How Virtual List View searches work

An address-book or admin console typically sends one LDAP search with two related controls:

text
LDAP client request
        |
        +-- Search base, scope, and filter
        +-- Server-side sort control (attribute order)
        +-- VLV control (result window)
        |
        v
Directory Server locates a matching vlvSearch and vlvIndex definition
        |
        v
Returns only the requested entries around the target position

The VLV control is available on every 389 Directory Server instance, even before you create a browsing index. On a large directory, the server can still sort and window results without an index, but that work is expensive. A VLV browsing index precomputes a fixed search-and-sort combination so repeated window requests stay fast.

VLV window example

Suppose the full sorted result contains 500 entries and the client asks for one entry before the target, two after, and offset 70:

text
Full sorted result: 500 entries

Requested target: entry 70
Before count: 1
After count: 2

Returned window:
  entry 69
  entry 70
  entry 71
  entry 72

The client receives four entries instead of all 500.

Server-side sort and VLV controls

Control OID (advertised on Root DSE) Purpose
Server-side sorting 1.2.840.113556.1.4.473 Orders the complete matching result on the server
Virtual List View 2.16.840.1.113730.3.4.9 Returns only the requested slice of that sorted result

Production clients normally attach both controls to the same search. Sorting without VLV still processes the full result set on the server. VLV without a valid sort control fails.

A VLV configuration contains at least two configuration entries under the backend:

text
vlvSearch entry
  base, scope, filter
  |
  +-- vlvIndex child (sort: cn sn)
  +-- vlvIndex child (sort: sn cn)   [optional second sort order]

Offline instance work in this section uses dsctl commands.

New entries in this section are added with the ldapadd command.

Compare VLV with standard indexes and paged results

Feature Standard index Simple paged results VLV index
Indexes attribute values Yes No Indexes a defined search and sort
Requires server-side sorting No No Yes
Retrieves sequential pages Not applicable Yes Can retrieve a window around a position
Supports jumping to an offset No No Yes
Uses a client cookie No Yes Uses VLV target and result context
Best use Filter acceleration Sequential result traversal Scrollable sorted lists

Creating a VLV browsing index does not turn an ordinary LDAP search into a VLV search. The application must send the VLV and server-side sort controls. Standard indexes remain necessary for everyday filter matching on attributes such as uid, mail, and cn.


Decide whether a VLV index is required

Use VLV when the application already submits VLV and server-side sort controls for repeatable browsing patterns:

  • Address books with thousands of entries
  • Administrative consoles that display scrollable user lists
  • Certificate repositories and large employee directories
  • Clients that jump directly to an approximate position in a sorted list

Skip VLV indexes when:

  • Result sets are small
  • The search is a one-time administrative query
  • Filters change frequently
  • The client uses only simple paged results
  • Clients never request the sort orders you would index

Before creating any definition, capture the exact production request:

text
Search base
Search scope
Search filter
Sort attribute list
Ascending or descending order
Matching rules (when used)

This lab uses one fixed definition throughout:

text
Base:   ou=people,dc=example,dc=com
Scope:  one level
Filter: (&(objectClass=person)(mail=*))
Sort:   cn sn

Prepare the VLV test dataset

I use a scrollable employee list under ou=people,dc=example,dc=com with enough entries that returning four names instead of hundreds is obvious.

text
dc=example,dc=com
└── ou=people
    ├── uid=user001
    ├── uid=user002
    ├── ...
    └── uid=user500

Each entry carries objectClass: person, uid, cn, sn, and mail.

To generate a similar tree quickly, follow Generate test data with dsctl ldifgen. This lab loaded 500 users with dsctl ldifgen users and ldapadd.

Confirm the filter matches the entries you expect before touching VLV configuration:

Directory queries in this section use the ldapsearch command.

bash
ldapsearch -LLL -x -H ldap://127.0.0.1:389 -y /root/dm.pw -D "cn=Directory Manager" -b "ou=people,dc=example,dc=com" -s one "(&(objectClass=person)(mail=*))" dn | grep -c '^dn:'

Sample output:

output
500

Five hundred matches is enough to contrast a four-entry VLV window with a full sorted download.


Check VLV support and ldapsearch syntax

Confirm controls on the Root DSE

Query the Root DSE for advertised controls before you rely on VLV in production clients:

bash
ldapsearch -LLL -x -H ldap://127.0.0.1:389 -b "" -s base supportedControl

Sample output (trimmed):

output
supportedControl: 1.2.840.113556.1.4.473
supportedControl: 2.16.840.1.113730.3.4.9
...

1.2.840.113556.1.4.473 is server-side sorting. 2.16.840.1.113730.3.4.9 is Virtual List View.

Test VLV before creating a browsing index

You can exercise the controls without a VLV index. The server sorts and windows the result; it simply does not use a prebuilt browsing index yet.

bash
timeout 5 ldapsearch -LLL -x -H ldap://127.0.0.1:389 -y /root/dm.pw -D "cn=Directory Manager" -b "ou=people,dc=example,dc=com" -s one -E 'sss=cn/sn' -E 'vlv=1/2/70/0' "(&(objectClass=person)(mail=*))" uid cn sn 2>&1 | head -25

Sample output:

output
dn: uid=user069,ou=people,dc=example,dc=com
uid: user069
cn: user069
sn: Collier

dn: uid=user070,ou=people,dc=example,dc=com
uid: user070
cn: user070
sn: HDI

dn: uid=user071,ou=people,dc=example,dc=com
uid: user071
cn: user071
sn: Frankenberger

dn: uid=user072,ou=people,dc=example,dc=com
uid: user072
cn: user072
sn: Travis

# sortResult: (0) Success
# vlvResult: pos=70 count=500 context= (0) Success
Press [before/after(/offset/count|:value)] Enter for the next window.

Offset 70 is the target position in the sorted list, so the window contains entries 69 through 72 (user069 through user072).

The VLV expression 1/2/70/0 means one entry before the target, two after, target offset 70, and count 0 because the client does not supply an estimated result size.

General index design on OpenLDAP is covered in OpenLDAP indexing and performance.

OpenLDAP ldapsearch enters an interactive paging loop after the first window. timeout and head keep scripted tests from hanging. Production LDAP clients handle VLV without that prompt.

ldapsearch extension syntax

Pair server-side sort and VLV on the same command:

text
-E 'sss=SORT_ATTRIBUTES'
-E 'vlv=BEFORE/AFTER/OFFSET/COUNT'
Value Meaning
before Entries before the target
after Entries after the target
offset Requested position, starting from 1
count Client estimate of the complete result size (0 when unknown)

For a multi-attribute ascending sort, separate attributes with /:

bash
-E 'sss=cn/sn' -E 'vlv=1/2/70/0'

Value-based targeting requests a window around the first result whose primary sort attribute starts with the given prefix:

bash
-E 'sss=sn/cn' -E 'vlv=0/9:Smith'

Descending order depends on client support. When available, prefix the attribute with -:

bash
-E 'sss=-sn/cn'

Run ldapsearch --help or read the local man page to confirm the exact -E syntax for your installed OpenLDAP client.


Check existing VLV configurations

List VLV searches and sort indexes for the backend before adding another overlapping definition:

bash
dsconf ldap1 backend vlv-index list userroot

On a clean lab backend with no prior VLV definitions, the command exits with no output.

You can also read the configuration tree directly when definitions already exist:

bash
ldapsearch -LLL -x -H ldap://127.0.0.1:389 -y /root/dm.pw -D "cn=Directory Manager" -b "cn=userroot,cn=ldbm database,cn=plugins,cn=config" "(|(objectClass=vlvSearch)(objectClass=vlvIndex))" dn cn vlvBase vlvScope vlvFilter vlvSort vlvEnabled vlvUses

No VLV search definitions are configured for backend userroot until you create them in the next section. Record any existing definitions on a production server so you do not create duplicate searches with slightly different filters.


Create and build the VLV index

VLV configuration is a two-step process: define the search pattern, then define one or more sort indexes beneath it. If VLV People already exists on your server with the same base, scope, and filter, skip add-search and reuse the existing parent entry, or delete it with dsconf ldap1 backend vlv-index del-search before following this clean-lab workflow.

Create the VLV search entry

The search entry (vlvSearch) stores the base, scope, and filter that client requests must match:

bash
dsconf ldap1 backend vlv-index add-search --name "VLV People" --search-base "ou=people,dc=example,dc=com" --search-filter "(&(objectClass=person)(mail=*))" --search-scope 1 userroot
Option Purpose
--name Identifies the VLV search entry
--search-base Base DN used by matching client searches
--search-filter Fixed filter included in the browsing index
--search-scope 0 base, 1 one-level, 2 subtree
userroot Backend that contains the indexed entries

Verify the parent entry exists:

bash
dsconf ldap1 backend vlv-index list userroot

Sample output:

output
dn: cn=VLV People,cn=userroot,cn=ldbm database,cn=plugins,cn=config
cn: VLV People
vlvbase: ou=people,dc=example,dc=com
vlvscope: 1
vlvfilter: (&(objectClass=person)(mail=*))
Sorts:

The list shows the vlvSearch entry with its filter and scope. Sort children appear after add-index.

Create and build the sort index

Add a vlvIndex child that defines the sort order:

bash
dsconf ldap1 backend vlv-index add-index --index-name "VLV People - cn sn" --parent-name "VLV People" --sort "cn sn" userroot
Option Purpose
--index-name Identifies this sort index
--parent-name Links to the VLV search entry
--sort Ordered list of sort attributes

On 389 Directory Server 3.2.0 in this lab, add-index --index-it printed Error: Failed to find backend matching the suffix while still creating the entry with vlvEnabled: 0. Building through reindex (next step) is the reliable path here.

Start the build explicitly:

bash
dsconf ldap1 backend vlv-index reindex --parent-name "VLV People" userroot

Sample output:

output
Index task index_vlv_2026-07-17T23:34:12.748668 completed successfully
Successfully reindexed VLV indexes

Monitor /var/log/dirsrv/slapd-ldap1/errors for VLV indexing progress. Do not treat the browsing index as ready until the task completes and vlvEnabled is 1.

bash
dsconf ldap1 backend vlv-index list userroot

Sample output:

output
dn: cn=VLV People,cn=userroot,cn=ldbm database,cn=plugins,cn=config
cn: VLV People
vlvbase: ou=people,dc=example,dc=com
vlvscope: 1
vlvfilter: (&(objectClass=person)(mail=*))
Sorts:
 - dn: cn=VLV People - cn sn,cn=VLV People,cn=userroot,cn=ldbm database,cn=plugins,cn=config
 - cn: VLV People - cn sn
 - vlvsort: cn sn
 - vlvenabled: 1
 - vlvuses: 0

After a successful build, the sort child shows vlvenabled: 1. Note vlvbase, vlvscope, vlvfilter, vlvsort, vlvenabled, and vlvuses for every sort child.

One vlvSearch entry can have several vlvIndex children when clients request different sort orders:

text
VLV People
├── Sort by cn sn
├── Sort by sn cn
└── Sort by uid

Add another sort only when a real client uses it:

bash
dsconf ldap1 backend vlv-index add-index --index-name "VLV People - sn cn" --parent-name "VLV People" --sort "sn cn" userroot

Each additional sort index consumes database space and adds maintenance work on add, modify, delete, and rename operations. Run reindex --parent-name after adding sort children without a successful immediate build.

Rebuild after configuration changes

Rebuild every sort index under one search after filter, scope, base, or sort changes:

bash
dsconf ldap1 backend vlv-index reindex --parent-name "VLV People" userroot

Also rebuild after bulk import or restore when index consistency is uncertain, or when vlvEnabled stays 0 after add-index.


Verify that the VLV index is used

Run a search that exactly matches the configured base, scope, filter, and sort:

bash
timeout 5 ldapsearch -LLL -x -H ldap://127.0.0.1:389 -y /root/dm.pw -D "cn=Directory Manager" -b "ou=people,dc=example,dc=com" -s one -E 'sss=cn/sn' -E 'vlv=1/2/70/0' "(&(objectClass=person)(mail=*))" uid cn sn 2>&1 | head -25

Look for successful control results in the output:

text
# sortResult: (0) Success
# vlvResult: pos=70 count=500 context= (0) Success

Check the access log on the server for SORT and VLV lines on the same connection operation:

text
conn=30 op=1 SORT cn sn (500)
conn=30 op=1 VLV 1:2:69:0 70:500 (0)

The request tuple reflects vlv=1/2/70/0 from the client. The result side reports position 70 and content count 500, matching # vlvResult: pos=70 count=500.

List the configuration again and confirm vlvuses increased on the matching sort index:

bash
dsconf ldap1 backend vlv-index list userroot

A successful indexed search should not add unexpected notes=U to the RESULT line for reasons unrelated to ID-list scan limits. If vlvuses stays flat while the query succeeds, treat exact matching as the first suspect (next section).

Compare behavior with and without the browsing index

Record the same VLV request before and after the index is built and enabled:

Metric Without VLV index With VLV index
Entries in full result 500 500
Entries returned to client 4 4
Access-log etime higher on large trees lower when index selected
vlvUses unchanged increases
Unexpected notes=U possible on broad sorts should not appear when matched

Compare like with like: same bind identity, base, scope, filter, sort, and VLV window. Do not compare a VLV sorted search against an ordinary unsorted search and call the difference a VLV win.


Exact VLV search matching requirements

For Directory Server to select a browsing index, the client request must match the configured definition on every axis that the index encodes.

text
Search base
Search scope
Search filter
Sort attribute order
Sort direction
Matching rule (when used)

Configured example in this lab:

text
Base:   ou=people,dc=example,dc=com
Scope:  one
Filter: (&(objectClass=person)(mail=*))
Sort:   cn sn

These client changes look small but select a different code path:

text
Scope changed from one to subtree

Filter changed to:
  (objectClass=person)

Sort changed to:
  sn cn

Search base changed to:
  dc=example,dc=com

A subtree scope with an otherwise identical filter does not use a one-level VLV index. sss=sn/cn does not match a vlvSort: cn sn index. Capture the application's real SRCH and SORT access-log lines and mirror them in dsconf, not the other way around.

When troubleshooting, line up access-log fields with configuration:

Access log VLV configuration
SRCH base="..." vlvBase
SRCH scope=... vlvScope (0 base, 1 one, 2 sub)
SRCH filter="..." vlvFilter
SORT attr ... vlvSort

Control access to the VLV feature

By default, the VLV feature ACI permits authenticated users to request the VLV control. Inspect the feature entry:

bash
ldapsearch -LLL -Y EXTERNAL -H ldapi://%2Fvar%2Frun%2Fslapd-ldap1.socket -b "oid=2.16.840.1.113730.3.4.9,cn=features,cn=config" -s base aci

Sample output (trimmed):

output
dn: oid=2.16.840.1.113730.3.4.9,cn=features,cn=config
aci: (targetattr != "aci")(version 3.0; acl "VLV Request Control"; allow( read
 ,search,compare,proxy) userdn="ldap:///all";)

Do not enable anonymous VLV unless the application genuinely requires it. Prefer a dedicated application bind account with narrowly scoped ACIs.

Where anonymous VLV is unavoidable, update the feature ACI carefully and confirm anonymous binds, entry-level read access, and search limits still protect the server:

ldif
dn: oid=2.16.840.1.113730.3.4.9,cn=features,cn=config
changetype: modify
replace: aci
aci: (targetattr != "aci")(version 3.0; acl "VLV Request Control";
 allow(read,search,compare,proxy)
 userdn="ldap:///anyone";)

Configure VLV on multiple directory servers

VLV search and sort entries live under the local backend configuration in cn=config. Replicated directory data does not copy them.

On every server that answers VLV client requests:

  1. Create an equivalent vlvSearch entry.
  2. Create the required vlvIndex sort children.
  3. Build or rebuild the indexes locally.
  4. Test the exact client request.
  5. Compare vlvEnabled and vlvUses on each instance.

Supplier and consumer replication agreements are outside this article. The operational rule is simple: configure and build VLV separately wherever the search is served.


Modify, remove, and troubleshoot VLV indexes

Before changing or deleting a definition:

  1. Record the search and sort entries.
  2. Search access logs for current VLV use.
  3. Identify applications using each sort order.
  4. Create and test a replacement index when needed.
  5. Remove obsolete sort indexes before deleting the parent search entry.

Use dsconf ldap1 backend vlv-index --help to confirm del-search, del-index, and edit-search on your installed release. Unused sort indexes continue to consume storage and write-processing resources.

Symptom Likely cause Fix
VLV query succeeds but stays slow Index not built (vlvEnabled: 0), or request does not match Run reindex; compare base, scope, filter, and sort with access log
vlvUses does not increase Client request mismatch Align SRCH/SORT log lines with vlvBase/vlvScope/vlvFilter/vlvSort
unwilling to perform Missing sort control, feature ACI denial, or unsupported sort Send sss with vlv; check feature ACI and sort index exists
Sort succeeds, VLV fails Malformed VLV window Verify before/after/offset/count or value syntax
Works for Directory Manager, not the app Feature or entry-level ACI, limits, or proxy auth Check VLV feature ACI, read ACIs, size/time limits, bind identity
No entries returned Base, scope, or filter matches nothing Run the same search without VLV controls
Results stale after filter change Index not rebuilt reindex --parent-name after editing the search definition
Index damaged or inconsistent Interrupted build or restore Rebuild; if needed, export config, remove, recreate, and reindex

What's next

After you complete this guide, continue with:

Summary

  1. Confirm the client sends server-side sort and VLV controls.
  2. Record the exact search base, scope, filter, and sort from production traffic.
  3. Create the vlvSearch entry with dsconf backend vlv-index add-search.
  4. Create one or more vlvIndex sort children with add-index.
  5. Build the browsing index with reindex and wait for vlvEnabled: 1.
  6. Test with a matching ldapsearch -E 'sss=...' -E 'vlv=...' request.
  7. Confirm vlvUses increases and access logs show SORT and VLV on the same operation.
  8. Protect the VLV feature with appropriate ACIs.
  9. Configure and build indexes separately on every server that serves VLV queries.

References


Frequently Asked Questions

1. Does creating a VLV index automatically speed up every LDAP search?

No. The client must send both server-side sort and VLV controls on a search that exactly matches the configured base, scope, filter, and sort order. Ordinary searches and simple paged results do not use a VLV browsing index.

2. What is the difference between a VLV control and a VLV index?

The VLV control lets a client request a window from a sorted result set. The VLV browsing index precomputes a fixed search and sort combination so those windowed requests are fast on large directories. The control works without an index, but sorting large result sets without a matching index is expensive.

3. Why does vlvUses stay at zero after I created the index?

The client request probably does not exactly match vlvBase, vlvScope, vlvFilter, and vlvSort. Compare the application SRCH and SORT lines in the access log with dsconf backend vlv-index list output. A different scope, filter, or sort attribute order prevents index selection.

4. Can anonymous users use VLV in 389 Directory Server?

By default the VLV feature ACI allows authenticated users only. Anonymous VLV requires updating the feature ACI at oid=2.16.840.1.113730.3.4.9,cn=features,cn=config, enabling anonymous binds, and granting read access to the searched entries. Prefer a dedicated application bind account instead.

5. Do VLV definitions replicate between directory servers?

No. VLV search and sort entries live under cn=config on each server. Replicated user data does not copy local VLV configuration or built browsing indexes. Create, build, and test the same VLV definitions on every instance that answers VLV client requests.
Deepak Prasad

R&D Engineer

Founder of GoLinuxCloud with more than 15 years of expertise in Linux, Python, Go, Laravel, DevOps, Kubernetes, Git, Shell scripting, OpenShift, AWS, Networking, and Security. With extensive …