Requests for comment/Multi-Content Revisions

This page is a full description for "Multi-Content Revisions", an RFC tracked as T107595.

Multi-Content Revision support (also MCR or MuCoRe) allows multiple content "slots" per revisions (comprising multiple "streams" per page). Each slot has a specific role (revision+role are unique), and typically each slot has a different content model. Please refer to the Glossary for a definition of the terms used in this document.

The main objectives of introducing MCR can be summarized as follows: On the technical level, this translates to introducing a level of indirection between the revision and the content object, so multiple content objects can be managed per page revision. The concept of a wiki page changes from a sequence of revisions of a single document to a sequence of revisions of multiple documents (streams of slots). For simplicity and compatibility, each revision must have a "main" slot that corresponds to what is without MCR the singular page content.
 * Allow auxiliary information that is currently embedded in wikitext (e.g. infoboxes, quality assessment, categories, template schema) to be factored out, and managed as a separate document (slot), without losing the shared history and page-level functionality associated with the main content, still allowing the auxiliary data to be edited at the same time as the main content, creating a single revision.
 * Allow auxiliary information that is currently managed on associated pages (e.g. template documentation, gadget styles) to share the edit history and page-level functionality associated with the main content, and allowing the auxiliary data to be edited at the same time as the main content, creating a single revision.
 * Potentially, provide a generic way to associate derived data with a revision (e.g. blame maps).

Introducing MCR support does not change how content is stored (it continues to be treated as blobs, and use the text table or External Storage), although the PHP interface for blob storage will likely need to be refactored. Introducing MCR also does not change how derived index information, such as the imagelinks or page_props table, are managed and used. MCR does change how and where content meta-data is stored (the content model, format, hash, etc).

System Design
A top-down view of the service architecture for MCR support:
 * Application logic uses a RevisionLookup service to access revision (resp page) content. Some of the database level code currently in WikiPage and Revision will move there.
 * Application logic uses a PageUpdateController to create new revisions (to update pages). Much of the update logic currently in WikiPage will go there.
 * The existing Revision class will continue to provide read and write access to the main slot, implemented based on RevisionLookup and PageUpdateController.
 * RevisionLookup uses a RevisionSlotLookup to load revision content meta-data, and a BlobLookup to load the serialized content. A ContentLookup layer may be added between RevisionLookup and RevisionSlotLookup to support structured storage and virtual slots. Access to the revision table may be implemented directly in RevisionLookup, or abstracted into a DAO.
 * PageUpdateController uses a RevisionSlotStore to write revision content meta-data, and a BlobStore to store the serialized content. A ContentStore layer may be added between the PageUpdateController and the BlobStore, to provide structured storage. Access to the revision table may be implemented directly in PageUpdateController, or abstracted into a DAO.
 * BlobStore and BlobLookup are initially implemented based on the text table and the External Storage logic. Much of the logic in Revision that currently implements access to revision content will move here.
 * Note that BlobLoockup and BlobStore will typically be implemented by a single class, as will RevisionSlotLookup and RevisionSlotStore.

Necessary configuration includes:
 * content model per slot role (not for the main slot)
 * blob store per slot role and (as a fallback) per content model.
 * legal slots per namespace (and/or main slot model)
 * rules for combined display and editing of slots (per namespace and/or main slot model)
 * Various compatibility flags to manage the transition phase.

More detailed design documents can be found on the following sub-pages:
 * Content Meta-Data Storage defines a DAO layer for content meta-data. It is the preferred way to access the content table.
 * Blob Storage defines a generic mechanism for storing and retrieving arbitrary data blobs, similar to ExternalStore.
 * Transaction Management specifies a generic transaction management interface.
 * Revision Retrieval Interface defines a (lazy loading) RevisionRecord object that provides access to revision meta-data as well as the Content objects of each slot, based on the Content Meta-Data and Blob Storage interfaces.
 * Page Update Interface uses the Content Meta-Data Storage and Blob Storage components to provide a builder object for new revisions which is responsible for managing the transactional context of the update.
 * The legacy Revision and WikiPage classes uses the Revision Lookup and Page Updater components to retain the legacy interface for the main slot.
 * Rendering and Parser Cache needs to be aware of slots. The web cache will be for a rendering of all slots of the current revision.

External Interface Compatibility
Support for multi-content revisions will have to be integrated into the external interfaces (both UI and API).
 * User Interface
 * Views (view, edit, diff, etc)
 * Web API
 * Dumps (Import/Export)

