← Back to Blog

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:

  1. Basic attributes: video URL, thumbnail, title, duration
  2. Progress information: user's current playback position, total duration
  3. Type identification: Is this a movie? A series? Or just a general recommendation?
  4. 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: TMaybe explicitly 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:

  1. Bitmasks vs Strings: Bitmasks provide type-safe combination capability, but require additional parsing logic
  2. Optional fields: The Maybe pattern in strongly-typed systems requires balancing semantic clarity with library generality
  3. 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.