Why does removing an obsolete branch from a directory take more than one LDAP Delete request?
answer
- one name per request, nothing more
- children first, parents last
- the server guards non-leaf entries
- no transaction spans the walk
- a re-run must tolerate a missing entry
basics
~20 sA Delete request is nothing but one Distinguished Name, and a server refuses to delete an entry that still has subordinates with notAllowedOnNonLeaf (66). Removing a branch is therefore a client-side depth-first walk, one request per entry.
solid answer
~40 s`DelRequest` carries a single Distinguished Name — no filter, no scope, no recursion. A server refuses an entry that still has subordinate entries with `notAllowedOnNonLeaf (66)`, so the client must walk the branch and delete children before their parents. Each request is an independent operation with its own verdict, and nothing ties them into a transaction, so a run that stops half way leaves the branch partly gone. That makes two things mandatory: the walk must tolerate `noSuchObject (32)` on a re-run as already-done, and it must expect `notAllowedOnNonLeaf (66)` on a parent whose child was created while the walk was in progress. `insufficientAccessRights (50)` on one entry stops that sub-branch, not the job.
code
pseudocode · 13 linesfunction remove_branch(dn)
for each child_dn one level below dn
remove_branch(child_dn)
result = send DelRequest(dn)
if result = noSuchObject (32)
return done -- an earlier run already removed it
if result = notAllowedOnNonLeaf (66)
return remove_branch(dn) -- a new entry appeared during the walk
if result = insufficientAccessRights (50)
report and stop this branch -- ancestors cannot be removed either
return donego deeper
Recall that a Delete request names exactly one entry and that a server will not delete an entry that still has children below it.
Explain why the ordering is forced by the leaf-only rule, and what the refusal code for a non-leaf entry is telling the client to do next.
Show that you build the walk for resumption: idempotent on an already-deleted entry, re-enumerating when a new child appears, and repairing stored Distinguished Names before the entries vanish.
Decide how much consistency a deprovisioning pipeline should promise when the protocol gives no transaction across requests, and where that reconciliation belongs in the estate.
## The smallest request in the protocol A Delete request is a single Distinguished Name and nothing else. There is no filter to match on, no search scope to widen, and no recursion flag. The operation deletes **one** entry, and it succeeds only if that entry is a leaf: a server refuses an entry that still has subordinates with `notAllowedOnNonLeaf (66)`. That single constraint turns "remove the retired practice's branch" from one request into a client-side algorithm. ## The walk, and why its order is fixed The client enumerates the branch and deletes **depth-first**: every descendant before its parent, the branch's root last. Any other order simply collects `notAllowedOnNonLeaf (66)` refusals until it happens to hit a leaf. The important property of that walk is what it is **not**: it is not a transaction. Each Delete is an independent operation with its own `resultCode`, and the base protocol offers nothing that joins a sequence of them. A run that stops half way — a network drop, a rate limit, an operator's interrupt — leaves a branch partly deleted, and the directory has no memory of what the job intended. ## Writing the walk so a re-run is safe Because a half-finished run is the normal case rather than the exception, the interesting engineering is in the result codes: - **`noSuchObject (32)`** — the entry is already gone. On a re-run this is the expected outcome for everything the previous attempt finished, and it must be treated as **done**, not as an error. A job that aborts on it can never complete after its first failure. - **`notAllowedOnNonLeaf (66)`** — a subordinate exists. On a first pass that means the walk missed a level; on a re-run of a live directory it usually means an entry was **created under the branch while the walk was running**. The fix is to re-enumerate that node rather than to retry the same request. - **`insufficientAccessRights (50)`** — the connection's authenticated identity may not delete this entry. That stops the sub-branch containing it and, by extension, every ancestor up to the root of the walk, but it does not invalidate the entries already removed. - **`unwillingToPerform (53)`** — the server declines on policy grounds. The request was well formed and no amount of retrying changes the answer. ## What deletion does not clean up Deleting an entry removes that entry. It does not touch anything that **names** it. Group entries hold Distinguished Names in `member` and `uniqueMember` values, and the base protocol defines no referential integrity, so those values survive as references to entries that no longer resolve. A deprovisioning job that does not also send a Modify with a `delete (1)` change against each group entry leaves the directory quietly inconsistent, and the next audit of group membership reads as though the people are still there. ## Ordering the whole job For the retirement of a practice's branch, the order that actually works is: 1. Enumerate the branch and record every Distinguished Name, deepest first. 2. Repair references: for each group entry naming one of those DNs, send a Modify with a `delete (1)` change removing that value. 3. Delete depth-first, treating `noSuchObject (32)` as already-done and re-enumerating on `notAllowedOnNonLeaf (66)`. 4. Delete the branch's root entry last, and confirm the result rather than assuming it. Step 2 before step 3 matters: once the entries are gone, finding which groups referenced them is much harder, because the only remaining evidence is the stale values themselves. ## The interview point The answer an interviewer is listening for is not "there is a recursive delete". It is that the protocol's write surface is deliberately per-entry, that the server's leaf-only rule is what forces the ordering, and that the absence of a transaction across requests is what makes idempotence — not cleverness — the property a bulk deprovisioning job has to be built around.
- Why can the same run be refused twice on the same parent entry?Because another client created an entry under that branch between the enumeration and the parent's Delete, so the parent is no longer a leaf. Retrying the identical request gets `notAllowedOnNonLeaf (66)` again; the walk has to re-enumerate below that node and delete the newcomer first.
- What happens to group memberships naming the deleted entries?Nothing — they stay. `member` and `uniqueMember` values are Distinguished Names stored on the group entry, and the base protocol defines no referential integrity, so the values remain as references that no longer resolve. Each one is removed by a Modify with a `delete (1)` change on the group entry.
saying these in an interview costs you the question
- Expects a Delete to take a search scope and remove the branch
- Thinks notAllowedOnNonLeaf(66) means access control refused it
- Treats noSuchObject(32) on a re-run as a failure to investigate
- Believes the server removes subordinates along with the parent
- Says deleting a person's entry removes them from every group
- Assumes a sequence of Delete requests is applied as a transaction