**Clarify requirements**- Users share documents, documents have versions, per-user permissions (read/write/share). Support ~100k active users, ~1M documents.**Normalized PostgreSQL schema (core tables)**sql
-- documents: immutable identity, metadata
CREATE TABLE documents (
id uuid PRIMARY KEY,
owner_id uuid NOT NULL REFERENCES users(id),
latest_version_id uuid REFERENCES document_versions(id),
title text,
created_at timestamptz DEFAULT now()
);
-- document_versions: immutable content blobs
CREATE TABLE document_versions (
id uuid PRIMARY KEY,
document_id uuid NOT NULL REFERENCES documents(id),
content bytea,
version_number int NOT NULL,
created_by uuid REFERENCES users(id),
created_at timestamptz DEFAULT now(),
checksum text
);
-- permissions: per-user ACL
CREATE TABLE document_permissions (
document_id uuid NOT NULL REFERENCES documents(id),
user_id uuid NOT NULL REFERENCES users(id),
role smallint NOT NULL, -- 10=read,20=comment,30=write,40=share,50=owner
PRIMARY KEY (document_id, user_id)
);
-- optimistic concurrency token stored on documents row
ALTER TABLE documents ADD COLUMN etag text;
**API endpoints**- POST /documents — create doc (sets owner, etag)- GET /documents?after=<cursor>&limit=50 — list (keyset pagination by created_at,id)- GET /documents/{id} — returns latest_version + ETag header- GET /documents/{id}/versions?limit=20&after=<version_number>- POST /documents/{id}/versions — create new version (requires write). Returns new ETag- PATCH /documents/{id} — update metadata; must send If-Match: "<etag>" header- POST /documents/{id}/permissions — grant/revoke (owner/share role only)- DELETE /documents/{id} — owner only**Pagination**- Use keyset pagination (created_at, id) for listing to scale; support limit capped (max 100). For versions use version_number desc with after cursor.**Ownership & permission checks**- Every modifying endpoint validates permission via document_permissions.role OR owner check.- Enforce least-privilege: write needed for adding versions, share for changing ACLs.**Optimistic concurrency**- Documents row has etag (e.g., base64 of version_number + checksum + timestamp). API returns ETag header.- Mutating requests must include If-Match header; server compares etag in single UPDATE ... WHERE id=$1 AND etag=$2, returning 412 Precondition Failed if mismatch.- For version creation use transactional insert: verify user permission, insert document_versions, update documents.latest_version_id and etag in same transaction.**Indexes & scaling**- Index documents(created_at,id), document_versions(document_id, version_number DESC), document_permissions(document_id), document_permissions(user_id) for ACL checks.- Use read replicas for GET-heavy traffic; cache ACL results in Redis (short TTL) and invalidate on ACL change.- Store large content in object store (S3) and keep only references/checksums in DB if blobs large.**Trade-offs**- Keyset pagination is fast but not random page numbers. ETag approach avoids locking and scales well; occasional conflicts return 412 which clients should handle by refetch-and-retry.