system-design54 min read

How to Design an Enterprise Document Management System — A Senior+ Guide | Ayodhyya

How to Design an Enterprise Document Management System

Building a Production-Grade SharePoint/Google Drive for Enterprise — Storage, Versioning, Search, Workflows, Compliance

Senior+ System Design Guide 12,000+ Words 26 Deep-Dive Sections C# · Mermaid · Real-World Case Studies

1. System Overview and Requirements

What is an Enterprise Document Management System?

An Enterprise Document Management System (DMS) is a centralized platform for storing, organizing, managing, securing, and retrieving organizational documents throughout their lifecycle. Think SharePoint, Google Drive for Enterprise, Box, or OpenText — but designed for regulated industries that demand versioning, audit trails, retention policies, and granular access control.

This is not a simple file server. A production DMS must handle millions of documents across thousands of users, support concurrent editing, enforce compliance, provide full-text search across terabytes of content, integrate with enterprise identity providers, and maintain an unbreakable audit trail. Every design decision must account for security, scalability, and regulatory requirements simultaneously.

Functional Requirements

  • Document CRUD: Upload, download, view, and delete documents with support for 500+ file types
  • Folder Hierarchy: Nested folder structures with libraries, sites, and workspaces
  • Versioning: Major and minor version tracking with check-in/check-out to prevent edit conflicts
  • Content Types: Define custom metadata schemas per document type (contracts, invoices, SOPs)
  • Full-Text Search: Search across document content, metadata, and file names with sub-second latency
  • Document Preview: Render PDFs, Office documents, images, and videos in-browser without download
  • Co-Authoring: Multiple users editing the same document simultaneously with operational transforms
  • Workflows: Configurable approval, review, and electronic signature pipelines
  • Access Control: ACL-based permissions with role inheritance and external sharing links
  • Retention Policies: Time-based archival and deletion with legal hold capabilities
  • Sensitivity Labels: Classification labels (Public, Internal, Confidential, Restricted) with enforcement
  • Audit Trail: Immutable log of every access, modification, share, and deletion event
  • Templates: Predefined document templates with auto-populated metadata fields
  • Metadata Views: List, gallery, calendar, and board views driven by document metadata
  • OCR: Extract text from scanned PDFs, images, and photographs
  • Barcode/QR Scanning: Extract and index barcode data for document categorization
  • Records Management: Declare records, apply legal holds, and support eDiscovery exports
  • Content Deduplication: Block-level deduplication to reduce storage costs
  • Large File Support: Chunked, resumable uploads for files up to 250 GB
  • Template Generation: Create new documents from templates with dynamic field population

Non-Functional Requirements

AttributeTargetRationale
Availability99.99% (52 min downtime/year)Enterprise-critical; legal teams depend on document access
Latency<200ms for metadata ops, <2s for searchUsers expect snappy file browsing
Throughput10,000+ uploads/min, 50,000+ downloads/minPeak usage during month-end filing
Storage Scale10 PB+ across all tenantsEnterprise customers accumulate petabytes over decades
Document Count500M+ documents per large tenantLegal and healthcare orgs have billions of files
Durability99.999999999% (11 nines)Document loss is catastrophic for compliance
ConsistencyStrong for metadata, eventual for search indexMetadata must be immediately consistent; search can lag
SecuritySOC 2 Type II, GDPR, HIPAA, FedRAMPEnterprise compliance is non-negotiable
Interview Tip: Start by clarifying whether this is a greenfield system or migrating from an existing file server. This affects every design decision — existing systems have legacy data, migration paths, and compatibility requirements. Also clarify whether co-authoring is a Day 1 requirement or can be phased in, as it dramatically increases system complexity.

Capacity Estimation

Assume 10,000 enterprises with an average of 5,000 users each. That is 50 million total users. Average document size: 2 MB. Average documents per user: 200. Total documents: 10 billion. Total storage: 20 PB.

Daily uploads: 50 million users x 5 documents/day = 250 million uploads/day. Peak upload rate: ~5,000 per second. Daily downloads (view + edit): 250 million x 10 = 2.5 billion downloads/day. Peak download rate: ~30,000 per second.

Search index size: assuming 1 KB metadata + 50 KB extracted text per document = 51 KB x 10 billion = 510 TB of index data.

2. High-Level Architecture

