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
vlvSearchandvlvIndexentries withdsconf - 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:
- Install 389 Directory Server — running instance with a suffix (this lab uses
ldap1ondc=example,dc=com) - Configure 389 Directory Server Indexes for Faster LDAP Searches — equality, presence, and substring indexes for filters
- dsconf commands — online backend configuration
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:
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 positionThe 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:
Full sorted result: 500 entries
Requested target: entry 70
Before count: 1
After count: 2
Returned window:
entry 69
entry 70
entry 71
entry 72The 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:
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:
Search base
Search scope
Search filter
Sort attribute list
Ascending or descending order
Matching rules (when used)This lab uses one fixed definition throughout:
Base: ou=people,dc=example,dc=com
Scope: one level
Filter: (&(objectClass=person)(mail=*))
Sort: cn snPrepare 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.
dc=example,dc=com
└── ou=people
├── uid=user001
├── uid=user002
├── ...
└── uid=user500Each 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.
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:
500Five 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:
ldapsearch -LLL -x -H ldap://127.0.0.1:389 -b "" -s base supportedControlSample output (trimmed):
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.
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 -25Sample 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:
-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 /:
-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:
-E 'sss=sn/cn' -E 'vlv=0/9:Smith'Descending order depends on client support. When available, prefix the attribute with -:
-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:
dsconf ldap1 backend vlv-index list userrootOn 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:
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 vlvUsesNo 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:
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:
dsconf ldap1 backend vlv-index list userrootSample 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:
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:
dsconf ldap1 backend vlv-index reindex --parent-name "VLV People" userrootSample output:
Index task index_vlv_2026-07-17T23:34:12.748668 completed successfully
Successfully reindexed VLV indexesMonitor /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.
dsconf ldap1 backend vlv-index list userrootSample 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: 0After a successful build, the sort child shows vlvenabled: 1. Note vlvbase, vlvscope, vlvfilter, vlvsort, vlvenabled, and vlvuses for every sort child.
Add multiple sort indexes for one search
One vlvSearch entry can have several vlvIndex children when clients request different sort orders:
VLV People
├── Sort by cn sn
├── Sort by sn cn
└── Sort by uidAdd another sort only when a real client uses it:
dsconf ldap1 backend vlv-index add-index --index-name "VLV People - sn cn" --parent-name "VLV People" --sort "sn cn" userrootEach 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:
dsconf ldap1 backend vlv-index reindex --parent-name "VLV People" userrootAlso 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:
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 -25Look for successful control results in the output:
# sortResult: (0) Success
# vlvResult: pos=70 count=500 context= (0) SuccessCheck the access log on the server for SORT and VLV lines on the same connection operation:
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:
dsconf ldap1 backend vlv-index list userrootA 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.
Search base
Search scope
Search filter
Sort attribute order
Sort direction
Matching rule (when used)Configured example in this lab:
Base: ou=people,dc=example,dc=com
Scope: one
Filter: (&(objectClass=person)(mail=*))
Sort: cn snThese client changes look small but select a different code path:
Scope changed from one to subtree
Filter changed to:
(objectClass=person)
Sort changed to:
sn cn
Search base changed to:
dc=example,dc=comA 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:
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 aciSample output (trimmed):
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:
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:
- Create an equivalent
vlvSearchentry. - Create the required
vlvIndexsort children. - Build or rebuild the indexes locally.
- Test the exact client request.
- Compare
vlvEnabledandvlvUseson 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:
- Record the search and sort entries.
- Search access logs for current VLV use.
- Identify applications using each sort order.
- Create and test a replacement index when needed.
- 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:
- Configure LDAP Search Limits in 389 Directory Server — lookthrough and size limits with paged results
- Tune LMDB, Entry Cache and DN Cache in 389 Directory Server — backend tuning for large result sets
- Benchmark 389 Directory Server Performance with ldclt and logconv — measure browse and paged-search latency
- Generate LDAP Test Data with dsctl ldifgen — generate larger suffixes for VLV testing
Summary
- Confirm the client sends server-side sort and VLV controls.
- Record the exact search base, scope, filter, and sort from production traffic.
- Create the
vlvSearchentry withdsconf backend vlv-index add-search. - Create one or more
vlvIndexsort children withadd-index. - Build the browsing index with
reindexand wait forvlvEnabled: 1. - Test with a matching
ldapsearch -E 'sss=...' -E 'vlv=...'request. - Confirm
vlvUsesincreases and access logs showSORTandVLVon the same operation. - Protect the VLV feature with appropriate ACIs.
- Configure and build indexes separately on every server that serves VLV queries.
References
- 389 Directory Server project
- Red Hat Directory Server 13 — Using virtual list view control (§6.5)
- RFC 2891 — LDAP Control Extension for Server Side Sorting
- man ldapsearch — OpenLDAP client
-Eextensions

