System Design (High-Level Design) Module 28 – Case studies: India's digital public infrastructure
Design a document wallet like DigiLocker
Design a national document wallet: references to issuer-signed documents, encrypted uploads, consent grants that expire, and result-day peaks.
What you will learn
- Design issued-document references and user uploads
- Verify issued documents and share them with consent that expires
- Plan for result-day and admission-season peaks
- Estimate the references, uploads and result-day load of a national wallet
Before you start
What you’ll learn · Before you start
What you will learn
- Design issued-document references and user uploads
- Verify issued documents and share them with consent that expires
- Plan for result-day and admission-season peaks
- Estimate the references, uploads and result-day load of a national wallet
Before you start
On this page
Not affiliated
Based on public information about DigiLocker (MeitY). MySmartCoPilot is not affiliated with DigiLocker (MeitY), and DigiLocker (MeitY)’s real architecture may differ from what this lesson describes.
On the morning an exam board publishes 2.5 million results, a million students may ask a document wallet for their mark sheets within 15 minutes, from a board that answers 200 requests a second. A document wallet gives every person one place for official papers, such as mark sheets and driving licences, and its central choice is to hold references, not loose copies: for a document an issuer publishes, it stores a URI built from the issuer’s id, the document type and a document id, and fetches the issuer-signed file on demand or keeps a cached copy; for a scan someone uploads, an encrypted file in object storage.
A bank, a university or an employer gets a document only through a consent grant that names the documents, the purpose and an end time, and it checks the issuer’s signature itself instead of trusting the wallet. The hard parts are that check, consent withdrawn at any moment, and the result morning. India’s DigiLocker is the best-known wallet of this kind.
One locker, many issuers, and a reference to each document
Why the wallet keeps a reference, not a copy
The issuer stays the authority for its documents, so the wallet points at them. Walk through the life of one mark sheet and watch what the wallet holds at each step:
- Issued
The board signs the mark sheet
The exam board issues Ravi's mark sheet as a signed file in its own repository, under a URI made of its issuer id, the document type and a random document id.
- Linked
The locker gains a reference about 500 bytes
When Ravi searches for it, or the board pushes it, his locker gains a reference of about 500 bytes, the URI with a version and a status, not the file.
- Opened
The signed file is fetched
Opening the reference fetches the signed file from the board, or from a copy the wallet cached to spare the board a burst of requests.
- Shared
A lender reads it with consent
Ravi grants a lender access for one purpose until an end time. The lender receives the signed file and checks the board's signature itself, instead of trusting the wallet.
- Corrected
The board corrects it the issuer decides
The board corrects the mark sheet. It is still the authority, so the same reference now leads to the corrected document.
- Elsewhere
A loose copy stays as it was
A copy saved anywhere else still shows the mark sheet as it was first issued, and nothing in that copy tells its reader that the board corrected it.
The timeline has 6 events, from Issued to Elsewhere.
Checkpoint
Check: Why a wallet keeps references
Requirements
Settle these first, and write down what you assume:
- Which documents? Issued ones, from exam boards, universities, transport offices and tax departments, and scans that people upload themselves.
- Who reads them? The owner; requesters, only with consent; and the issuer, which corrects or withdraws.
- How does a requester know a document is genuine? From the issuer’s signature, checked with the issuer’s own published key.
- How long does sharing last? Until an end time the owner picks, or until the owner revokes it.
- When is traffic worst? On result days and in admission season, when millions want one issuer’s documents within minutes.
What DigiLocker's public specifications say
DigiLocker’s homepage reports more than 70 crore registered users and more than 900 crore issued documents
(DigiLocker). Its issuer specification gives every document a URI of the form
IssuerId-DocType-DocId, with document ids it recommends drawing at random so that nobody can guess the next one,
and each issuer implements one Pull URI API that the wallet calls when a person searches for a document
(issuer specification).
Requesters use OAuth 2.0 with PKCE (Proof Key for Code Exchange): An addition to OAuth 2.0, RFC 7636, that binds an authorization code to the app that started the sign-in, so a code intercepted on its way back is useless. More in “Authentication and authorisation for services”, and the consent request can name document types, a
purpose and the time the consent ends; uploads are JPG, PNG or PDF files of at most 10 MB
(requester specification).
The functional requirements: link issued documents by search or by the issuer’s push, open the signed copy, upload and delete scans, share with a grant, revoke it, let requesters read with a token, and show the owner every read. The non-functional ones: a requester can verify a document without trusting the wallet; a revocation works within a minute; nothing leaves without a grant; uploads are encrypted at rest; and a result-day surge never reaches an issuer’s servers as a burst.
Estimates
Work out three figures first, then compare them with the sheet:
Checkpoint
Check the estimates
# The estimate sheet of a national document wallet (lesson system-design-hld/design-document-wallet). Issuers publish
# documents such as mark sheets and licences; the wallet keeps a reference to each one, caches some signed copies, and
# stores the scans people upload themselves. The account and document counts are about the size the largest wallet
# reports; every other number is a design target for the exercise, not any wallet's real traffic. Sizes are decimal
# (1 KB = 1,000 bytes, 1 TB = 10**12 bytes, 1 PB = 10**15 bytes).
HOUR = 3_600
ACCOUNTS = 700_000_000
ISSUED = 9_000_000_000 # references to issued documents
REF_BYTES = 500 # one reference row with its indexes: URI, owner, issuer, type, version, status
REF_COPIES = 3
CACHED_SHARE = 0.30 # issued documents whose signed copy the wallet keeps
COPY_KB = 150 # the signed PDF and its machine-readable XML
EC_OVERHEAD = 1.5 # erasure coding in the object store, instead of three full copies
UPLOAD_SHARE = 0.20 # accounts that upload anything at all
FILES_EACH = 5
UPLOAD_MB = 1.2
VISITS_PER_DAY = 14_000_000 # 2 % of accounts on an ordinary day
REQUESTS_PER_VISIT = 8
BUSY_HOUR = 2.5 # the busiest hour against the day's average
STUDENTS = 2_500_000 # one exam board's results, published at a fixed minute
FIRST_SHARE = 0.40 # of them open their mark sheet within the first quarter of an hour
FIRST_MINUTES = 15
SHEET_KB = 200
ISSUER_PULLS = 200 # pull requests a second the board's repository can answer
PUSH_HOURS = 8 # pushing every document the night before instead
refs_tb = ISSUED * REF_BYTES * REF_COPIES / 10**12
cached_tb = ISSUED * CACHED_SHARE * COPY_KB * 1_000 * EC_OVERHEAD / 10**12
uploaders = ACCOUNTS * UPLOAD_SHARE
upload_raw_tb = uploaders * FILES_EACH * UPLOAD_MB * 10**6 / 10**12
upload_pb = upload_raw_tb * EC_OVERHEAD / 1_000
average = VISITS_PER_DAY * REQUESTS_PER_VISIT / (24 * HOUR)
result_rate = STUDENTS * FIRST_SHARE / (FIRST_MINUTES * 60)
gbps = result_rate * SHEET_KB * 1_000 * 8 / 10**9
push_rate = STUDENTS / (PUSH_HOURS * HOUR)
print("Document wallet: the estimate sheet")
print(f" References {ISSUED / 10**9:.1f} billion issued documents x {REF_BYTES} B x {REF_COPIES} copies = {refs_tb:.1f} TB")
print(f" Cached copies {CACHED_SHARE:.0%} of them at {COPY_KB} KB, stored with {EC_OVERHEAD}x for erasure coding = {cached_tb:,.0f} TB")
print(f" Uploads {uploaders / 10**6:.0f} million accounts x {FILES_EACH} files x {UPLOAD_MB} MB = {upload_raw_tb:,.0f} TB, {upload_pb:.2f} PB stored")
print(f" A normal day {VISITS_PER_DAY / 10**6:.0f} million visits, {average:,.0f} requests/s on average, {average * BUSY_HOUR:,.0f}/s in the busiest hour")
print(f" Result day {STUDENTS * FIRST_SHARE:,.0f} of {STUDENTS:,} students in {FIRST_MINUTES} minutes: {result_rate:,.0f} mark sheets/s, {gbps:.1f} Gbit/s, and as many sign-in codes")
print(f" The issuer answers {ISSUER_PULLS} pulls/s: on demand it is {result_rate / ISSUER_PULLS:.1f}x short; pushed over {PUSH_HOURS} hours the night before, it needs {push_rate:.0f}/s") Output
Document wallet: the estimate sheet References 9.0 billion issued documents x 500 B x 3 copies = 13.5 TB Cached copies 30% of them at 150 KB, stored with 1.5x for erasure coding = 608 TB Uploads 140 million accounts x 5 files x 1.2 MB = 840 TB, 1.26 PB stored A normal day 14 million visits, 1,296 requests/s on average, 3,241/s in the busiest hour Result day 1,000,000 of 2,500,000 students in 15 minutes: 1,111 mark sheets/s, 1.8 Gbit/s, and as many sign-in codes The issuer answers 200 pulls/s: on demand it is 5.6x short; pushed over 8 hours the night before, it needs 87/s
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 wallet_sheet.py
Runs on this device, in your browser. The first run downloads Python (about 13.5 MB), which is kept for the next runs.
Your run, in this browser
The sheet’s lessons are about shape, not size. References are small, 13.5 TB for nine billion of them, while uploads fill 1.26 PB. A board’s result morning asks for 1,111 mark sheets a second from a repository that answers 200: on demand it falls 5.6 times short, while the same documents pushed over the night before need 87 a second.
Exercise · Easy · Python
Size a document wallet for an exam result day
A document wallet is sized by its references, its uploads and the minutes after an exam board publishes results. Write four functions in sizing.py. Sizes are decimal (1 MB = 106 bytes, 1 TB = 1012 bytes, 1 PB = 10**15 bytes).
result_day_rate(students, share, minutes): mark sheets a second whenshareofstudentsopen theirs within the firstminutesminutes.reference_tb(documents, bytes_each, copies): the terabytes thatdocumentsreference rows ofbytes_eachbytes take withcopiescopies.upload_pb(accounts, share_uploading, files_each, mb_each, overhead): the petabytes of uploads whenshare_uploadingofaccountsuploadfiles_eachfiles ofmb_eachMB, stored withoverheadtimes their size.push_rate(documents, hours): the documents a second an issuer sends when it pushesdocumentsevenly overhourshours.
The sample tests accept each figure within 10 %, as an estimate should.
Starter code · sizing.py
HOUR = 3_600
def result_day_rate(students, share, minutes):
"""Mark sheets a second in the first minutes after results go live."""
# Replace this line with your code.
return 0
def reference_tb(documents, bytes_each, copies):
"""Terabytes of reference rows, with every copy."""
# Replace this line with your code.
return 0
def upload_pb(accounts, share_uploading, files_each, mb_each, overhead):
"""Petabytes of uploaded files, with the storage overhead."""
# Replace this line with your code.
return 0
def push_rate(documents, hours):
"""Documents a second when an issuer pushes them evenly."""
# Replace this line with your code.
return 0 The sample tests · test_sizing.py
import math
from sizing import push_rate, reference_tb, result_day_rate, upload_pb
def near(actual, expected):
"""Within 10%, as an estimate should be."""
return math.isclose(actual, expected, rel_tol=0.10)
def test_result_day_rate():
"""spreads the first wave of students over its seconds"""
assert near(result_day_rate(2_500_000, 0.40, 15), 1_111)
assert near(result_day_rate(1_200_000, 0.50, 20), 500)
def test_reference_tb():
"""counts every copy of every reference row"""
assert near(reference_tb(9_000_000_000, 500, 3), 13.5)
assert near(reference_tb(2_000_000_000, 300, 2), 1.2)
def test_upload_pb():
"""only the accounts that upload, with the storage overhead"""
assert near(upload_pb(700_000_000, 0.20, 5, 1.2, 1.5), 1.26)
assert near(upload_pb(100_000_000, 0.30, 4, 2.0, 3.0), 0.72)
def test_push_rate():
"""spreads a push evenly over its hours"""
assert near(push_rate(2_500_000, 8), 86.8)
assert near(push_rate(600_000, 5), 33.3) A hint
The first minutes minutes hold students * share requests spread over minutes * 60 seconds. Bytes become terabytes by dividing by 10**12, and megabytes become bytes by multiplying by 10**6. A push spread over hours hours lasts hours * 3600 seconds.
Results of the sample tests
| Test | Result | Details |
|---|
What your code printed
The sample tests run on this device, in your browser (Pyodide): nothing is sent to mysmartcopilot.com. The first run downloads Python (about 13.5 MB), which is kept for the next runs. A check in your browser is feedback for you, not proof that the code is right for every input.
The first design
The document wallet's first design
Text description of the diagram
The diagram runs from top to bottom.
- People use the app or the website and sign in with a one-time code; requesters such as banks, universities and employers call the same API gateway with a token.
- The gateway keeps sessions and applies limits per account and per requester. For every read by a requester it asks the consent service, which holds grants: which documents, for which purpose, for which requester and until when. The consent service writes every read, allowed or refused, to an audit log that the owner can see.
- The wallet service keeps each person's references to issued documents, their uploads and the versions of each.
- To find or fetch an issued document, the wallet calls the issuer gateway, which sends signed pull requests to the issuer repositories of exam boards, transport offices and tax departments, and also accepts documents that issuers push. A dashed arrow shows the signed document, or its URI, coming back.
- The wallet keeps signed copies in object storage, keyed by document URI, and stores uploads in a separate encrypted object store with a data key for each object.
- Two dashed arrows show the issuers' public keys being published to an issuer directory, which requesters use to verify signatures themselves.
The owner shares through the consent service, and a lender reads through the gateway with a token. Step through one share on the diagram’s parts:
The drawing shows the whole scene; its 5 steps are listed below.
Every step as text
- The grant. Ravi picks three documents, the lender, the purpose and an end date. The consent service records the grant.
- A read with a token. The lender calls the gateway with its token and the URI of the mark sheet it wants.
- Is this read allowed?. The gateway asks the consent service whether this read is allowed, and the answer, yes or no, is logged where Ravi can see it.
- The signed copy. The wallet returns the signed file, from its cache or fetched from the board's repository.
- The lender checks. The lender checks the signature with the board's public key, which it takes from the issuer directory, never from the wallet.
Checkpoint
Check: The first design: one share through it
The deep-dive questions
- What does the wallet store for an issued document, and when does it keep a copy?
- How does a requester check a document without trusting the wallet?
- What is inside a consent grant, and how fast does a revocation take effect?
- How are uploads encrypted, checked and limited?
- How does the wallet survive the morning an exam board publishes its results?
- What changes at ten and at a hundred times the volume?
The URI names a document wherever it lives, so it keys every table Pro: opens with a Pro, Premium or Ultimate pass
In this part:
- A requester checks four things, and the issuer’s signature is only the first
- Twelve cases through a requester’s verifier
- The checks in order
- A grant names one requester, a set of documents, one purpose and an end time
- Seven attempts against one grant
- The grant check in code
- A grant checked on every read makes a revocation immediate
- Uploads are untrusted bytes, scanned first and encrypted with a key each
- On result day only moving the board’s work to the night before keeps up
- Four designs on one board’s morning
- The morning with the work moved
- At 10× the reference store is sharded by account, and at 100× object storage is the bill
- Failure drills: what breaks, and what keeps working
- Alternatives considered and rejected
- What each level covers, and a 45-minute rehearsal
- What each level is expected to cover
- A 45-minute rehearsal
- Practise the whole design, weakest idea first
- Interview questions
- Why does a document wallet keep a reference to an issued document, made of the issuer, the document type and an id, instead of only its own copy of the file?
- A bank receives a mark sheet through a document wallet. How does it check that the document is genuine and still valid without trusting the wallet, and what has to happen when the exam board rotates its signing key?
- An owner shares three documents with a lender for 30 days and revokes the share after two days. What must stop working at once, what can never be undone, and how do you design grants, tokens and the audit log for it?
- An exam board publishes 2.5 million results at 10:00, and 40 % of the students open their mark sheet within 15 minutes. The board's repository answers 200 requests a second. Design the wallet's side of that morning.
- Key takeaways
3 diagrams · 4 runnable examples (JavaScript, Python) · 3 interview answers · about 57 minutes
Opening this part with your pass…
This part opens with any MySmartCoPilot pass.
- Pro pass · 1 week₹79$2.99
- 30 days₹179$5.99
- 1 year₹1,699$45
One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.
Passes can’t be bought right now, so this part can’t be opened at the moment. The free part above stands on its own. If you have a pass, enter your licence key.
It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).
Read a complete sample first: Design a real-time leaderboard
References
- DigiLocker (Ministry of Electronics and Information Technology, Government of India)
- Issuer API Specification, version 1.13 (DigiLocker) (National e-Governance Division, Ministry of Electronics and Information Technology)
- Requester API Specification, version 1.12 (DigiLocker) (National e-Governance Division, Ministry of Electronics and Information Technology)
- XML Certificate Formats, version 1.9 (DigiLocker) (National e-Governance Division, Ministry of Electronics and Information Technology)
- DigiLocker Partners Resource Center (API Setu) (Ministry of Electronics and Information Technology, Government of India)
- The Digital Personal Data Protection Act, 2023 (No. 22 of 2023) (Ministry of Electronics and Information Technology, Government of India)
- RFC 7515: JSON Web Signature (JWS) (IETF)
- RFC 8037: CFRG Elliptic Curve Diffie-Hellman (ECDH) and Signatures in JSON Object Signing and Encryption (JOSE) (IETF)
- RFC 8725: JSON Web Token Best Current Practices (IETF)
- RFC 7636: Proof Key for Code Exchange by OAuth Public Clients (IETF)
- XML Signature Syntax and Processing Version 1.1 (W3C)
- Crypto (Node.js 24 documentation) (OpenJS Foundation)
Related tools
Report a problem with this lesson
Kept only in this browser. Your Learn progress