Implementation Roadmap
The actual scheduling of implementation steps will depend on available resources (that is, programmer time and mind share). For now, this section documents the dependencies of the relevant components.
 * Content Meta-Data Storage is the structural heart of MCR. It can be implemented without any dependencies, though having the new BlobStore service in place would be useful. Introducing the new meta-data storage requires a substantial schema change. All actual MCR functionality is blocked on this being implemented, but preliminary implementations that support only the main slot can be created before content meta-data storage is in place.
 * Blob Storage is a refactoring of existing functionality. It depends on none of the other components. Optional features that can be added at any later time: transaction support, ExternalStore integration, and structured content storage.
 * Transaction Management is highly generic and depends on none of the other components. Other components do not need to implement support for transactions from the start, though introducing such support will mean a breaking change to some method signatures (in particular for BlobStore and RevisionSlotStore).
 * Revision Retrieval Interface depends on content meta-data storage and BlobStore for full MCR functionality. A baseline implementation could however use existing code to provide access the the main slot only.
 * Page Update Interface depends on content meta-data storage and BlobStore for full MCR functionality. A baseline implementation could however use existing code to provide access the the main slot only.
 * Views and web API with MCR support need the high level revision retrieval interface, editing needs the page update interface. Read support for MCR should be enabled before write support. UI support should probably be available before API support.
 * Dumps (Import/Export) with MCR support needs to be available before MCR support is enabled for UI and API.

Use Cases
The ability to manage different independent content objects (documents) on the same page may be useful for a variety of applications, some of which are given below. Note that only a few of the use cases mentioned are actually planned or identified as desirable. Others are just ideas so far.

Primary (user editable) content: Derived (possibly virtual) content: Other:
 * Structured "media info", especially license data, associated with a file description page, in addition to the free form wikitext. Currently stored inline using the template. There is a desire be the reading and multimedia teams to have media info integrated closely with the file description page. This is one of the use cases that drive this proposal.
 * Article quality assessments ("needs love", "featured", etc), see PageAssessments. Currently stored inline in wikitext with templates. There is a desire by the Community Tech team to move this out of wikitext. This is one of the use cases that drive this proposal.
 * Infobox data. Removing the infobox parameters from the wikitext allows the infobox to easily be stripped, or shown separately, or formatted according to the target device. It would also make form-based editing simpler. Currently stored as parametrers of the infobox template, inline in wikitext. There is a desire by both the reading and the editing team to do this. This is one of the use cases that drive this proposal, even though the details are still unclear.
 * Focus area of images, for generating header images for the mobile app. Interest in this by the mobile team was discussed at the 2016 summit.
 * Template schema information (aka template data). Currently stored inline in wikitext using JSON syntax. There appears to be desire from the editing team to have atomic edits for template styles, documentation, and schema. This is one of the use cases that drive this proposal.
 * Template styles: the CSS used by a template would live in a separate slot, but could be edited along with the template. Currently stored in MediaWiki:Common.css. There appears to be desire from the editing team to have atomic edits for template styles, documentation, and schema.
 * Template documentation. Currently using a subpage or inline in a section. There appears to be desire from the editing team to have atomic edits for template styles, documentation, and schema.
 * MassMessage target lists, which are part wikitext and part structured JSON.
 * Allow JS, CSS, HTML and wikitext for CentralNotice to be managed as separate documents and edited using a dedicated editor.
 * ProofreadPage pages, which are composed of 3 separate wikitext content sections (header, body, footer).
 * Gadget styles, currently stored on a parallel page with the .css extension. Has been discussed in the context of a global repository for Gadgets.
 * Manage categories as structured data. Compare T87686. Currently stored inline in wikitext. The editing teams seems to find this desirable, but it's not a high priority. Has been suggested several times over the years.
 * Lua module documentation, currently usually maintained in the Help namespace. Has been discussed in the context of a global repository for Lua modules. That combination would require a localization mechanism.
 * Workflow state. Currently stored in the form of templates and template parameters in the wikitext. There are currently no plans to implement this.
 * Blame maps (as derived content) that record which part of an article was added by which user. There are currently no plans to implement this.
 * OCR text from DeJaVu and PDF files (as derived content). Works best if the upload history is managed using MCR. There are currently no plans to implement this.
 * Managing file upload history. Managing meta-data about uploaded files along with the wikitext would remove the need for an oldimage table (see Requests for comment/image and oldimage tables). This would however require quite a bit of refactoring, and would introduce a large number of revisions (one per upload). There are currently no plans to implement this.
 * Rendered HTML (as derived, possibly virtual "on-thy-fly" content). This would require the ability to update derived content without creating a new revision, on "events" like a template update. It would also require "sub-slots", since different HTML may be produced for different devices or target languages. There are currently no plans to implement this.

Rationale
The above use cases indicate that there is a need for bundling different kinds of information on a single page, to allow atomic editing of the different kinds of content together, provide a shared version history, and allow the different kinds of information to be watched, protected, moved, and deleted together. That is, a revision can have multiple "slots" containing different kinds of data, each having a unique role. The requirements for each slot correspond to what is already defined in the Content interface: we need to be able to store and load, import and export, render, edit, and diff them.

