siyuan-note/siyuan · error

view state patch is too large

Error message

view state patch is too large

What it means

PatchViewState size guard in the kernel view-state storage: the marshalled patch payload exceeds maxViewStatePatchBytes (after already passing the entry-count check). The client sent a single state update too large to store atomically.

Source

Thrown at kernel/model/view_state.go:105

		return nil, errors.New("view state patch contains too many entries")
	}
	for valueKey := range values {
		if err = validateViewStateDataKey(valueKey); err != nil {
			return nil, err
		}
	}
	for _, removeKey := range removeKeys {
		if err = validateViewStateDataKey(removeKey); err != nil {
			return nil, err
		}
	}
	patch := &viewStatePatch{Values: values, RemoveKeys: removeKeys}
	patchData, marshalErr := gulu.JSON.MarshalJSON(patch)
	if nil != marshalErr {
		return nil, marshalErr
	}
	if maxViewStatePatchBytes < len(patchData) {
		return nil, errors.New("view state patch is too large")
	}
	// 后续合并使用序列化快照,避免调用方在请求执行期间继续修改嵌套值。
	normalizedPatch := &viewStatePatch{}
	if err = gulu.JSON.UnmarshalJSON(patchData, &normalizedPatch); nil != err {
		return nil, err
	}
	values = normalizedPatch.Values
	removeKeys = normalizedPatch.RemoveKeys

	viewStateStorageLock.Lock()
	defer viewStateStorageLock.Unlock()

	storage, err := getViewStateStorage()
	if err != nil {
		return nil, err
	}
	state := storage.Views[key]
	if nil == state {

View on GitHub (pinned to 8641553a1f)

Solutions

  1. Reduce value size: strip or truncate large nested data before patching
  2. Split the payload across multiple keys and patch them separately, if each resulting patch fits the byte cap
  3. Move bulk data to a dedicated storage (kernel-side file or SQL) and keep only a reference/ID in view state

Example fix

// before
_, err := model.PatchViewState(key, map[string]any{"blob": base64Content}, nil)
// after
id, _ := model.StoreAsset(boxID, content) // store bulk data elsewhere
_, err := model.PatchViewState(key, map[string]any{"blobRef": id}, nil)
Defensive patterns

Strategy: try-catch

Validate before calling

func isPatchBytesOK(values map[string]any, removeKeys []string) bool {
    data, err := json.Marshal(map[string]any{"Values": values, "RemoveKeys": removeKeys})
    return err == nil && len(data) <= model.MaxViewStatePatchBytes
}

Try / catch

_, err := model.PatchViewState(key, values, removeKeys)
if err != nil && strings.Contains(err.Error(), "patch is too large") {
    // shrink values (drop/truncate large ones) and retry once
    for k, v := range values {
        if jsonSize(v) > largeValueThreshold {
            delete(values, k)
        }
    }
    _, err = model.PatchViewState(key, values, removeKeys)
}
return err

Prevention

When it happens

Trigger: Calling PatchViewState where the JSON-serialized viewStatePatch{Values, RemoveKeys} is larger than maxViewStatePatchBytes — typically a few large values (e.g. big nested arrays, base64 blobs) rather than many keys.

Common situations: Caching large documents or image data inside a single view-state value; storing base64-encoded assets in UI state; a plugin accumulating ever-growing state in one value that eventually crosses the byte cap.

Understand the failure class

Background: payload too large / request exceeds maximum size: why libraries cap bytes and how to fix oversize payloads — this error's family across 50 libraries.

Related errors


AI-assisted analysis of siyuan-note/siyuan@8641553a1f (2026-09-11). Data as JSON: /api/errors/a978268805c48e30. Report an issue: GitHub.