You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

AsyncStorage大存储量计算报错及批量方案失效问题求助

问题描述

在Expo环境中使用Zustand结合AsyncStorage存储大于2MB的数据时,计算存储占用遇到以下问题:

  1. 初始计算代码触发Row too big to fit into CursorWindow requiredPos=0, totalRows=1错误
  2. 改用批量获取加重试的方案后,存储量超过4MB时,计算出的存储占用数值自动变为0(仅希望点击清理缓存时置0)

初始计算代码

import AsyncStorage from "@react-native-async-storage/async-storage";
import { logger, LOG_LEVEL } from "../utils/general/logger";
export const checkAsyncStorageMemoryUsage = async (setAsyncStorageMemoryUsage) => {
    try {
        const keys = await AsyncStorage.getAllKeys();
        let totalSize = 0;
        for (let i = 0; i < keys.length; i++) {
            const key = keys[i];
            const value = await AsyncStorage.getItem(key);
            const valueSize = value ? value.length * 2 : 0;
            totalSize += key.length * 2 + valueSize;
        }
        const totalSizeInMB = (totalSize / (1024 * 1024)).toFixed(2);
        console.log(`AsyncStorage memory usage: ${totalSizeInMB} MB`);
        logger(`AsyncStorage memory usage: ${totalSizeInMB} MB`, LOG_LEVEL.INFO);
        setAsyncStorageMemoryUsage(totalSizeInMB);
    } catch (error) {
        logger(`Error checking AsyncStorage memory usage: ${error}`, LOG_LEVEL.ERROR);
    }
};

触发的错误

Row too big to fit into CursorWindow requiredPos=0, totalRows=1

尝试的批量重试方案(存在超4MB数值变0问题)

import AsyncStorage from "@react-native-async-storage/async-storage";
import { logger, LOG_LEVEL } from "../utils/general/logger";

const BATCH_SIZE = 5;
const MAX_RETRIES = 5;

export const checkAsyncStorageMemoryUsage = async (setAsyncStorageMemoryUsage) => {
    try {
        const keys = await AsyncStorage.getAllKeys();

        let totalSize = 0;

        for (let i = 0; i < keys.length; i += BATCH_SIZE) {
            const batchKeys = keys.slice(i, i + BATCH_SIZE);

            let batchValues = [];
            let retryCount = 0;

            while (retryCount < MAX_RETRIES) {
                try {
                    batchValues = await Promise.all(batchKeys.map(key => AsyncStorage.getItem(key)));
                    break; 
                } catch (error) {
                    retryCount++;
                    console.warn(`Retrying batch retrieval (${retryCount}/${MAX_RETRIES}) due to error: ${error}`);
                }
            }

            const batchData = batchKeys.map((key, j) => ({
                key,
                value: batchValues[j],
            }));

            for (const { key, value } of batchData) {
                const valueSize = value ? value.length * 2 : 0;
                totalSize += key.length * 2 + valueSize;
            }
        }

        const totalSizeInMB = (totalSize / (1024 * 1024)).toFixed(2);
        console.log(`AsyncStorage memory usage: ${totalSizeInMB} MB`);
        logger(`AsyncStorage memory usage: ${totalSizeInMB} MB`, LOG_LEVEL.INFO);
        setAsyncStorageMemoryUsage(totalSizeInMB);
    } catch (error) {
        logger(`Error checking AsyncStorage memory usage: ${error}`, LOG_LEVEL.ERROR);
    }
};

缓存清理代码

export const clearPersistedStore = async () => {

    try {

        await AsyncStorage.removeItem("storage");

        resetStore();

    } catch (error) {

        logger("Error removing item:" + error, LOG_LEVEL.ERROR);

    }
};

解决方案建议

方案1:单个键值逐个获取(降低CursorWindow负载)

放弃批量Promise.all,改为逐个获取每个键值,避免单个批次包含大体积数据导致CursorWindow溢出,同时捕获单个键的读取错误,跳过异常键继续计算:

import AsyncStorage from "@react-native-async-storage/async-storage";
import { logger, LOG_LEVEL } from "../utils/general/logger";

export const checkAsyncStorageMemoryUsage = async (setAsyncStorageMemoryUsage) => {
    try {
        const keys = await AsyncStorage.getAllKeys();
        let totalSize = 0;

        for (const key of keys) {
            try {
                const value = await AsyncStorage.getItem(key);
                const valueSize = value ? value.length * 2 : 0;
                totalSize += key.length * 2 + valueSize;
            } catch (keyError) {
                console.warn(`Failed to read key ${key}: ${keyError}`);
                logger(`Failed to read key ${key}: ${keyError}`, LOG_LEVEL.WARN);
            }
        }

        const totalSizeInMB = (totalSize / (1024 * 1024)).toFixed(2);
        console.log(`AsyncStorage memory usage: ${totalSizeInMB} MB`);
        logger(`AsyncStorage memory usage: ${totalSizeInMB} MB`, LOG_LEVEL.INFO);
        setAsyncStorageMemoryUsage(totalSizeInMB);
    } catch (error) {
        logger(`Error checking AsyncStorage memory usage: ${error}`, LOG_LEVEL.ERROR);
    }
};

方案2:提前维护元数据记录存储大小

在每次写入AsyncStorage时,同步更新专门的元数据键(比如storage-meta),记录每个键的大小,计算总占用时无需读取所有值,直接读取元数据求和:

