Trees in Django: From Old Solutions to the New Wave

Why Trees Are Important? The world is structured hierarchically. Online store catalogs, organizational charts, access control systems, website menus, and comment threads—all involve parent-child relationships. In computer science, these structures are called trees: connected graphs without cycles. They appear in almost every application area, from administrative structures to biological taxonomies. Even a computer's file system is a tree. Not all trees are compact. In business, science, and industry, structures can include tens of thousands of nodes and hundreds of levels deep: E-commerce catalogs can easily grow to 70,000 items. In mechanical engineering, the specification of complex products (BOM) is a tree of nodes and parts. Machine learning and decision-support systems often use huge state trees. In logging or task planning, event trees can reach depths of over 1,000 levels. These structures aren't just hard to visualize—they require effective management: selecting subtrees, moving branches, and analyzing dependencies. For example, the Django community once discussed a project with a tree of about 140,000 nodes, where roughly 500 nodes were added daily, and queries selecting thousands of nodes were executed every few seconds. Limitations of Python Libraries with Databases Pure Python offers many libraries for working with trees and graphs: NetworkX, graph-tool, python-igraph—powerful tools for graph analysis. AnyTree, Treelib—simple solutions for in-memory trees. These tools work well as long as the tree fits into memory. However, when working with databases, especially in web applications, these tools become ineffective. They can't handle complex queries, filtering, and scalable access. What Django Provides Out-of-the-Box and Why It Isn't Enough When a tree becomes part of your data model, you need to store it in a database. Relational databases can't store hierarchies "natively." The most popular approach is the Adjacency List (parent field), but it doesn't scale well for deep descendant queries. Alternatives include Materialized Path, Nested Set, and Closure Table—each has pros and cons. Out-of-the-box, Django's ORM offers only the Adjacency List. Other solutions require additional packages: django-mptt—modified Nested Set, no longer actively updated. django-treebeard—supports three storage schemes, actively maintained. django-tree-queries—modern Adjacency + CTE, but unstable for large datasets. django-closuretree—fast selections using closure tables but expensive insertions. Evolution of Solutions: From Adjacency List to Treenode Framework Hybrids In recent years, hybrid solutions have emerged. The django-treenode package uses a simple approach (Adjacency + cache) but involves extensive pre-calculations during node insertion. Convenient but not scalable. On the opposite side, there's django-fast-treenode. Initially a hybrid of Adjacency List and Closure Table, it offered fast queries even in deep trees. However, version 2.x included a hidden closure model, complicating migrations. Treenode Framework: Next-Generation Platform In Treenode Framework (django-fast-treenode version 3.x), the architecture underwent significant changes. Now everything exists within one model using Adjacency List and Materialized Path. This removed the "magic" of the closure model, sped up insertions, and simplified migrations. Further performance optimization included: SQL queue defers and groups costly updates. Task queue manages recalculations without blocking and includes its own task optimizer. Built-in fast cache drastically reduces database calls. Additional features include a drag-and-drop and tree-widget in admin interface, API generator for API-first projects, and the richest set of tree management methods among Django packages. Treenode Framework Key Features: Retrieval of ancestors, descendants, children, and entire families. Counting, existence checks, retrieving lists of PKs or objects. Adding, deleting, pruning, grafting, and moving nodes. Finding common ancestors and shortest paths between nodes. Automatic sorting and full QuerySet API support. Works without additional dependencies. Install it as a regular package: pip install django-fast-treenode Comparative Testing of Solutions A series of stress tests modeled trees from 10 to 1000+ levels deep with varying widths and branching factors. Six models were compared: MP_Node, NS_Node, AL_Node, MPTT, FastTreeNode (django-fast-treenode v2.x), and Treenode Framework (django-fast-treenode v3.x). Impact of Tree Width and Depth: "Wide" trees (many nodes on a single level) slow down all models. Increasing depth to 20 or more severely slows down AL_Node and MP_Node; only FastTreeNode, NS_Node, and Treenode Framework remain stable. At depths of 250+ levels, AL and MP_Node fail completely, NS_Node slows down significantly, while FastTreeNode and Treenode

May 1, 2025 - 14:49
 0
Trees in Django: From Old Solutions to the New Wave

Why Trees Are Important?

