Django integration for HeeRanjID distributed ID generation
Project description
heeranjid-django
HeerRanjId ([ɦiːɾ.ɾaːnd͡ʒ.ɪd])
Django integration for HeerRanjId — a Snowflake-inspired distributed ID system that gives you compact, time-ordered primary keys without the drawbacks of auto-increment or plain UUIDs.
Snowflake IDs (originally from Twitter) are a well-established approach to distributed ID generation. HeeRanjID is a plug-and-play implementation of that concept for Django, with first-class PostgreSQL and SQL Server support and a UUID-compatible external representation built in.
Why not just use UUIDs?
UUIDs are portable and globally unique, but they are a poor choice for primary keys:
- Random UUIDs fragment your indexes. Each insert lands in a random position in the B-tree, causing page splits and cache thrashing at scale.
- They are large. 128 bits vs 64 bits — doubles the size of every foreign key and join column.
- They leak nothing useful. You cannot tell when a record was created from its UUID.
Auto-increment integers avoid the indexing problem but are not safe for distributed systems — two nodes will collide.
HeeRanjID gives you a third option.
The ID model
HeeRanjID defines two related identifier types:
HeerId — the default format
HeerId is a 64-bit, time-ordered integer — the same size as a bigint. It is composed from a millisecond timestamp, a node identifier, and a sequence counter (Snowflake-style). This means:
- Inserts are sequential — new rows land at the end of the index, not scattered throughout it.
- IDs are globally unique across nodes without coordination.
- You can extract when a record was created directly from its ID.
- It fits in a
bigintcolumn — no schema changes, no UUID columns, no type casting.
Limits: up to 511 nodes, 8,191 IDs per node per millisecond.
RanjId — the upgrade format
RanjId is a 128-bit, UUIDv8-compatible identifier. It stores in a standard uuid column and is accepted anywhere a UUID is expected.
RanjId is not random. It encodes the same timestamp, node, and sequence components as a HeerId, with more headroom:
- Up to 32,767 nodes and 65,535 IDs per node per timestamp unit
- Sub-millisecond precision — nanoseconds by default, configurable down to femtoseconds
- UUID-compatible — works in
uuidcolumns, UUID form fields, admin, and DRF without extra configuration
Migrating from HeerId to RanjId
When a system outgrows HeerId's limits, it can migrate to RanjId. The conversion is lossless — every HeerId maps to exactly one RanjId — and the HeeRanjIdConversion migration operation handles the column type change and all foreign key updates without disrupting a running system.
Installation
pip install heeranjid-django
Requires heeranjid (installed automatically) and Django 4.2 or later.
Database setup
Run the initial migration to install the ID generation functions in your database:
python manage.py migrate heeranjid_django
This installs the generate_id(), generate_ranjid(), and related SQL functions into your PostgreSQL or SQL Server database. IDs can then be generated at the application level or directly in the database.
Settings
Add your node ID to Django settings:
HEERANJID_NODE_ID = int(os.environ["NODE_ID"])
Each application node (process, container, worker) should have a unique node ID. HeerId supports up to 512 nodes; RanjId supports up to 32,768.
Usage
The quickest path — use the mixin
from django.db import models
from heeranjid_django import HeeRanjIdPKMixin, HeeRanjIdFieldType, HeeRanjIdPrefetch
class Article(HeeRanjIdPKMixin, models.Model):
class HeeRanjId:
field_type = HeeRanjIdFieldType.HEERID # 64-bit integer primary key
prefetch = HeeRanjIdPrefetch.SAVE # generate ID on save
title = models.CharField(max_length=255)
body = models.TextField()
HeeRanjIdPKMixin injects the correct primary key field and manager before Django finalises the model. No manual field declaration needed.
To use RanjId instead:
class Article(HeeRanjIdPKMixin, models.Model):
class HeeRanjId:
field_type = HeeRanjIdFieldType.RANJID
prefetch = HeeRanjIdPrefetch.SAVE
Prefetch modes
| Mode | Behaviour |
|---|---|
SAVE |
ID is generated in pre_save() — the default |
INIT |
ID is generated when the model is instantiated |
MANUAL |
You assign the ID yourself |
Declaring fields explicitly
If you prefer explicit field declarations:
from heeranjid_django.fields import HeerIdField, RanjIdField
class Order(models.Model):
id = HeerIdField(primary_key=True)
objects = HeeRanjIdManager()
Bulk operations
Standard Django bulk_create does not call pre_save(), so IDs would not be assigned automatically. The HeeRanjIdManager overrides bulk_create to handle this transparently:
articles = [Article(title=f"Post {i}") for i in range(1000)]
Article.objects.bulk_create(articles)
This pre-fetches a batch of IDs from the database in a single call, assigns them to any objects missing an ID, then delegates to Django's bulk_create normally. It is efficient and safe for large inserts.
Pre-fetching IDs manually
If you need IDs before saving — for example, to build related objects in memory before committing — you can prefetch them directly:
from heeranjid_django import prefetch_ids
ids = prefetch_ids(Article, 500)
# ids is a list of 500 HeerId (or RanjId) values
Database-backed generation
ID generation happens in the database, not in application code. This means:
- Multiple services can share the same ID space without coordination — they all call the same SQL functions.
- Batch allocation is efficient — fetching 1,000 IDs is a single round-trip.
- Consistency is guaranteed — the same generation algorithm runs regardless of which language or service is generating the ID.
This is the recommended approach for distributed systems. If you are running a single-process Django application, application-level generation works just as well.
Migrations
Initial setup
The heeranjid_django migration installs the SQL schema into your database. It is reversible — rolling back removes all installed functions and tables cleanly.
Replacing an existing UUIDField with RanjIdField
If you have a model that currently uses Django's built-in UUIDField and want to switch to RanjIdField, you can do so with a standard AlterField migration — as long as the column has no existing rows (or the table has not yet been created).
RanjIdField stores uuid on PostgreSQL (the same column type as UUIDField), so the migration is a zero-cost metadata change on that backend. On SQL Server, UUIDField uses uniqueidentifier while RanjIdField uses BINARY(16) (to preserve correct byte order); the column type change requires an empty column.
Step 1 — update your model
# Before
from django.db import models
import uuid
class Widget(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4)
# After
from heeranjid_django.fields import RanjIdField
from heeranjid_django.managers import HeeRanjIdManager
class Widget(models.Model):
id = RanjIdField(primary_key=True)
objects = HeeRanjIdManager()
Step 2 — generate the migration
python manage.py makemigrations
Django detects that the field class changed and generates an AlterField operation.
Step 3 — apply the migration
python manage.py migrate
On PostgreSQL the migration is a no-op at the schema level — the underlying column type (uuid) is identical to UUIDField, so no ALTER COLUMN is executed. However, this migration is only safe when the column is empty. RanjId validates UUIDv8 version bits on every read; any existing rows containing UUIDv4 (or other non-UUIDv8) values will raise InvalidRanjIdVersion when fetched.
On SQL Server the migration changes the column from uniqueidentifier to BINARY(16), which also requires an empty column.
Why BINARY(16) on SQL Server? SQL Server's
uniqueidentifierstores GUIDs in mixed-endian format (first three UUID components are byte-swapped from RFC 4122). RanjId encodes timestamp, node, and sequence into specific bit positions using big-endian layout; storing viauniqueidentifierwould silently corrupt those bits.BINARY(16)preserves the raw big-endian bytes faithfully.
Converting between HeeRanjID formats
If you need to migrate an existing model from HeerId to RanjId primary keys (or vice versa) — where both columns already contain HeeRanjID-generated values — use the HeeRanjIdConversion migration operation:
from heeranjid_django.operations import HeeRanjIdConversion
class Migration(migrations.Migration):
operations = [
HeeRanjIdConversion(
model="myapp.Article",
direction="heerid_to_ranjid",
foreign_keys=[
("myapp_comment", "article_id"),
],
chunk_size=10000,
),
]
This converts the primary key column, updates all specified foreign key columns, and handles constraints. It is chunked to avoid locking large tables. The operation is reversible.
Supported databases
| Database | HeerId | RanjId |
|---|---|---|
| PostgreSQL | bigint |
uuid |
| SQL Server | bigint |
BINARY(16) |
Relationship to the core package
heeranjid-django depends on heeranjid, the Python extension module (built in Rust). The HeerId and RanjId types you get back from fields and managers are instances from that package — you can use them in arithmetic, comparisons, and conversions directly.
article = Article.objects.get(pk=some_id)
print(article.pk) # HeerId(...)
print(article.pk.to_ranjid()) # RanjId(...) — UUID-compatible
print(int(article.pk)) # raw integer
License
MIT
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file heeranjid_django-0.1.1.tar.gz.
File metadata
- Download URL: heeranjid_django-0.1.1.tar.gz
- Upload date:
- Size: 30.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7664e6dd3123d7e51ddf597eaeba784f6b3865f39b67f165e9f99969fb3e7fb4
|
|
| MD5 |
9a4af4c0e16f23e3995640cf60f18c0e
|
|
| BLAKE2b-256 |
0dcd184a89d2344b90c7018b5f100993e593df98c0ca0c3fa3e09535a1c6b47d
|
File details
Details for the file heeranjid_django-0.1.1-py3-none-any.whl.
File metadata
- Download URL: heeranjid_django-0.1.1-py3-none-any.whl
- Upload date:
- Size: 15.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
412fc9c2bd03b939cd05f6fd87c931f53db65abd11b6c9c8d2b1a9cd1cd3e003
|
|
| MD5 |
f26413b4a22ff936100a1503d78da3b5
|
|
| BLAKE2b-256 |
d3e2e8d32c4dbf3f916dca0ff921c7b55d3cac129178670a31e4110cb56baae4
|