// 写入数据时同步更新元数据
export const saveDataToStorage = async (key, value) => {
    try {
        await AsyncStorage.setItem(key, value);
        const keySize = key.length * 2;
        const valueSize = value.length * 2;
        const totalKeySize = keySize + valueSize;
        
        const metaStr = await AsyncStorage.getItem("storage-meta");
        const meta = metaStr ? JSON.parse(metaStr) : {};
        meta[key] = totalKeySize;
        await AsyncStorage.setItem("storage-meta", JSON.stringify(meta));
    } catch (error) {
        logger(`Error saving data: ${error}`, LOG_LEVEL.ERROR);
    }
};

// 计算存储占用时直接读取元数据
export const checkAsyncStorageMemoryUsage = async (setAsyncStorageMemoryUsage) => {
    try {
        const metaStr = await AsyncStorage.getItem("storage-meta");
        const meta = metaStr ? JSON.parse(metaStr) : {};
        let totalSize = Object.values(meta).reduce((sum, size) => sum + size, 0);
        
        // 加上元数据自身的大小
        const metaSize = metaStr ? metaStr.length * 2 + "storage-meta".length * 2 : 0;
        totalSize += metaSize;

        const totalSizeInMB = (totalSize / (1024 * 1024)).toFixed(2);
        console.log(`AsyncStorage memory usage: ${totalSizeInMB} MB`);
        logger(`AsyncStorage memory usage: ${totalSizeInMB} MB`, LOG_LEVEL.INFO);
        setAsyncStorageMemoryUsage(totalSizeInMB);
    } catch (error) {
        logger(`Error checking AsyncStorage memory usage: ${error}`, LOG_LEVEL.ERROR);
    }
};

// 清理缓存时同时删除元数据
export const clearPersistedStore = async () => {
    try {
        await AsyncStorage.removeItem("storage");
        await AsyncStorage.removeItem("storage-meta");
        resetStore();
    } catch (error) {
        logger("Error removing item:" + error, LOG_LEVEL.ERROR);
    }
};

该方案从根源避免读取大体积数据的问题,性能最优,但需确保所有写入操作同步更新元数据。

方案3:用Expo FileSystem替代大体积数据存储

AsyncStorage设计用于小数据存储,大于2MB的数据建议改用Expo FileSystem存储文件,仅用AsyncStorage记录文件路径和大小:

import * as FileSystem from 'expo-file-system';
import AsyncStorage from "@react-native-async-storage/async-storage";
import { logger, LOG_LEVEL } from "../utils/general/logger";

// 保存大数据到文件系统
export const saveLargeData = async (key, data) => {
    try {
        const filePath = `${FileSystem.documentDirectory}${key}.dat`;
        await FileSystem.writeAsStringAsync(filePath, data);
        
        const fileInfo = await FileSystem.getInfoAsync(filePath);
        await AsyncStorage.setItem(`file-meta-${key}`, JSON.stringify({
            path: filePath,
            size: fileInfo.size
        }));
    } catch (error) {
        logger(`Error saving large data: ${error}`, LOG_LEVEL.ERROR);
    }
};

// 计算总存储占用:AsyncStorage数据 + 文件系统数据
export const checkTotalStorageUsage = async (setStorageMemoryUsage) => {
    try {
        const keys = await AsyncStorage.getAllKeys();
        let asyncStorageSize = 0;
        let fileTotalSize = 0;

        for (const key of keys) {
            if (key.startsWith("file-meta-")) {
                const metaStr = await AsyncStorage.getItem(key);
                const meta = JSON.parse(metaStr);
                fileTotalSize += meta.size;
                asyncStorageSize += key.length * 2 + (metaStr ? metaStr.length * 2 : 0);
            } else {
                try {
                    const value = await AsyncStorage.getItem(key);
                    asyncStorageSize += key.length * 2 + (value ? value.length * 2 : 0);
                } catch (keyError) {
                    console.warn(`Failed to read key ${key}: ${keyError}`);
                }
            }
        }

        const totalSize = asyncStorageSize + fileTotalSize;
        const totalSizeInMB = (totalSize / (1024 * 1024)).toFixed(2);
        console.log(`Total storage usage: ${totalSizeInMB} MB`);
        logger(`Total storage usage: ${totalSizeInMB} MB`, LOG_LEVEL.INFO);
        setStorageMemoryUsage(totalSizeInMB);
    } catch (error) {
        logger(`Error checking storage usage: ${error}`, LOG_LEVEL.ERROR);
    }
};

// 清理缓存时同时删除文件和元数据
export const clearPersistedStore = async () => {
    try {
        const keys = await AsyncStorage.getAllKeys();
        for (const key of keys) {
            if (key.startsWith("file-meta-")) {
                const metaStr = await AsyncStorage.getItem(key);
                const meta = JSON.parse(metaStr);
                await FileSystem.deleteAsync(meta.path);
                await AsyncStorage.removeItem(key);
            }
        }
        await AsyncStorage.removeItem("storage");
        resetStore();
    } catch (error) {
        logger("Error removing item:" + error, LOG_LEVEL.ERROR);
    }
};

该方案符合React Native/Expo最佳实践,大文件存储更稳定,避免AsyncStorage的大小限制和CursorWindow问题。


内容的提问来源于stack exchange,提问作者Keerthana Vadivel

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.14 07:10:57