The world is structured hierarchically. Online store catalogs, organizational charts, access control systems, website menus, and comment threads—all involve parent-child relationships. In computer science, these structures are called trees: connected graphs without cycles. They appear in almost every application area, from administrative structures to biological taxonomies. Even a computer's file system is a tree.
Not all trees are compact. In business, science, and industry, structures can include tens of thousands of nodes and hundreds of levels deep:

  • E-commerce catalogs can easily grow to 70,000 items.
  • In mechanical engineering, the specification of complex products (BOM) is a tree of nodes and parts.
  • Machine learning and decision-support systems often use huge state trees.
  • In logging or task planning, event trees can reach depths of over 1,000 levels.

These structures aren't just hard to visualize—they require effective management: selecting subtrees, moving branches, and analyzing dependencies.

For example, the Django community once discussed a project with a tree of about 140,000 nodes, where roughly 500 nodes were added daily, and queries selecting thousands of nodes were executed every few seconds.

Limitations of Python Libraries with Databases

Pure Python offers many libraries for working with trees and graphs:

  • NetworkX, graph-tool, python-igraph—powerful tools for graph analysis.
  • AnyTree, Treelib—simple solutions for in-memory trees.

These tools work well as long as the tree fits into memory. However, when working with databases, especially in web applications, these tools become ineffective. They can't handle complex queries, filtering, and scalable access.

What Django Provides Out-of-the-Box and Why It Isn't Enough

When a tree becomes part of your data model, you need to store it in a database. Relational databases can't store hierarchies "natively." The most popular approach is the Adjacency List (parent field), but it doesn't scale well for deep descendant queries. Alternatives include Materialized Path, Nested Set, and Closure Table—each has pros and cons.

Out-of-the-box, Django's ORM offers only the Adjacency List. Other solutions require additional packages:

  • django-mptt—modified Nested Set, no longer actively updated.
  • django-treebeard—supports three storage schemes, actively maintained.
  • django-tree-queries—modern Adjacency + CTE, but unstable for large datasets.
  • django-closuretree—fast selections using closure tables but expensive insertions.

Evolution of Solutions: From Adjacency List to Treenode Framework

Hybrids

In recent years, hybrid solutions have emerged. The django-treenode package uses a simple approach (Adjacency + cache) but involves extensive pre-calculations during node insertion. Convenient but not scalable.
On the opposite side, there's django-fast-treenode. Initially a hybrid of Adjacency List and Closure Table, it offered fast queries even in deep trees. However, version 2.x included a hidden closure model, complicating migrations.

Treenode Framework: Next-Generation Platform

In Treenode Framework (django-fast-treenode version 3.x), the architecture underwent significant changes. Now everything exists within one model using Adjacency List and Materialized Path. This removed the "magic" of the closure model, sped up insertions, and simplified migrations.

Further performance optimization included:

  • SQL queue defers and groups costly updates.
  • Task queue manages recalculations without blocking and includes its own task optimizer.
  • Built-in fast cache drastically reduces database calls.

Additional features include a drag-and-drop and tree-widget in admin interface, API generator for API-first projects, and the richest set of tree management methods among Django packages.

Treenode Framework Key Features:

  • Retrieval of ancestors, descendants, children, and entire families.
  • Counting, existence checks, retrieving lists of PKs or objects.
  • Adding, deleting, pruning, grafting, and moving nodes.
  • Finding common ancestors and shortest paths between nodes.
  • Automatic sorting and full QuerySet API support.

Works without additional dependencies. Install it as a regular package: pip install django-fast-treenode

Comparative Testing of Solutions

A series of stress tests modeled trees from 10 to 1000+ levels deep with varying widths and branching factors. Six models were compared: MP_Node, NS_Node, AL_Node, MPTT, FastTreeNode (django-fast-treenode v2.x), and Treenode Framework (django-fast-treenode v3.x).

Impact of Tree Width and Depth:

  • "Wide" trees (many nodes on a single level) slow down all models.
  • Increasing depth to 20 or more severely slows down AL_Node and MP_Node; only FastTreeNode, NS_Node, and Treenode Framework remain stable.
  • At depths of 250+ levels, AL and MP_Node fail completely, NS_Node slows down significantly, while FastTreeNode and Treenode Framework stay reliably fast up to ~1000 levels.

Conclusions and Recommendations:

  • ALNodeModel is good only for flat, shallow trees (less 1,000 nodes).
  • NS_Node is the only choice for depths beyond 1500 if delays are tolerable.
  • FastTreeNode is stable but needs cache optimization.
  • MPTT is historically significant but can't handle heavy workloads.
  • Treenode Framework is the absolute champion in combined performance and stability.

If your project relies on hierarchies, the best solution already exists.

Want to try Treenode Framework? Visit my GitHub repository and join the package development!

Your support, questions and pull requests are always welcome!