graph TB subgraph Clients["Client Layer"] WEB["Web App
React SPA"] MOB["Mobile Apps
iOS / Android"] DESKTOP["Desktop Sync
Windows / macOS"] API_CLIENT["Third-Party
API Consumers"] end subgraph Gateway["API Gateway & Auth"] GW["API Gateway
Rate Limiting, Routing"] AUTH["Identity Provider
Azure AD / Okta / SAML"] RBAC["Authorization Service
ACL Engine"] end subgraph Core["Core Services"] DOC["Document Service
CRUD, Metadata"] VER["Versioning Service
Major/Minor, Check-In/Out"] SEARCH_SVC["Search Service
Elasticsearch Cluster"] PREVIEW_SVC["Preview Service
Rendering Pipeline"] WORKFLOW_SVC["Workflow Engine
Approval, Review, Signature"] COLLAB_SVC["Collaboration Service
OT / CRDT Co-Authoring"] RETENTION_SVC["Retention Service
Policies, Archival"] CLASSIFY_SVC["Classification Service
Sensitivity Labels"] AUDIT_SVC["Audit Service
Event Logging"] TEMPLATE_SVC["Template Service
Document Generation"] VIEW_SVC["View Service
Metadata-Driven Views"] DEDUP_SVC["Deduplication Service
Block-Level Dedup"] OCR_SVC["OCR Service
Text Extraction"] BARCODE_SVC["Barcode Service
Scanning, Indexing"] end subgraph Storage["Storage Layer"] BLOB["Blob Storage
Azure Blob / S3"] DB["Metadata DB
Cosmos DB / PostgreSQL"] CACHE_SVC["Cache Layer
Redis Cluster"] QUEUE["Message Queue
Kafka / Service Bus"] GRAPH["Graph DB
Neo4j (Permissions)"] end subgraph Pipeline["Async Pipelines"] INDEX_P["Indexing Pipeline
Content Extraction"] THUMB_P["Thumbnail Pipeline
Preview Generation"] SCAN_P["Malware Scan
ClamAV / Defender"] DLP_P["DLP Pipeline
Data Loss Prevention"] end WEB --> GW MOB --> GW DESKTOP --> GW API_CLIENT --> GW GW --> AUTH GW --> RBAC GW --> DOC GW --> SEARCH_SVC GW --> PREVIEW_SVC GW --> WORKFLOW_SVC GW --> COLLAB_SVC GW --> RETENTION_SVC GW --> CLASSIFY_SVC GW --> AUDIT_SVC GW --> TEMPLATE_SVC GW --> VIEW_SVC GW --> OCR_SVC GW --> BARCODE_SVC DOC --> BLOB DOC --> DB DOC --> CACHE_SVC DOC --> QUEUE VER --> DB VER --> BLOB SEARCH_SVC --> DB RBAC --> GRAPH WORKFLOW_SVC --> QUEUE QUEUE --> INDEX_P QUEUE --> THUMB_P QUEUE --> SCAN_P QUEUE --> DLP_P INDEX_P --> SEARCH_SVC THUMB_P --> BLOB

Service Decomposition

The architecture follows a service-oriented design where each major capability is an independently deployable service. The Document Service is the central orchestrator — it handles CRUD operations, delegates to blob storage for binary content, and coordinates metadata in the database. All other services subscribe to events from the Document Service via Kafka topics.

The API Gateway is the single entry point. It handles authentication via JWT tokens validated against the enterprise's identity provider, enforces rate limits per tenant, and routes requests to the appropriate backend service. We use a claims-based authorization model where the token carries role and permission claims.

Key Design Decision: We store blob content and metadata in separate systems. Blob storage (Azure Blob or S3) handles the binary content with high durability and low cost. A relational/document database (Cosmos DB or PostgreSQL) stores metadata, versioning info, and permission records. This separation allows us to scale storage capacity independently from metadata query performance.

Data Flow for Upload

  1. Client initiates upload via POST /api/v1/documents/upload with file stream and metadata JSON
  2. API Gateway authenticates the request and validates the JWT token
  3. Document Service checks quotas and permissions
  4. File stream is written to a temp blob location via chunked upload
  5. Malware scan is triggered asynchronously via Kafka
  6. Content is hashed for deduplication; if duplicate exists, reference is created instead of new blob
  7. Metadata record is created in the database with PENDING_SCAN status
  8. Indexing pipeline extracts text content and indexes it in Elasticsearch
  9. Thumbnail pipeline generates preview images
  10. Document status transitions to ACTIVE and becomes searchable

3. Document Storage — Blobs and Metadata

Two-Tier Storage Architecture

Every document in the system is split into two components: the blob (the actual file binary) and the metadata record (the descriptive information about the file). These are stored in fundamentally different systems optimized for their respective access patterns.

Blob Storage

We use Azure Blob Storage (or S3 on AWS) organized into containers with a hierarchical namespace. The blob key follows a deterministic path structure:

plaintext
/{tenant_id}/{library_id}/{year}/{month}/{day}/{document_id}/{version_number}/{filename}

Example:
/acme-corp/contracts-lib/2026/07/12/doc-8f3a-92bc/v2/Service-Agreement-v2.pdf

This path structure provides several benefits: tenant isolation at the storage level, temporal locality for archival operations, and deterministic paths that can be regenerated from metadata alone. We use the document ID (a UUID) rather than the filename to avoid collisions and path traversal issues.

Metadata Storage

Metadata is stored in a globally-distributed database like Cosmos DB (multi-master) or a sharded PostgreSQL cluster. The core document metadata record contains:

C#
public class DocumentMetadata
{
    public Guid DocumentId { get; set; }
    public Guid TenantId { get; set; }
    public Guid LibraryId { get; set; }
    public Guid? ParentFolderId { get; set; }
    public string FileName { get; set; }
    public string ContentType { get; set; }  // MIME type
    public long FileSize { get; set; }
    public string BlobPath { get; set; }
    public string ContentHash { get; set; }  // SHA-256 for dedup
    public int MajorVersion { get; set; }
    public int MinorVersion { get; set; }
    public DocumentStatus Status { get; set; }
    public Guid CreatedBy { get; set; }
    public DateTime CreatedAt { get; set; }
    public Guid ModifiedBy { get; set; }
    public DateTime ModifiedAt { get; set; }
    public Guid? CheckedOutBy { get; set; }
    public DateTime? CheckedOutAt { get; set; }
    public Guid ContentTypeId { get; set; }
    public Guid? RetentionPolicyId { get; set; }
    public SensitivityLabel Sensitivity { get; set; }
    public bool IsDeleted { get; set; }
    public DateTime? DeletedAt { get; set; }
    public string ExternalId { get; set; }  // For barcode/QR mapping
}

public enum DocumentStatus
{
    PendingScan,
    Active,
    CheckedOut,
    Archived,
    OnHold,
    Deleted
}

public enum SensitivityLabel
{
    Public,
    Internal,
    Confidential,
    Restricted
}

The metadata database uses a composite partition key of (TenantId, LibraryId) to ensure all documents within a library are colocated for efficient range queries. The primary index is on DocumentId, with secondary indexes on (TenantId, ModifiedAt), (TenantId, ContentTypeId), and (ParentFolderId).

Blob Storage Tiers

TierAccess PatternLatencyCost (per GB/month)Use Case
HotFrequent read/write<10ms$0.018Active documents, recent uploads
CoolInfrequent access<100ms$0.010Documents older than 90 days
ColdRare access<150ms$0.0045Archived documents, retention period
ArchiveCompliance onlyHours$0.001Legal hold, regulatory retention
Cost Optimization: A lifecycle policy automatically transitions blobs between tiers. Documents with no access for 90 days move to Cool. After 1 year, they move to Cold. After 7 years (or per retention policy), they move to Archive. This can reduce storage costs by 80% compared to keeping everything in the Hot tier.

Write Path — How Uploads Work

For files under 100 MB, we use a single PUT operation to blob storage. For larger files, the upload is chunked into 4 MB blocks. Each block is uploaded independently (and can be retried), then committed atomically. The upload session is tracked in Redis with a TTL of 24 hours, allowing resumable uploads if the client disconnects.

C#
public class BlobStorageService
{
    private readonly IBlobContainer _container;
    private readonly IDeduplicationService _dedup;

    public async Task<BlobUploadResult> UploadDocumentAsync(
        Stream fileStream, DocumentMetadata metadata)
    {
        var contentHash = await ComputeSha256Async(fileStream);
        fileStream.Position = 0;

        var existingBlob = await _dedup.FindByHashAsync(
            metadata.TenantId, contentHash);
        if (existingBlob != null)
        {
            return new BlobUploadResult
            {
                BlobPath = existingBlob.BlobPath,
                IsDeduplicated = true,
                StorageSaved = metadata.FileSize
            };
        }

        var tier = DetermineStorageTier(metadata);
        var blobPath = GenerateBlobPath(metadata);
        var accessTier = tier switch
        {
            StorageTier.Hot => AccessTier.Hot,
            StorageTier.Cool => AccessTier.Cool,
            _ => AccessTier.Hot
        };

        if (metadata.FileSize < 100 * 1024 * 1024)
        {
            await _container.UploadAsync(
                blobPath, fileStream, accessTier);
        }
        else
        {
            await _container.ChunkedUploadAsync(
                blobPath, fileStream,
                chunkSize: 4 * 1024 * 1024,
                accessTier);
        }

        return new BlobUploadResult
        {
            BlobPath = blobPath,
            IsDeduplicated = false,
            StorageSaved = 0
        };
    }
}

4. Folder and Library Hierarchy

The Entity Hierarchy

Enterprise DMS platforms organize content in a strict hierarchy: Tenant > Site > Library > Folder > Document. Each level has its own access control, settings, and metadata schemas.

graph TB TENANT["Tenant
(Enterprise Account)
acme-corp"] --> SITE1["Site: Legal"] TENANT --> SITE2["Site: Engineering"] TENANT --> SITE3["Site: Human Resources"] SITE1 --> LIB1["Library: Contracts"] SITE1 --> LIB2["Library: NDAs"] SITE1 --> LIB3["Library: Litigation"] LIB1 --> F1["Folder: 2026"] F1 --> F2["Folder: Q1"] F1 --> F3["Folder: Q2"] F2 --> D1["Document: Service-Agreement.pdf"] F2 --> D2["Document: SOW.docx"] LIB2 --> F4["Folder: Active"] LIB2 --> F5["Folder: Expired"]

Entity Model

C#
public class Tenant
{
    public Guid TenantId { get; set; }
    public string Name { get; set; }
    public string Domain { get; set; }
    public StorageQuota Quota { get; set; }
    public TenantSettings Settings { get; set; }
    public List<Site> Sites { get; set; }
}

public class Site
{
    public Guid SiteId { get; set; }
    public Guid TenantId { get; set; }
    public string Name { get; set; }
    public string Description { get; set; }
    public Guid OwnerUserId { get; set; }
    public List<DocumentLibrary> Libraries { get; set; }
}

public class DocumentLibrary
{
    public Guid LibraryId { get; set; }
    public Guid SiteId { get; set; }
    public string Name { get; set; }
    public Guid DefaultContentTypeId { get; set; }
    public VersioningPolicy VersioningPolicy { get; set; }
    public List<ContentType> AllowedContentTypes { get; set; }
    public List<Folder> RootFolders { get; set; }
}

public class Folder
{
    public Guid FolderId { get; set; }
    public Guid? ParentFolderId { get; set; }  // null = root
    public Guid LibraryId { get; set; }
    public string Name { get; set; }
    public string Path { get; set; }  // Materialized path
    public int Depth { get; set; }
}

Materialized Path for Fast Queries

Every folder stores a materialized path — a denormalized string containing the full hierarchy from root to the current folder. For example: /contracts/2026/Q1/. This enables efficient subtree queries without recursive CTEs. When moving a folder, we update the materialized path of all descendants in a single batch operation.

SQL
SELECT d.* FROM Documents d
JOIN Folders f ON d.ParentFolderId = f.FolderId
WHERE f.MaterializedPath LIKE '/contracts/2026/%'
  AND f.TenantId = @tenantId
  AND d.IsDeleted = 0
ORDER BY d.FileName;

Site Templates and Provisioning

When a new enterprise tenant is created, we provision a set of default sites (Legal, Engineering, HR, Finance) each with preconfigured libraries, content types, and retention policies. The provisioning process runs as an asynchronous workflow that creates all database records, configures blob containers, sets up search indexes, and applies initial ACLs.

Design Note: Libraries are the fundamental unit of organization. Each library has its own versioning policy, allowed content types, and default views. Users can create custom views (filtered by metadata, sorted by date, grouped by content type) within a library. Think of a library as a specialized "database" for a specific category of documents.

5. File Versioning — Major/Minor and Check-In/Check-Out

Versioning Model

Enterprise DMS systems use a dual version numbering scheme: major versions (1.0, 2.0, 3.0) for published/approved documents and minor versions (1.1, 1.2, 2.1) for drafts and working copies. This mirrors how legal and compliance teams think about document lifecycle — a contract in negotiation might be at version 2.3 (draft 2, revision 3) before being finalized as version 3.0.

Version TypeExampleVisibilityUse Case
Major (published)1.0, 2.0, 3.0All users with read accessApproved, finalized documents
Minor (draft)1.1, 1.2, 2.1Only author + edit permissionsWork in progress, revisions
Check-out draft2.0 (locked)Only the person who checked outExclusive editing to prevent conflicts

Version Storage

When a new version is uploaded, we create a new version record in the database pointing to the new blob. Previous version blobs are NOT deleted — they remain in storage for the lifetime of the document (and beyond, if retention policies require it). This means version 1.0 and version 2.0 are both fully accessible at any time.

C#
public class DocumentVersion
{
    public Guid VersionId { get; set; }
    public Guid DocumentId { get; set; }
    public int MajorVersion { get; set; }
    public int MinorVersion { get; set; }
    public string BlobPath { get; set; }
    public long FileSize { get; set; }
    public string ContentHash { get; set; }
    public Guid CreatedBy { get; set; }
    public DateTime CreatedAt { get; set; }
    public string Comment { get; set; }
    public VersionStatus Status { get; set; }
}

public enum VersionStatus
{
    Draft,
    PendingApproval,
    Published,
    Archived,
    Superseded
}

Check-In / Check-Out Workflow

Check-out provides pessimistic locking for documents that cannot tolerate concurrent edits. When a user checks out a document, a lock record is created and the document becomes exclusively editable by that user. Other users see a "Checked out by [user]" badge and can only view the last published version.

sequenceDiagram actor User1 as Alice actor User2 as Bob participant API as API Gateway participant DOC as Document Service participant DB as Metadata DB participant BLOB as Blob Storage User1->>API: POST /docs/{id}/checkout API->>DOC: CheckOut(documentId, userId) DOC->>DB: Acquire lock (INSERT with unique constraint) DB-->>DOC: Lock acquired DOC-->>API: 200 OK, checkout token API-->>User1: Document locked for editing User2->>API: POST /docs/{id}/checkout API->>DOC: CheckOut(documentId, userId) DOC->>DB: Acquire lock DB-->>DOC: FAIL - already locked by Alice DOC-->>API: 409 Conflict API-->>User2: "Checked out by Alice since 2:30 PM" User1->>API: PUT /docs/{id}/content API->>DOC: Upload new version DOC->>BLOB: Store new blob DOC->>DB: Create version record DOC->>DB: Release lock API-->>User1: 200 OK User2->>API: POST /docs/{id}/checkin API->>DOC: CheckIn(documentId, comment) DOC->>DB: Create version, release lock DOC-->>API: 200 OK API-->>User2: Document available for editing

Version Cleanup Policies

Keeping every version forever is impractical at scale. We implement configurable version limits per library:

  • Keep last N major versions: Default is 50. Older major versions are soft-deleted.
  • Keep last N minor versions per major: Default is 10.
  • Expiration: Versions older than N days are purged (except published versions protected by retention policy).
  • Legal hold override: Documents under legal hold retain ALL versions regardless of cleanup policy.
C#
public class VersionCleanupService
{
    public async Task CleanupVersionsAsync(Guid libraryId)
    {
        var policy = await _db.GetVersioningPolicyAsync(libraryId);
        var libraries = await _db.GetDocumentsInLibraryAsync(libraryId);

        foreach (var doc in libraries)
        {
            var versions = await _db.GetVersionsAsync(doc.DocumentId);

            if (doc.RetentionPolicy?.IsOnHold == true) continue;

            var publishedVersions = versions
                .Where(v => v.Status == VersionStatus.Published)
                .OrderByDescending(v => v.MajorVersion)
                .ToList();

            var versionsToKeep = publishedVersions
                .Take(policy.MajorVersionsToKeep)
                .Select(v => v.MajorVersion)
                .ToHashSet();

            var versionsToArchive = versions
                .Where(v => !versionsToKeep.Contains(v.MajorVersion))
                .ToList();

            foreach (var version in versionsToArchive)
            {
                await _blob.TierBlobAsync(version.BlobPath, BlobTier.Archive);
                version.Status = VersionStatus.Superseded;
                await _db.UpdateVersionAsync(version);
            }
        }
    }
}
Interview Tip: Be prepared to discuss the trade-off between versioning granularity and storage cost. Every minor save creates a new version. For a 100 MB document, keeping 50 major versions with 10 minor versions each means 550 copies of the blob — 55 GB of storage for a single document. This is why version limits and cleanup policies are essential at enterprise scale.

6. Content Types and Metadata Schemas

What Are Content Types?

Content types are the mechanism that makes a DMS more than a glorified file server. A content type defines a template for a class of documents — its required metadata fields, validation rules, default workflows, retention policies, and allowed templates. For example, the "Contract" content type might require fields like Contract Number, Party Name, Effective Date, Expiration Date, and Contract Value.

C#
public class ContentType
{
    public Guid ContentTypeId { get; set; }
    public Guid TenantId { get; set; }
    public string Name { get; set; }
    public string Description { get; set; }
    public string IconUrl { get; set; }
    public Guid? ParentContentTypeId { get; set; }
    public List<MetadataField> MetadataFields { get; set; }
    public List<WorkflowDefinition> DefaultWorkflows { get; set; }
    public RetentionPolicy DefaultRetention { get; set; }
    public List<DocumentTemplate> Templates { get; set; }
}

public class MetadataField
{
    public Guid FieldId { get; set; }
    public string InternalName { get; set; }
    public string DisplayName { get; set; }
    public FieldType Type { get; set; }
    public bool IsRequired { get; set; }
    public bool IsIndexed { get; set; }
    public bool IsSearchable { get; set; }
    public string DefaultValue { get; set; }
    public List<ChoiceOption> Choices { get; set; }
    public FieldValidation Validation { get; set; }
}

public enum FieldType
{
    Text, MultiLineText, Number, Currency, DateTime,
    Choice, MultiChoice, YesNo, Person, Hyperlink,
    Calculated, ManagedMetadata
}

Example Content Types

Content TypeRequired FieldsWorkflowRetention
ContractContract#, Party, Start Date, End Date, Value, SignatoryLegal Review > Manager Approval > eSignature10 years after expiration
InvoiceInvoice#, Vendor, Amount, Due Date, PO ReferenceAP Review > Manager Approval > Payment7 years (tax requirement)
Employee RecordEmployee ID, Name, Department, Start Date, PositionHR Review > Manager Acknowledgment7 years after termination
SOPSOP#, Department, Effective Date, Review CyclePeer Review > Quality Approval > PublishPermanent (regulatory)
Meeting MinutesMeeting Date, Attendees, Action ItemsAuto-archive after 30 days3 years

Content Type Inheritance

Content types support inheritance. A "Master Service Agreement" content type inherits from "Contract" and adds fields specific to MSAs. This allows organizations to define a base set of metadata fields and progressively specialize them. The inheritance tree is stored as an adjacency list in the database, and the full field set for a content type is computed by traversing the tree and merging fields.

Interview Tip: Content types are the biggest differentiator between a DMS and a file server. Emphasize that content types enable: (1) structured data extraction from unstructured documents, (2) automated workflow routing based on document classification, (3) retention policy enforcement based on document type, and (4) metadata-driven search and filtering that goes far beyond filename matching.

Managed Metadata and Taxonomy

Enterprises often maintain a centralized taxonomy — a hierarchy of terms like "Legal > Contracts > Vendor Agreements" or "Engineering > Design Documents > Architecture Specs". This taxonomy is stored in a managed metadata service and referenced by choice fields. When a user tags a document with a taxonomy term, they are creating a link to a globally unique term that can be used for cross-site navigation, aggregation, and governance.

SQL
CREATE TABLE TaxonomyTerms (
    TermId UNIQUEIDENTIFIER PRIMARY KEY,
    TenantId UNIQUEIDENTIFIER NOT NULL,
    ParentTermId UNIQUEIDENTIFIER NULL,
    Name NVARCHAR(256) NOT NULL,
    Path NVARCHAR(4000) NOT NULL,
    Depth INT NOT NULL DEFAULT 0,
    IsAvailableInNewForms BIT DEFAULT 1,
    IsDeprecated BIT DEFAULT 0
);

CREATE TABLE DocumentTerms (
    DocumentId UNIQUEIDENTIFIER NOT NULL,
    TermId UNIQUEIDENTIFIER NOT NULL,
    FieldId UNIQUEIDENTIFIER NOT NULL,
    PRIMARY KEY (DocumentId, TermId, FieldId)
);

8. Document Preview and Rendering

Preview Pipeline

When a user clicks on a document in the web app, they expect to see an inline preview without downloading the file. The preview pipeline converts documents into web-friendly formats (HTML, images, or PDF) and caches the results. The conversion happens asynchronously after upload, and the results are stored alongside the original blob.

graph LR REQ["Preview Request"] --> CACHE{"Cache Hit?"} CACHE -->|Yes| SERVE["Serve Cached"] CACHE -->|No| CONVERT["Conversion Service"] CONVERT --> PDF["LibreOffice /
Office Online"] CONVERT --> IMG["ImageMagick /
Sharp"] CONVERT --> VIDEO["FFmpeg"] PDF --> STORE["Store in
Preview Cache"] IMG --> STORE VIDEO --> STORE STORE --> SERVE

Conversion Strategies by File Type

Source FormatConversion ToolOutput FormatNotes
DOCX/XLSX/PPTXLibreOffice Headless / Word OnlinePDF + PNG pagesFidelity is critical for enterprise docs
PDFPDF.js rendererHTML canvasServer-side: page images for thumbnails
JPG/PNG/SVGSharp / ImageMagickOptimized thumbnailResize for preview, keep original for download
Video (MP4)FFmpegThumbnail sprite + HLSGenerate preview frames at intervals
Audio (MP3/WAV)FFmpegWaveform visualizationSVG waveform for inline display
MarkdownMarkdigHTMLRender inline with syntax highlighting
Code filesHighlight.jsSyntax-highlighted HTMLAuto-detect language
C#
public class PreviewService
{
    private readonly IBlobStorage _blobStorage;
    private readonly IRedisCache _cache;
    private readonly ILibreOfficeConverter _libreOffice;
    private readonly IImageProcessor _imageProcessor;

    public async Task<PreviewResult> GetPreviewAsync(
        Guid documentId, int? pageNumber = null)
    {
        var cacheKey = $"preview:{documentId}:{pageNumber ?? 0}";
        var cached = await _cache.GetAsync<PreviewResult>(cacheKey);
        if (cached != null) return cached;

        var metadata = await _db.GetDocumentAsync(documentId);
        var originalBlob = await _blobStorage.GetBlobAsync(metadata.BlobPath);

        PreviewResult result = metadata.ContentType switch
        {
            var ct when ct.Contains("wordprocessingml") =>
                await ConvertOfficeDocumentAsync(originalBlob, metadata),
            var ct when ct.Contains("pdf") =>
                await RenderPdfAsync(originalBlob, pageNumber),
            var ct when ct.StartsWith("image/") =>
                await GenerateImagePreviewAsync(originalBlob, metadata),
            var ct when ct.StartsWith("video/") =>
                await GenerateVideoPreviewAsync(originalBlob, metadata),
            _ => await GenerateTextPreviewAsync(originalBlob, metadata)
        };

        await _cache.SetAsync(cacheKey, result, TimeSpan.FromHours(24));
        return result;
    }

    private async Task<PreviewResult> ConvertOfficeDocumentAsync(
        IBlob file, DocumentMetadata metadata)
    {
        var pdfBytes = await _libreOffice.ConvertToPdfAsync(
            file.Content, metadata.ContentType);

        var pages = new List<PreviewPage>();
        var pageCount = await _libreOffice.GetPageCountAsync(pdfBytes);
        for (int i = 1; i <= pageCount; i++)
        {
            var pageImage = await _libreOffice.RenderPageAsync(pdfBytes, i, width: 1200);
            var thumbImage = await _imageProcessor.ResizeAsync(pageImage, width: 300);

            var fullPath = await _blobStorage.UploadAsync(
                $"previews/{metadata.DocumentId}/page-{i}.png", pageImage);
            var thumbPath = await _blobStorage.UploadAsync(
                $"previews/{metadata.DocumentId}/thumb-{i}.png", thumbImage);

            pages.Add(new PreviewPage
            {
                PageNumber = i,
                FullImageUrl = fullPath,
                ThumbnailUrl = thumbPath
            });
        }

        return new PreviewResult
        {
            Type = PreviewType.Paginated,
            Pages = pages,
            TotalPages = pageCount
        };
    }
}
Performance Consideration: Preview conversion is CPU-intensive, especially for large Office documents. We cap the conversion at 500 pages and 100 MB. For larger documents, we generate previews for the first 50 pages and display a "Preview limited to first 50 pages" notice. Conversion jobs are processed by a dedicated worker pool with auto-scaling based on queue depth.

9. Co-Authoring and Real-Time Editing

Why Co-Authoring Is Hard

Real-time co-authoring transforms a DMS from a static repository into a living collaboration platform. Multiple users editing the same document simultaneously must see each other's changes in near real-time without overwriting each other's work. This is fundamentally a distributed systems problem: each client holds a local copy of the document state, and changes must be merged deterministically across all connected clients. The two dominant approaches are Operational Transformation (OT) and Conflict-free Replicated Data Types (CRDTs).

OT was pioneered by Google Docs and works by transforming operations on the server so they can be applied in a consistent order regardless of the network path each operation takes. CRDTs, used by Figma and newer collaborative editors, use data structures that are mathematically guaranteed to converge without a central server. For enterprise DMS, we use OT because it offers stronger guarantees around cursor position and formatting state, and integrates well with existing Office file formats.

sequenceDiagram participant A as Alice (Browser) participant S as Collaboration Service participant B as Bob (Browser) participant DB as Document DB A->>S: Connect(docId, userId) B->>S: Connect(docId, userId) S-->>A: Connected (presence: Alice, Bob) S-->>B: Connected (presence: Alice, Bob) A->>S: Insert("Hello", position=0) B->>S: Insert("World", position=0) S->>S: Transform operations S-->>A: RemoteInsert("World", position=0) S-->>B: RemoteInsert("Hello", position=5) S->>DB: Persist merged state

Collaboration Service Architecture

C#
public class CollaborationSession
{
    public Guid DocumentId { get; set; }
    public List<ConnectedUser> ConnectedUsers { get; set; }
    public List<Operation> OperationHistory { get; set; }
    public int LastAppliedRevision { get; set; }
    public string BaseContent { get; set; }
}

public class Operation
{
    public string OperationId { get; set; }
    public Guid UserId { get; set; }
    public int Revision { get; set; }
    public OperationType Type { get; set; }
    public int Position { get; set; }
    public string Content { get; set; }
    public DateTime Timestamp { get; set; }
}

public enum OperationType
{
    Insert,
    Delete,
    Format,
    Replace
}

public interface ICollaborationService
{
    Task<CollaborationSession> JoinSessionAsync(
        Guid documentId, Guid userId);
    Task<OperationResult> SubmitOperationAsync(
        Guid documentId, Guid userId, Operation op);
    Task LeaveSessionAsync(Guid documentId, Guid userId);
    IAsyncEnumerable<Operation> SubscribeToChangesAsync(
        Guid documentId, Guid userId, CancellationToken ct);
}

Conflict Resolution Strategy

When two users edit the same paragraph simultaneously, the OT server receives two operations that may have overlapping positions. The transform function rewrites the second operation's position to account for the first operation's effect. For example, if Alice inserts "Hello " at position 0, and Bob inserts "World" at position 0, the server transforms Bob's operation to insert "World" at position 6 (after "Hello "). Both clients then see "Hello World" in the same order.

For binary files (Excel, PowerPoint), co-authoring uses cell-level or slide-level locking rather than character-level OT. Only the specific cell or slide a user is editing is locked; the rest of the document remains editable by others. This is how SharePoint handles Excel co-authoring in practice.

Scale Consideration: Each active collaboration session holds state in memory on the Collaboration Service instance. With 50 concurrent editors on a single document, the server maintains 50 WebSocket connections and replays operations against the base document. We limit concurrent editors to 100 per document. Beyond that, we recommend the document be restructured into smaller work items.

10. Document Workflows — Approval, Review, Signature

Workflow Engine Design

Document workflows encode business processes as state machines attached to documents. A typical contract might go through Draft → Legal Review → Manager Approval → eSignature → Published. Each transition has conditions, assignees, deadlines, and escalation rules. The workflow engine must support branching logic (parallel approvals), delegation, rework loops, and timeout handling.

stateDiagram-v2 [*] --> Draft Draft --> LegalReview : Submit LegalReview --> ManagerApproval : Legal Approved LegalReview --> Draft : Rework Requested ManagerApproval --> ExecutiveApproval : Value > $100K ManagerApproval --> eSignature : Manager Approved ExecutiveApproval --> eSignature : Executive Approved ManagerApproval --> Draft : Rejected ExecutiveApproval --> Draft : Rejected eSignature --> Published : All Signatures
C#
public class WorkflowDefinition
{
    public Guid WorkflowId { get; set; }
    public string Name { get; set; }
    public List<WorkflowState> States { get; set; }
    public List<WorkflowTransition> Transitions { get; set; }
    public List<WorkflowVariable> Variables { get; set; }
    public TimeSpan? DefaultTimeout { get; set; }
}

public class WorkflowTransition
{
    public string TransitionId { get; set; }
    public string FromState { get; set; }
    public string ToState { get; set; }
    public string TriggerAction { get; set; }
    public List<TransitionCondition> Conditions { get; set; }
    public List<AssigneeRule> Assignees { get; set; }
    public TimeSpan? Timeout { get; set; }
    public string TimeoutAction { get; set; }
}

public class WorkflowInstance
{
    public Guid InstanceId { get; set; }
    public Guid DocumentId { get; set; }
    public Guid WorkflowDefinitionId { get; set; }
    public string CurrentState { get; set; }
    public List<WorkflowAction> History { get; set; }
    public DateTime CreatedAt { get; set; }
    public DateTime? CompletedAt { get; set; }
}

public class WorkflowAction
{
    public Guid ActionId { get; set; }
    public Guid InstanceId { get; set; }
    public string TransitionId { get; set; }
    public Guid PerformedBy { get; set; }
    public string Comment { get; set; }
    public DateTime PerformedAt { get; set; }
}

Approval Chain Patterns

PatternDescriptionUse Case
SequentialEach approver acts in order; document advances on approvalContract signing, policy approval
ParallelMultiple approvers act independently; all must approveHR onboarding checklist, multi-dept sign-off
Majority VoteDocument advances when more than N% of approvers approveCommittee decisions, board resolutions
Conditional BranchNext approver depends on document metadata (e.g., contract value)Tiered approval thresholds
DelegationApprover delegates to another user (manager on leave)Availability-aware routing
EscalationIf no action within SLA, escalate to next levelTime-sensitive approvals
Integration Point: Workflows integrate with external eSignature services (DocuSign, Adobe Sign) via webhooks. When the eSignature step is reached, the workflow engine calls the external API to create a signature request, then listens for a callback webhook to transition the document to the next state.

11. Retention Policies and Archival

Why Retention Matters

Every document has a lifecycle. Retention policies define how long a document must be kept, what happens when that period expires, and whether the document is subject to legal hold. Organizations in regulated industries (healthcare, finance, legal) face fines for destroying documents too early, and face liability for keeping them too long (privacy violations). An automated retention system removes human error from this equation.

C#
public class RetentionPolicy
{
    public Guid PolicyId { get; set; }
    public Guid TenantId { get; set; }
    public string Name { get; set; }
    public RetentionTrigger Trigger { get; set; }
    public int RetentionDays { get; set; }
    public ExpirationAction Action { get; set; }
    public bool AllowExtension { get; set; }
    public int MaxExtensionDays { get; set; }
    public List<LitigationHold> ActiveHolds { get; set; }
}

public enum RetentionTrigger
{
    DocumentCreated,
    DocumentModified,
    DocumentApproved,
    ContractExpired,
    EmployeeTerminated,
    CustomDateField
}

public enum ExpirationAction
{
    Delete,
    Archive,
    NotifyOwner,
    ReviewRequired,
    MoveToRecordCenter
}

Legal Hold Mechanism

A legal hold overrides ALL retention policies and cleanup operations. When a document is placed under legal hold (typically due to pending litigation or regulatory investigation), it cannot be deleted, modified, or moved to archive — regardless of its retention period. The hold is stored as a separate entity linked to the document, and every delete/archive operation checks for active holds before proceeding.

SQL
CREATE TABLE LitigationHolds (
    HoldId UNIQUEIDENTIFIER PRIMARY KEY,
    TenantId UNIQUEIDENTIFIER NOT NULL,
    CaseNumber NVARCHAR(100) NOT NULL,
    CaseName NVARCHAR(500) NOT NULL,
    Description NVARCHAR(MAX),
    CreatedBy UNIQUEIDENTIFIER NOT NULL,
    CreatedAt DATETIME2 NOT NULL,
    ExpiresAt DATETIME2 NULL,
    IsActive BIT DEFAULT 1
);

CREATE TABLE DocumentHolds (
    DocumentId UNIQUEIDENTIFIER NOT NULL,
    HoldId UNIQUEIDENTIFIER NOT NULL,
    PlacedBy UNIQUEIDENTIFIER NOT NULL,
    PlacedAt DATETIME2 NOT NULL,
    RemovedAt DATETIME2 NULL,
    PRIMARY KEY (DocumentId, HoldId)
);

Archival Pipeline

The retention service runs a daily batch job that evaluates every document against applicable retention policies. Documents past their retention period (and not under legal hold) trigger their configured expiration action. The archival pipeline moves blobs from Hot/Cool tier to Cold/Archive tier, generates a deletion schedule for documents that must be permanently destroyed, and logs every retention action for audit purposes.

Interview Tip: Be prepared to explain the difference between "retention" and "destruction." Retention ensures documents are kept for a minimum period. Destruction (or disposition) ensures documents are permanently deleted after that period. Many organizations underestimate the complexity of disposition — it requires verifying that no legal hold exists, that the retention period has truly elapsed, and that no other regulatory requirement overrides the policy.

12. Sensitivity Labels and Classification

Classification Framework

Sensitivity labels define the confidentiality level of a document and drive downstream enforcement — encryption, access restrictions, watermarking, and DLP policies. A "Restricted" document is encrypted at rest and in transit, accessible only to explicitly named users, watermarked with the viewer's identity, and logged in a separate audit trail. A "Public" document has no restrictions and may be cached on CDN edges worldwide.

C#
public class SensitivityLabelDefinition
{
    public SensitivityLabel Label { get; set; }
    public string DisplayName { get; set; }
    public string Description { get; set; }
    public bool EncryptAtRest { get; set; }
    public bool EncryptInTransit { get; set; }
    public bool ApplyWatermark { get; set; }
    public bool BlockExternalSharing { get; set; }
    public bool RequireMfa { get; set; }
    public int MaxAccessLogRetentionDays { get; set; }
    public List<AccessControlRule> AccessRules { get; set; }
}

public class AccessControlRule
{
    public string PrincipalType { get; set; }
    public string PrincipalId { get; set; }
    public PermissionLevel Permission { get; set; }
    public bool RequiresApproval { get; set; }
}
LabelEncryptionExternal SharingWatermarkAuditExample
PublicNoAllowedNoStandardMarketing brochure, press release
InternalIn-transitBlockedNoStandardInternal memo, team handbook
ConfidentialAt-rest + In-transitBlockedYesExtended (1 year)Financial reports, HR records
RestrictedAES-256 + Key VaultBlockedYes (dynamic)IndefiniteM&A docs, trade secrets, legal strategy

Auto-Classification with Machine Learning

Manual classification is unreliable — users forget, misclassify, or skip it. We deploy a classification model trained on labeled document samples that analyzes content patterns, metadata, and file characteristics to predict the appropriate sensitivity label. The model runs as part of the indexing pipeline and pre-populates the label, which the user can then confirm or override. For "Restricted" labels, we require manual confirmation — the system never auto-assigns the highest classification level.

Interview Tip: Sensitivity labels must be enforced at the storage layer, not just the UI layer. If a user downloads a "Confidential" document and shares it externally, the encryption key (managed by Azure Key Vault or AWS KMS) ensures the file is unreadable without proper authorization. This is how Microsoft Information Protection works — labels drive encryption, not just UI badges.

13. Access Control — ACLs, Inheritance, Sharing Links

Permission Model

Enterprise DMS requires hierarchical access control where permissions are inherited down the folder tree but can be broken at any level. A user with "Contribute" access on a library automatically gets "Contribute" on all subfolders — unless a subfolder has broken inheritance and explicitly grants or denies access to a different set of users.

C#
public class AccessControlEntry
{
    public Guid AceId { get; set; }
    public Guid ResourceId { get; set; }
    public ResourceType ResourceType { get; set; }
    public PrincipalType PrincipalType { get; set; }
    public Guid PrincipalId { get; set; }
    public PermissionLevel Permission { get; set; }
    public bool IsInherited { get; set; }
    public bool IsDenied { get; set; }
    public DateTime GrantedAt { get; set; }
    public Guid GrantedBy { get; set; }
}

public enum PermissionLevel
{
    None = 0,
    Read = 1,
    Contribute = 2,
    Edit = 3,
    Design = 4,
    FullControl = 5,
    Owner = 6
}

Permission Evaluation Algorithm

When a user requests access to a document, the system walks up the folder hierarchy collecting all applicable ACLs (both inherited and explicit), evaluates explicit denies first (deny always wins), then checks for the required permission level. The result is cached in Redis with a TTL of 5 minutes to avoid repeated tree walks on every request.

graph TD ROOT["Library Root
ACL: [All Staff: Read]"] --> F1["Folder: Contracts
ACL: [Legal Team: FullControl]
Inherits: Read from Root"] F1 --> F2["Subfolder: Active
ACL: [Partners: Contribute]
Inherits: FullControl from Contracts"] F1 --> F3["Subfolder: Expired
ACL: BREAK INHERITANCE
[Archivists: Read]"] F2 --> D1["Document: NDA.pdf
Inherits: Contribute from Active"] style F3 fill:#f85149,color:#fff style D1 fill:#3fb950,color:#fff

External Sharing Links

Sharing links allow external users to access specific documents without a full account. Links can be scoped to a single document, a folder, or an entire library. Each link has an expiration date, optional password protection, and an access limit (number of unique visitors). All access through sharing links is logged to the audit trail with the link creator's identity.

C#
public class SharingLink
{
    public Guid LinkId { get; set; }
    public Guid DocumentId { get; set; }
    public Guid CreatedBy { get; set; }
    public SharingLinkType Type { get; set; }
    public PermissionLevel Permission { get; set; }
    public DateTime ExpiresAt { get; set; }
    public string PasswordHash { get; set; }
    public int? MaxAccessCount { get; set; }
    public int CurrentAccessCount { get; set; }
    public bool IsRevoked { get; set; }
    public List<SharingLinkAccess> AccessLog { get; set; }
}

public enum SharingLinkType
{
    SpecificPeople,
    AnyoneWithLink,
    OrganizationOnly
}
Interview Tip: The permission evaluation must be performant — it is on the critical path for every document access. We precompute the effective permission set for each user on each library and cache it. When an ACL changes, we invalidate the cache for affected users and libraries. For large libraries with thousands of ACLs, we use a permission matrix stored in a graph database (Neo4j) that supports fast traversal queries.

14. Audit Trail and Activity Logging

Immutable Audit Log

An enterprise DMS must produce an immutable, tamper-evident log of every significant action: who accessed which document, when, from where, and what they did. This is non-negotiable for compliance with SOX, HIPAA, GDPR, and FedRAMP. The audit log is append-only — no updates or deletes are permitted — and is stored in a write-once, read-many (WORM) storage tier.

C#
public class AuditEvent
{
    public long EventId { get; set; }
    public Guid TenantId { get; set; }
    public Guid UserId { get; set; }
    public string UserEmail { get; set; }
    public string UserAgent { get; set; }
    public string IpAddress { get; set; }
    public AuditEventType EventType { get; set; }
    public Guid? DocumentId { get; set; }
    public string DocumentPath { get; set; }
    public string Action { get; set; }
    public DateTime Timestamp { get; set; }
    public Dictionary<string, object> Details { get; set; }
    public string PreviousHash { get; set; }
    public string EventHash { get; set; }
}

public enum AuditEventType
{
    DocumentCreated,
    DocumentViewed,
    DocumentDownloaded,
    DocumentModified,
    DocumentDeleted,
    DocumentShared,
    PermissionChanged,
    WorkflowAction,
    LabelChanged,
    SearchPerformed,
    ExportRequested,
    LegalHoldPlaced,
    LegalHoldRemoved,
    RetentionAction
}

Hash Chain for Tamper Detection

Each audit event includes the SHA-256 hash of the previous event, forming a blockchain-like chain. If anyone modifies a historical event, the hash chain breaks and the tampering is detectable. Verification runs daily as a background job that re-hashes every event and compares it to the stored hash.

Compliance Guarantee: Audit events are written to three destinations simultaneously: (1) the primary audit database (Cosmos DB with time-to-live for query performance), (2) a WORM-compliant blob archive (Azure Immutable Blob Storage), and (3) an optional SIEM connector (Splunk, Sentinel) for real-time security monitoring. Even if one storage system is compromised, the other two serve as independent verification sources.

15. Document Templates

Template Engine

Document templates allow users to create new documents pre-populated with structure, formatting, and dynamic fields. The template engine uses the Open XML SDK for Office documents and Handlebars/Razor for HTML templates. Templates are stored as versioned blobs and can include conditional sections, repeating tables, and field calculations.

C#
public class DocumentTemplate
{
    public Guid TemplateId { get; set; }
    public Guid TenantId { get; set; }
    public string Name { get; set; }
    public string Description { get; set; }
    public Guid ContentTypeId { get; set; }
    public string TemplateBlobPath { get; set; }
    public string OutputMimeType { get; set; }
    public List<TemplateField> Fields { get; set; }
    public int Version { get; set; }
    public bool IsDefault { get; set; }
}

public class TemplateField
{
    public string FieldName { get; set; }
    public string DisplayName { get; set; }
    public TemplateFieldType Type { get; set; }
    public bool IsRequired { get; set; }
    public string Placeholder { get; set; }
    public string DefaultValue { get; set; }
    public string Pattern { get; set; }
}

When a user creates a document from a template, they fill in a form with the required field values. The engine replaces placeholders in the template document, evaluates any conditional sections, and generates a new document blob. The generated document inherits the content type, default workflow, and retention policy of the template.

16. Metadata-Driven Views

Flexible View System

Views allow users to browse library contents through different lenses — a list view sorted by modified date, a gallery view with thumbnails, a calendar view grouped by due date, or a Kanban board grouped by status. Views are stored as query definitions and rendered client-side, with search results from Elasticsearch providing the backing data.

C#
public class LibraryView
{
    public Guid ViewId { get; set; }
    public Guid LibraryId { get; set; }
    public string Name { get; set; }
    public ViewType Type { get; set; }
    public List<ViewFilter> Filters { get; set; }
    public List<ViewSort> SortColumns { get; set; }
    public List<ViewGroup> GroupBy { get; set; }
    public List<ViewColumn> Columns { get; set; }
    public bool IsDefault { get; set; }
    public bool IsPublic { get; set; }
    public Guid CreatedBy { get; set; }
}

public enum ViewType
{
    List,
    Gallery,
    Calendar,
    Board,
    Timeline,
    Custom
}

public class ViewColumn
{
    public string FieldName { get; set; }
    public string DisplayName { get; set; }
    public int Order { get; set; }
    public int? Width { get; set; }
    public bool IsVisible { get; set; }
    public string Format { get; set; }
}

Views are resolved to Elasticsearch queries at runtime. A "Board" view grouped by document status sends a terms aggregation query to Elasticsearch and renders the results in columns. A "Calendar" view sends a date histogram aggregation. This approach avoids maintaining a separate query engine — Elasticsearch's aggregation framework handles all view types natively.

17. Content Deduplication

Hash-Based Deduplication

Enterprises often have many copies of the same document — emailed versions, backup copies, and renamed duplicates. Content deduplication eliminates redundant storage by detecting identical files and storing only one blob, referenced by multiple metadata records. We use SHA-256 content hashing at the file level and optionally at the block level for large documents.

C#
public class DeduplicationService
{
    private readonly IDatabase _db;
    private readonly IBlobStorage _blob;

    public async Task<DeduplicationResult> CheckAndDeduplicateAsync(
        Guid tenantId, string contentHash, string blobPath, long fileSize)
    {
        var existing = await _db.QueryAsync<ContentHash>(
            "SELECT * FROM ContentHashes " +
            "WHERE TenantId = @tenant AND Hash = @hash",
            new { tenant = tenantId, hash = contentHash });

        if (existing != null)
        {
            await _blob.DeleteAsync(blobPath);
            return new DeduplicationResult
            {
                IsDuplicate = true,
                OriginalDocumentId = existing.DocumentId,
                StorageSavedBytes = fileSize
            };
        }

        await _db.ExecuteAsync(
            "INSERT INTO ContentHashes " +
            "(TenantId, Hash, BlobPath, FileSize, FirstDocumentId, CreatedAt) " +
            "VALUES (@tenant, @hash, @path, @size, @docId, @now)",
            new
            {
                tenant = tenantId,
                hash = contentHash,
                path = blobPath,
                size = fileSize,
                docId = Guid.NewGuid(),
                now = DateTime.UtcNow
            });

        return new DeduplicationResult { IsDuplicate = false };
    }
}

Deduplication Metrics

MetricTypical ValueImpact
Duplicate rate (enterprise)15-30% of uploads15-30% storage cost reduction
Average storage saved per tenant2-5 TB/year$400-$1,000/month in Azure Blob costs
Hash computation time<50ms for 100 MB fileNegligible latency impact
False positive rate0% (cryptographic hash)No risk of incorrect deduplication
Design Note: Deduplication only works within a single tenant. We never deduplicate across tenants — even if the content hash is identical — because different tenants may have different compliance and encryption requirements. A document marked "Confidential" in Tenant A must not be accessible to Tenant B even if the content is identical.

18. Large File Handling — Chunked and Resumable Uploads

Chunked Upload Protocol

Enterprise users upload large files — CAD drawings (500 MB), video files (2 GB), forensic images (250 GB). A single HTTP PUT cannot handle these reliably. We implement a chunked upload protocol where the client splits the file into 4 MB blocks, uploads each block independently, and the server assembles them into the final blob. If the connection drops, the client can resume from the last successful block.

sequenceDiagram participant C as Client participant API as API Gateway participant DS as Document Service participant BLOB as Blob Storage participant REDIS as Redis C->>API: POST /upload/initiate {fileName, fileSize, md5} API->>DS: CreateUploadSession() DS->>REDIS: Store session (TTL: 24h) DS-->>API: sessionId, chunkSize, totalChunks API-->>C: 201 Created {sessionId} loop Each chunk C->>BLOB: PUT /blobs/{sessionId}/chunk/{n} BLOB-->>C: 200 OK {bytesReceived} C->>API: POST /upload/progress {sessionId, chunkN} end C->>API: POST /upload/complete {sessionId} API->>DS: CommitUpload(sessionId) DS->>BLOB: CommitBlockList(sessionId) DS->>REDIS: Delete session DS-->>API: Document metadata API-->>C: 201 Created {documentId}
C#
public class UploadSession
{
    public Guid SessionId { get; set; }
    public Guid TenantId { get; set; }
    public Guid UserId { get; set; }
    public string FileName { get; set; }
    public string ContentType { get; set; }
    public long TotalFileSize { get; set; }
    public int ChunkSize { get; set; } = 4 * 1024 * 1024;
    public int TotalChunks => (int)Math.Ceiling(
        (double)TotalFileSize / ChunkSize);
    public HashSet<int> CompletedChunks { get; set; } = new();
    public DateTime CreatedAt { get; set; }
    public DateTime ExpiresAt { get; set; }
    public UploadSessionStatus Status { get; set; }
}
Interview Tip: Discuss how you handle chunk verification. Each chunk upload includes an MD5 checksum that the server validates before acknowledging. The final commit operation verifies the overall file hash to ensure integrity end-to-end. Without chunk-level verification, a corrupted chunk would only be detected after the entire upload completes, wasting bandwidth on retry.

19. Barcode, QR Code, and OCR Processing

OCR Pipeline

Many enterprise documents are scanned paper records — contracts, invoices, medical records, legal filings. OCR (Optical Character Recognition) extracts text from these images, making them searchable and enabling automated data extraction. Our OCR pipeline uses Tesseract for general-purpose recognition and Azure Computer Vision for high-accuracy scenarios.

C#
public class OcrProcessingService
{
    private readonly ITesseractEngine _tesseract;
    private readonly IAzureComputerVision _azureVision;
    private readonly IPdfRenderer _pdfRenderer;

    public async Task<OcrResult> ProcessDocumentAsync(
        Stream fileStream, string mimeType, OcrQuality quality)
    {
        var pages = new List<OcrPage>();

        if (mimeType == "application/pdf")
        {
            var images = await _pdfRenderer.RenderPagesToImagesAsync(
                fileStream, dpi: 300);
            foreach (var img in images)
            {
                var ocrPage = quality == OcrQuality.High
                    ? await _azureVision.RecognizeTextAsync(img)
                    : await _tesseract.RecognizeAsync(img);
                pages.Add(ocrPage);
            }
        }
        else if (mimeType.StartsWith("image/"))
        {
            var ocrResult = quality == OcrQuality.High
                ? await _azureVision.RecognizeTextAsync(fileStream)
                : await _tesseract.RecognizeAsync(fileStream);
            pages.Add(ocrResult);
        }

        return new OcrResult
        {
            FullText = string.Join("\n\n", pages.Select(p => p.Text)),
            Confidence = pages.Average(p => p.Confidence),
            DetectedLanguage = pages.First().DetectedLanguage,
            WordCount = pages.Sum(p => p.Words.Count),
            Pages = pages
        };
    }
}

Barcode and QR Code Scanning

Barcodes and QR codes on scanned documents serve as machine-readable document identifiers. The barcode service extracts codes, maps them to document metadata (e.g., a barcode on a contract might encode the contract number), and uses this mapping to auto-populate metadata fields during upload. We support Code 128, Code 39, QR Code, Data Matrix, and PDF417 formats.

OCR EngineAccuracySpeedCostBest For
Tesseract (local)85-95%Fast (local GPU)FreeStandard text documents
Azure Computer Vision95-99%Medium (API call)$1/1000 pagesHandwriting, low-quality scans
Google Cloud Vision94-98%Medium (API call)$1.50/1000 pagesMulti-language documents

20. Compliance — Records Management, Legal Hold, eDiscovery

Records Management

Records management goes beyond simple retention. A "record" is a document that has been officially declared as a business record — it cannot be modified, only appended with a reason code. Records management requires a chain of custody (who declared it, when, under what authority), a records schedule (when it must be destroyed), and an audit trail of all access.

C#
public class DocumentRecord
{
    public Guid RecordId { get; set; }
    public Guid DocumentId { get; set; }
    public string RecordCategory { get; set; }
    public string RecordSchedule { get; set; }
    public DateTime DeclaredAt { get; set; }
    public Guid DeclaredBy { get; set; }
    public DateTime RetentionExpiry { get; set; }
    public DestructionStatus DestructionStatus { get; set; }
    public List<ChainOfCustodyEntry> CustodyChain { get; set; }
}

public class ChainOfCustodyEntry
{
    public DateTime Timestamp { get; set; }
    public Guid UserId { get; set; }
    public string Action { get; set; }
    public string Reason { get; set; }
    public string Location { get; set; }
}

eDiscovery

eDiscovery (electronic discovery) is the process of identifying, preserving, and producing documents relevant to legal proceedings. The eDiscovery module allows legal teams to create cases, define search queries (custodian-based, date-range, keyword, content-type), review search results, and export matched documents with a complete chain of custody and metadata for court submission.

graph TD CASE["Create eDiscovery Case"] --> CUSTODIAN["Identify Custodians"] CUSTODIAN --> QUERY["Define Search Query"] QUERY --> HOLD["Place Legal Hold"] HOLD --> SEARCH["Execute Search"] SEARCH --> REVIEW["Attorney Review"] REVIEW --> EXPORT["Export for Production"] EXPORT --> CERTIFY["Certify Completeness"]
Interview Tip: The key challenge in eDiscovery is scope management. A single case might involve 10 custodians, 5 years of content, and millions of documents. The system must support iterative search refinement — narrowing from millions to thousands to hundreds of relevant documents — without requiring the entire dataset to be downloaded. This is why search runs server-side and only the metadata and snippets are returned to the review interface.

21. Security — Encryption at Rest and In Transit

Encryption Architecture

All document content is encrypted at rest using AES-256. For standard documents, we use platform-managed keys (Azure/S3 default encryption). For "Confidential" and "Restricted" documents, we use customer-managed keys stored in Azure Key Vault or AWS KMS, giving the enterprise full control over key rotation and revocation. In transit, all communication uses TLS 1.3 with certificate pinning on mobile clients.

C#
public class EncryptionService
{
    private readonly IKeyVaultClient _keyVault;
    private readonly IKeyResolver _keyResolver;

    public async Task<EncryptedContent> EncryptAsync(
        Stream content, SensitivityLabel label, Guid tenantId)
    {
        var keyId = label switch
        {
            SensitivityLabel.Restricted =>
                await _keyVault.GetKeyAsync($"restricted-{tenantId}"),
            SensitivityLabel.Confidential =>
                await _keyVault.GetKeyAsync($"confidential-{tenantId}"),
            _ => PlatformManagedKey
        };

        var encrypted = await Aes256.EncryptAsync(
            content, keyId.Value, keyId.IV);
        return new EncryptedContent
        {
            EncryptedStream = encrypted,
            KeyId = keyId.Kid,
            Algorithm = "AES-256-GCM",
            EncryptedAt = DateTime.UtcNow
        };
    }
}

Zero-Trust Architecture

Every service-to-service call within our architecture is mutually authenticated via mTLS (mutual TLS). The API Gateway validates JWT tokens with short expiry (15 minutes) and issues refresh tokens. Sensitive operations (deleting documents, changing permissions, exporting data) require step-up authentication — a second factor challenge — even if the user is already authenticated. All service identities are managed through a service mesh (Istio) that enforces authorization policies at the network layer.

22. Monitoring, Logging, and Observability

Observability Stack

We instrument every service with OpenTelemetry for distributed tracing, Prometheus for metrics, and structured logging to a centralized sink (ELK or Datadog). The key metrics we track include:

MetricTargetAlert Threshold
Upload success rate>99.9%<99.5%
Search latency (p99)<2 seconds>3 seconds
Preview generation time<10 seconds>30 seconds
OCR processing queue depth<1,000>10,000
Audit log write latency<50ms>200ms
Active collaboration sessionsTrackedAlert at 10K concurrent
Workflow completion rate>95%<80%
Storage utilization per tenant<90% quota>85%
Interview Tip: Discuss how you would detect a silent data corruption scenario. By comparing blob hashes on download against the stored content hash, we can detect bit-rot or unauthorized modifications. A periodic background job samples random blobs, verifies their integrity, and alerts if mismatches exceed a threshold. This is the "data durability" check that backs up the 11-nines durability guarantee.

23. API Design

RESTful API Surface

The DMS exposes a comprehensive RESTful API following OpenAPI 3.0 specification. All endpoints are versioned (v1, v2), accept JSON request bodies, and return standard HTTP status codes. Authentication is via Bearer token (JWT) in the Authorization header.

MethodEndpointDescription
POST/api/v1/documents/uploadUpload a new document
GET/api/v1/documents/{id}Get document metadata
GET/api/v1/documents/{id}/contentDownload document binary
PUT/api/v1/documents/{id}Update document metadata
DELETE/api/v1/documents/{id}Soft-delete a document
POST/api/v1/documents/{id}/versionsCreate a new version
GET/api/v1/documents/{id}/versionsList all versions
POST/api/v1/documents/{id}/checkoutCheck out for editing
POST/api/v1/documents/{id}/checkinCheck in with new version
POST/api/v1/documents/{id}/shareCreate a sharing link
POST/api/v1/searchFull-text search across documents
GET/api/v1/documents/{id}/previewGet inline preview
POST/api/v1/documents/{id}/workflow/startStart a workflow
POST/api/v1/workflows/{id}/actionPerform workflow action
GET/api/v1/libraries/{id}/viewsList views for a library
POST/api/v1/discovery/casesCreate eDiscovery case
GET/api/v1/audit/eventsQuery audit log

API Request/Response Example

JSON
// POST /api/v1/documents/upload
// Request (multipart/form-data):
//   file: [binary stream]
//   metadata: {
//     "libraryId": "a1b2c3d4-...",
//     "parentFolderId": "e5f6g7h8-...",
//     "contentTypeId": "i9j0k1l2-...",
//     "metadata": {
//       "ContractNumber": "MSA-2026-0042",
//       "PartyName": "Acme Corp",
//       "EffectiveDate": "2026-07-01"
//     },
//     "sensitivity": "Confidential"
//   }

// Response: 201 Created
{
  "documentId": "m3n4o5p6-...",
  "fileName": "Service-Agreement-Acme.pdf",
  "version": "1.0",
  "status": "Active",
  "size": 2048576,
  "sensitivity": "Confidential",
  "createdBy": "user-123",
  "createdAt": "2026-07-13T10:30:00Z",
  "previewAvailable": true,
  "searchIndexed": true
}

Pagination and Rate Limiting

All list endpoints support cursor-based pagination (not offset-based) for consistent results when data is changing. Rate limits are applied per-tenant at the API Gateway: 10,000 requests/minute for standard endpoints, 1,000 requests/minute for search, and 100 requests/minute for bulk export operations. Rate limit headers (X-RateLimit-Remaining, X-RateLimit-Reset) are included in every response.

C#
[ApiController]
[Route("api/v1/[controller]")]
[Authorize]
public class DocumentsController : ControllerBase
{
    private readonly IDocumentService _documents;
    private readonly IAuditService _audit;

    [HttpPost("upload")]
    [RequestSizeLimit(256L * 1024 * 1024 * 1024)]
    public async Task<ActionResult<DocumentResponse>> Upload(
        IFormFile file, [FromForm] UploadMetadataRequest metadata)
    {
        var userId = User.GetUserId();
        var tenantId = User.GetTenantId();

        var result = await _documents.UploadAsync(
            file.OpenReadStream(), metadata, userId, tenantId);

        await _audit.LogAsync(new AuditEvent
        {
            EventType = AuditEventType.DocumentCreated,
            UserId = userId,
            DocumentId = result.DocumentId,
            Action = "Upload",
            Details = new Dictionary<string, object>
            {
                ["fileName"] = file.FileName,
                ["fileSize"] = file.Length,
                ["sensitivity"] = metadata.Sensitivity
            }
        });

        return CreatedAtAction(
            nameof(GetDocument),
            new { id = result.DocumentId },
            result);
    }
}

24. Testing Strategy

Multi-Layer Testing Approach

A production DMS demands rigorous testing across every layer. We follow the testing pyramid with heavy emphasis on integration and contract tests, given the number of external dependencies (blob storage, Elasticsearch, Key Vault, Identity Provider).

LayerToolCoverage TargetWhat We Test
Unit TestsxUnit + Moq80%+Business logic, permission evaluation, version numbering, hash computation
Integration TestsTestcontainersAll service boundariesDatabase queries, blob storage operations, Elasticsearch indexing
Contract TestsPactAll API consumersAPI request/response schemas between frontend and backend
E2E TestsPlaywrightCritical user flowsUpload, search, preview, workflow, sharing
Load Testsk6N/A10K concurrent uploads, 50K search queries/min
Chaos TestsChaos MeshN/ABlob storage outage, DB failover, Kafka partition
Security TestsOWASP ZAPOWASP Top 10XSS, CSRF, path traversal, privilege escalation
C#
public class DocumentUploadIntegrationTest : IAsyncLifetime
{
    private readonly PostgreSqlContainer _db;
    private readonly AzuriteContainer _blobStorage;
    private DocumentService _service;

    public async Task InitializeAsync()
    {
        _db = new PostgreSqlBuilder().Build();
        await _db.StartAsync();

        _blobStorage = new AzuriteBuilder().Build();
        await _blobStorage.StartAsync();

        _service = new DocumentService(
            _db.GetConnectionString(),
            _blobStorage.GetBlobServiceClient());
    }

    [Fact]
    public async Task Upload_ShouldCreateBlobAndMetadata()
    {
        var stream = new MemoryStream(
            Encoding.UTF8.GetBytes("test document content"));

        var result = await _service.UploadAsync(
            stream, "test.pdf", "application/pdf",
            tenantId: Guid.NewGuid(),
            userId: Guid.NewGuid());

        Assert.NotNull(result.BlobPath);
        Assert.Equal("test.pdf", result.FileName);
        Assert.True(result.FileSize > 0);

        var metadata = await _service.GetAsync(result.DocumentId);
        Assert.Equal(DocumentStatus.Active, metadata.Status);
    }

    public async Task DisposeAsync()
    {
        await _db.DisposeAsync();
        await _blobStorage.DisposeAsync();
    }
}
Interview Tip: Discuss your strategy for testing permission inheritance. This is one of the most complex behaviors to test correctly. Create a test fixture with a deep folder hierarchy (10+ levels), break inheritance at various points, and verify that permission evaluation returns the correct result for users with different ACL combinations at different levels.

25. Cost Estimation

Infrastructure Cost Breakdown

The following table estimates monthly costs for a mid-size deployment serving 500 enterprises with 2.5 million total users and 5 PB of stored documents. Costs assume Azure pricing in US East region.

ComponentSpecificationMonthly CostNotes
Blob Storage (Hot)1 PB (20% of total)$18,000$0.018/GB/month
Blob Storage (Cool)2 PB (40%)$20,000$0.010/GB/month
Blob Storage (Archive)2 PB (40%)$2,000$0.001/GB/month
Cosmos DB (Metadata)500 TB RU/s, 50 TB data$75,000Multi-master, 400 RU/s per 1 GB
Elasticsearch Cluster200 TB, 120 data nodes$45,000D48s v5 instances
Redis Cache1 TB, 6 nodes$8,000Premium P6 tier
Kafka Cluster6 brokers, 50 TB$12,000Azure Event Hubs
Compute (AKS)80 pods, D16s v5$30,000All microservices combined
Key Vault10,000 operations/day$500Managed HSM for Restricted keys
Cognitive Services (OCR)500K pages/month$500At $1/1000 pages
CDN (Previews)50 TB egress/month$4,000$0.08/GB egress
Monitoring (Datadog)200 hosts$8,000Pro tier
DNS & SSLGlobal DNS + Certificates$500Traffic Manager + App Gateway
Backup & DRCross-region geo-redundancy$15,0002x storage replication cost
Total$238,500

Cost Per User

Total monthly cost: $238,500 / 2.5 million users = $0.095 per user per month. If we price the DMS at $15 per user per month (comparable to SharePoint Online), the gross margin is approximately 99.4%. However, this does not include engineering team costs (20 engineers at $200K average = $333K/month), sales/marketing, or support infrastructure.

Cost Optimization Opportunities: (1) Implementing content deduplication could save 15-30% on blob storage ($6K-$12K/month). (2) Aggressive tiering policies could shift another 10% from Hot to Cool/Cold ($2K-$4K/month). (3) Elasticsearch index optimization (reducing stored fields, using synthetic _source) could reduce the cluster cost by 20% ($9K/month). These optimizations combined could save $17K-$25K/month.

26. Interview Q&A

Question 1: How would you handle a scenario where a user uploads a 200 GB forensic video file?

Answer: The upload is chunked into 4 MB blocks (approximately 51,200 chunks). Each chunk is uploaded via a separate HTTP request to blob storage using a pre-signed URL generated by the Document Service. The upload session is tracked in Redis with a 24-hour TTL. If the client disconnects, it can query the session to determine which chunks were successfully uploaded and resume from the next unuploaded chunk. Each chunk includes an MD5 checksum for integrity verification. The final commit operation verifies the overall file hash. During upload, the client reports progress to the user, and the server emits progress events to a WebSocket channel for real-time status.

Question 2: How do you ensure that search results respect document-level permissions?

Answer: We use Elasticsearch's Document Level Security (DLS) feature. Each document in the search index includes a viewer_ids field containing all user and group IDs with read access. The search query is wrapped with a DLS filter that restricts results to documents where the requesting user's ID appears in viewer_ids. For performance, we also precompute a flattened permission set for each user (updated on ACL changes) and pass it as a runtime field filter. For tenants with fewer than 10,000 documents, we use a shared index with DLS. For larger tenants, we use index-per-tenant isolation, which provides stronger security guarantees and independent shard management.

Question 3: What happens when the approval chain requires 5 approvers but one is on leave for 2 weeks?

Answer: The workflow engine supports three mechanisms: (1) Delegation: The absent approver can pre-delegate to a colleague before going on leave. The delegation record specifies a time range and is automatically applied. (2) Escalation: Each workflow step has a timeout (default: 5 business days). If no action is taken within the timeout, the system escalates to the approver's manager. After 2 escalation levels, the step is auto-routed to a designated fallback approver. (3) Reassignment: A workflow administrator can manually reassign a pending step to another user at any time. All reassignment actions are logged to the audit trail with a reason code.

Question 4: How would you migrate 5 million documents from a legacy file server to this DMS?

Answer: Migration follows a four-phase approach: (1) Discovery: Scan the file server to catalog all documents, their folder structure, NTFS permissions, and file metadata. Generate a migration manifest (CSV/JSON) mapping source paths to target locations in the DMS. (2) Preparation: Create libraries, folders, content types, and ACLs in the DMS based on the manifest. Import the manifest into a migration tracking database. (3) Transfer: Use a parallel upload pipeline (Azure Data Factory or a custom .NET worker) that reads files from the source, computes content hashes, checks for deduplication, and uploads to blob storage. Target throughput: 10,000 files/hour. (4) Verification: For every migrated document, verify the content hash matches, metadata is correct, permissions are applied, and the document is searchable. Generate a migration report with any discrepancies. Estimated timeline for 5 million documents: 3-4 months.

Question 5: How do you prevent a rogue administrator from deleting audit logs?

Answer: Audit logs are write-once. We use Azure Immutable Blob Storage with a time-based retention policy (WORM - Write Once Read Many). Even a Storage Account administrator cannot delete or modify blobs in an immutable container until the retention period expires. Additionally, audit events are simultaneously written to: (1) the primary Cosmos DB audit table (with TTL for query performance), (2) an immutable blob archive, and (3) an external SIEM (Splunk/Sentinel) via a real-time streaming connector. An attacker would need to compromise all three independent storage systems simultaneously to tamper with the audit trail. The daily hash chain verification job would detect any discrepancy within 24 hours.

Question 6: Explain the trade-offs between storing metadata in Cosmos DB versus PostgreSQL.

Answer: Cosmos DB offers global distribution with multi-master replication, automatic sharding, and guaranteed single-digit millisecond reads. It scales horizontally without manual partition management. However, it lacks JOIN operations, has limited query flexibility (no complex aggregations), and costs significantly more per GB. PostgreSQL offers full SQL support including JOINs, CTEs, window functions, and transactions spanning multiple tables. It is better for complex queries (e.g., finding all documents with specific metadata field combinations across folders). The trade-off is that PostgreSQL requires manual sharding and read replicas for horizontal scaling. Our recommendation: use Cosmos DB for the primary document metadata store (high throughput, simple queries), and use PostgreSQL for secondary systems like workflow state, audit logs, and eDiscovery case management where complex queries and transactions are essential.

Question 7: How would you design the real-time notification system for co-authoring?

Answer: Co-authoring notifications use WebSockets for real-time delivery. Each collaboration session maps to a SignalR hub (or a custom WebSocket server). When a user connects to edit a document, they join a SignalR group keyed by document ID. Operations are broadcast to all group members via the hub. Presence information (who is editing, cursor position, selection range) is maintained in Redis with a short TTL (10 seconds, refreshed on each heartbeat). If Redis loses the presence data (e.g., during a failover), clients re-register on the next heartbeat without data loss. The WebSocket server is stateless — any instance can handle any document's session because the authoritative state is in Redis and the operation history is in the database. This allows horizontal scaling of the WebSocket tier behind a load balancer.

Question 8: How do you handle concurrent uploads of the same document by two different users?

Answer: This depends on the library's versioning and checkout policy. Three scenarios: (1) Check-out enabled: User A checks out the document first, creating an exclusive lock. User B sees the document as locked and cannot upload a new version until User A checks in. User B can view the last published version. (2) Check-out disabled (co-authoring): Both users upload independently. The system creates two parallel minor versions (1.1 and 1.2). A merge conflict resolution UI presents both versions to a user with edit permissions, allowing them to choose which to keep or manually merge. (3) Last-write-wins: For non-critical metadata updates, we use optimistic concurrency with ETags. The client sends an If-Match header with the version hash it read. If the hash has changed since the read, the server returns 409 Conflict and the client must re-read and retry.

Question 9: What strategies would you use to reduce Elasticsearch index size for a 10 PB document corpus?

Answer: Several strategies: (1) Stored field reduction: Only store fields needed for display (file_name, author, dates) in Elasticsearch. Full metadata lives in the metadata DB. (2) synthetic _source: Use synthetic source with doc values only, eliminating the stored _source field entirely. (3) Index lifecycle management: Move documents older than 2 years to a cold tier index with fewer replicas. (4) Content truncation: Store only the first 10 KB of extracted text in the index. Full text is retrieved from the original blob on demand for detailed viewing. (5) Field data types: Use keyword type instead of text for fields that are only filtered (not searched). Keyword fields use significantly less disk space. Combined, these strategies can reduce index size by 60-70%.

Question 10: How would you handle a scenario where the DMS must meet FedRAMP High compliance requirements?

Answer: FedRAMP High requires 421 controls across 17 control families. Key technical requirements: (1) All data at rest encrypted with FIPS 140-2 validated modules. (2) All data in transit encrypted with TLS 1.2+ using FIPS-validated libraries. (3) Multi-factor authentication for all administrative access. (4) Continuous monitoring with automated vulnerability scanning. (5) Audit logs retained for 12 months online, 7 years offline. (6) Personnel with access must have background checks. (7) Data must remain in US geography (no cross-border replication). (8) Annual third-party penetration testing. (9) Incident response plan with 1-hour notification SLA. (10) System Security Plan (SSP) documenting all 421 controls. The infrastructure must run on FedRAMP-authorized cloud services (Azure Government or AWS GovCloud). We would deploy a separate instance of the DMS on Azure Government, disable cross-region replication to non-US regions, and configure all Key Vault operations to use HSM-backed keys.