The ability to use a dedicated editing interface and specialized diffing code seems particularly desirable for information that is currently maintained inline as part of the wikitext, such as categories, infobox parameters or media licensing information.

To address this need, MCR introduces an indirection between revisions and content objects, changing the relationship from 1:0 to a 1:n (or m:n, if content meta-data can be re-used by multiple revisions). This requires a substantial refactoring in the storage layer of MediaWiki, and some changes to the database schema. By requiring all revisions to have a "main" slot which will be used as the default in many situations, we can achieve interoperability with code that does not know about MCR.

An alternative approach discussed earlier was Multipart Content: Multiple Content objects would be coded into a single meta-objects, a multipart content object, using a mechanism similar to the MIME-Multipart mechanism used for email attachments. This however has two major disadvantages: performance and interoperability. The performance is not optimal, since the entire content blob must always be loaded to access any part of the content. Interoperability is not optimal, since it is unclear how external interfaces like the web API should behave to be backwards compatible: should they return the entire multipart object as the revision content, or always the main part of the content? The former breaks code that manipulates the content, while the latter causes data loss with code that treats content as opaque blobs. MCR faces a similar problem, but allows the new structures to be made explicit in the API response data and request parameters, instead of requiring clients to decode nested blobs.

Another advantage of MCR over the Multipart approach is performance. Different kinds of content can use different optimized blob store mechanisms, and not all content needs to be loaded from storage if only the content of one slot is needed.

For these reasons, MCR was identified as the preferred approach.

Streams

 * Streams are not modeled directly, they exist purely as the history of a given slot for a given page and can be referenced by page title(+namespace) and slot name.
 * TBD: we may need a "closed" representation in a single string, for use in wiki links. For streams, we could use "namespace:title##slot" or "namespace:title||slot".
 * Which slots (resp. streams) are available for a given page may be configured per namespace, or per content model of the main slot (which in turn would typically be based on the namespace). The details of this are TBD.

Derived Content

 * Derived content can be stored using the generic blob storage service interface
 * The association between derived content and revisions may need a more specialized mechanism than slots provide.

Virtual Slots

 * Virtual slots will need to be implemented at a structured storage layer placed between RevisionLookup and the BlobLookup.
 * Virtual slots typically need access to the slot name and to the slots of the revision (per definition, virtual content is derived from other content). Perhaps it should have access to a RevisionRecord.
 * Some virtual slots may need access to data in the parent revision (e.g. for blame maps and diffs).
 * Virtual slots should have a way to signal whether the generated content should be stored (as persistent derived content) once it has been generated, or not. Virtual slots that do not allow generated content to be stored are considered "volatile". There could also be an option to persist virtual content only for a limited time ("cacheable").
 * Straw man interface: . Caution: accessing slot content via $revRec may cause other virtual slots to be triggered. Circular dependencies must be avoided.

Sub-slots

 * We may want to model sub-slots and/or sub-revisions.


 * Sub-slots: Slot names may be suffixed to allow multiple "sub-slots", all with the same storage backend and content model. E.g. there could be an "html" slot with one sub-slot per language, e.g. "html.de", "html.nl", etc.
 * Sub-revisions would model "events" such as a template bein updated.

Architecure Overview (old)
Note: primary (user generated) content slots must be enumerable. Which revision has which primary slots is recorded in the database. Secondary (derived) content slots may also be persistent in the database, but can just as well be purely virtual. As a point in case, we'd want a) a ParserCache implementation based on persistent derived slots as well as b) a virtual slot implementation based on the existing ParserCache.
 * PageStore -> create/update/delete pages. Uses RevisionStore. Does all the secondary data update stuff.
 * RevisionStore -> returns RevisionBuilder; Caller adds RevisionSlots and meta-data to RevisionBuilder
 * RevisionBuilder maintains transactional context. Needs to be aware of base rev id for "late" conflict detection!
 * later add support for RevisionUpdater, for updating persistent derived revision data
 * RevisionLookup returns RevisionRecord objects; LazyRevisionRecord for lazy loading?
 * RevisionRecord can enum RevisionSlots for primary content. LazyRevisionSlot for lazy loading of content.
 * RevisionSlots has Content and meta-data (size, hash, content model, change date, etc); Do we need a RevisionSlotLookup/RevisionSlotStore?
 * Primary content implements Content. Derived content implements Data(?!); Content extends Data.
 * RevisionStore/RevisionLookup is based on BlobStoreMultiplexer. Read/write is routed based on a prefix in the blob id.
 * BlobStoreMultiplexer manages multiple BlobStores
 * RevisionStore turns blobs into ContentObjects and creates RevisionSlot and RevisionRecord objects from them (or creates a LazyRevisionRecord that loads data on demand)