The Design of Delayed View Data Structures in Video Recommendation
In video search and recommendation systems, recommendation algorithms need to dynamically generate suggestion lists based on user viewing behavior. Among these, "Delayed View" is an important recommendation scenario: when a user is watching video A, the system needs to predict and recommend what the user might want to watch next, such as "next episode," "related movies," or "similar recommendations."
Behind this recommendation logic lies a carefully designed data structure. This article provides an in-depth analysis of the design philosophy in industrial-grade code, with a clean-room reconstruction in Go.
From Problem to Model
The delayed view scenario requires expressing quite rich information:
- Basic attributes: video URL, thumbnail, title, duration
- Progress information: user's current playback position, total duration
- Type identification: Is this a movie? A series? Or just a general recommendation?
- Metadata: If it's a series, which season and episode?
Faced with these requirements, a naive approach would be to use Map<String, String> or simple JSON objects. But this approach brings several problems:
- Type unsafety: String keys are easy to misspell, no compile-time checking
- Semantic ambiguity: Cannot distinguish between "not set" and "set to empty"
- Performance overhead: Runtime parsing of string keys adds overhead
Industrial-grade systems typically solve this with strong typing + bitmasks.
Bitmasks: Type-Safe Combinations
In the original code, view types are defined as enum class EDelayedViewType, using bitmasks to support type combinations:
enum class EDelayedViewType {
DVT_COMMON = (1 << 0), // "common"
DVT_FILM = (1 << 1), // "film"
DVT_SERIAL = (1 << 2), // "serial"
DVT_NEXT_EPISODE = (1 << 3) // "next_episode"
};
The advantages of this design:
- Combination capability: A video can be both "movie" type and "latest episode" type
- Efficient bit operations: Type checking requires only one bitwise operation, not multiple string comparisons
- Type safety: Type errors are caught at compile time
Optional Fields: The Maybe Pattern Trade-off
Series information (SerialInfo) doesn't exist for every video—only for series-type videos. The original code uses TMaybe<TSerialInfo> to model this "possibly existing" field:
TMaybe<TSerialInfo> MaybeSerialInfo;
TMaybe<TEntityInfo> MaybeEntityInfo;
The trade-offs of this design vs using std::optional or pointers directly:
- Clear semantics:
TMaybeexplicitly expresses the difference between "undefined" and "empty value" - Library consistency: Maintains consistent interface style with other modules in the project
- Learning curve: Requires understanding the project's specific type system
Serialization: The Bridge from Domain Objects to JSON
The frontend of recommendation systems typically needs JSON-formatted data. The original code provides DelayedViewToJson function for this transformation. This transformation process involves some interesting branching logic:
const bool isNextEpisode = (delayedView.Type == EDelayedViewType::DVT_NEXT_EPISODE);
if (!isNextEpisode) {
result["duration"] = delayedView.Duration;
// Calculate playback progress
}
This reflects the trade-off between polymorphism vs conditional branching:
- Polymorphism approach: Define different serializers for each type
- Conditional branching: Use if-else in one function to handle all cases
When there are few types, conditional branching is simpler; when types balloon to dozens, the polymorphism approach is more maintainable.
Go Clean-Room Demonstration
Below is a clean-room demonstration code in Go, reconstructing the above design philosophy:
package main
import (
"encoding/json"
"fmt"
)
// Design concept: Delayed view data structure in video recommendation
//
// Demonstrates data model trade-offs in industrial-grade systems:
// 1. **Strong typing**: Use constants instead of strings to distinguish view types
// 2. **Bitmask for multi-selection**: Support combined types via bitwise operations
// 3. **Optional field modeling**: Use pointers to handle optional metadata
// View type constants - corresponds to C++ enum class EDelayedViewType
const (
ViewTypeCommon = 1 << 0 // "common"
ViewTypeFilm = 1 << 1 // "film"
ViewTypeSerial = 1 << 2 // "serial"
ViewTypeNextEpisode = 1 << 3 // "next_episode"
)
// DelayedView domain object
type DelayedView struct {
Type int `json:"type"`
URL string `json:"url"`
ThumbURL string `json:"thumb"`
Title string `json:"title"`
TicksCount int64 `json:"ticks_count"`
Duration int64 `json:"duration"`
QueryText string `json:"query_text"`
Timestamp int64 `json:"ts"`
IsPorno bool `json:"is_porno"`
SerialInfo *SerialInfo `json:"serial_info,omitempty"`
EntityInfo *EntityInfo `json:"entity_info,omitempty"`
}
// Series information
type SerialInfo struct {
SerialId uint32 `json:"serial_id"`
SeasonId uint32 `json:"season_id"`
EpisodeId uint32 `json:"episode_id"`
}
// Entity information
type EntityInfo struct {
Type string `json:"type"`
Id string `json:"id"`
}
// ToJSON serializes domain object to JSON
func (dv DelayedView) ToJSON() (map[string]interface{}, error) {
result := make(map[string]interface{})
// Type conversion: bitmask -> string
typeStr := "unknown"
switch {
case (dv.Type & ViewTypeNextEpisode) != 0:
typeStr = "next_episode"
case (dv.Type & ViewTypeSerial) != 0:
typeStr = "serial"
case (dv.Type & ViewTypeFilm) != 0:
typeStr = "film"
case (dv.Type & ViewTypeCommon) != 0:
typeStr = "common"
}
result["type"] = typeStr
// Common fields
result["thumb"] = dv.ThumbURL
result["title"] = dv.Title
result["ts"] = dv.Timestamp
isNextEpisode := (dv.Type & ViewTypeNextEpisode) != 0
// Branch based on type
if !isNextEpisode {
result["duration"] = dv.Duration
if dv.Duration == 0 {
result["progress"] = 1.0
} else {
result["progress"] = float64(dv.TicksCount) / float64(dv.Duration)
}
}
// CGI output
cgis := map[string]interface{}{
"text": dv.QueryText,
}
if isNextEpisode {
cgis["filmId"] = "placeholder_id"
} else {
cgis["url"] = dv.URL
cgis["filmId"] = "placeholder_id"
}
cgis["source"] = "vdv"
result["cgis"] = cgis
// Tags
tags := []string{}
if isNextEpisode {
tags = append(tags, "next_episode")
} else {
tags = append(tags, "view_time")
}
result["tags"] = tags
// Optional fields: only serialize when present (omitempty)
if dv.SerialInfo != nil {
result["serial_info"] = dv.SerialInfo
}
if dv.EntityInfo != nil {
result["entity_info"] = dv.EntityInfo
}
return result, nil
}
func main() {
fmt.Println("=== Video Delayed View Data Structure: Clean-Room Demo (Go) ===")
// Example 1: Regular video delayed view
view1 := DelayedView{
Type: ViewTypeCommon,
URL: "https://example.com/video1",
ThumbURL: "https://example.com/thumb1.jpg",
Title: "Excellent Documentary",
TicksCount: 120,
Duration: 600,
QueryText: "documentary",
Timestamp: 1700000000,
IsPorno: false,
}
json1, _ := view1.ToJSON()
jsonBytes, _ := json.MarshalIndent(json1, "", " ")
fmt.Printf("\n[Common View] JSON Output:\n%s\n", string(jsonBytes))
// Example 2: Next episode recommendation (with series info)
view2 := DelayedView{
Type: ViewTypeNextEpisode | ViewTypeSerial,
URL: "https://example.com/episode2",
ThumbURL: "https://example.com/thumb2.jpg",
Title: "Series Episode 2",
TicksCount: 0,
Duration: 1800,
QueryText: "next episode",
Timestamp: 1700000100,
IsPorno: false,
SerialInfo: &SerialInfo{
SerialId: 12345,
SeasonId: 1,
EpisodeId: 2,
},
}
json2, _ := view2.ToJSON()
jsonBytes2, _ := json.MarshalIndent(json2, "", " ")
fmt.Printf("\n[Next Episode] JSON Output:\n%s\n", string(jsonBytes2))
}
Summary
This article provides an in-depth analysis of the industrial-grade design of delayed view data structures in video recommendation systems, exploring the following core trade-offs:
- Bitmasks vs Strings: Bitmasks provide type-safe combination capability, but require additional parsing logic
- Optional fields: The Maybe pattern in strongly-typed systems requires balancing semantic clarity with library generality
- Serialization branching: Conditional branching is simpler when there are few types; polymorphism is needed when types expand
These design choices have no absolute good or bad—understanding scenario constraints and making reasonable trade-offs is the key.