Redis 6.2.7(及Redis 7)版本现有Lua脚本出现只读表修改错误的排查求助
Redis 6.2.7+/7 Lua Script "Attempt to modify a readonly table" Error Fix & Troubleshooting
Hey, I’ve run into this exact issue before—super confusing when the release notes don’t call out the breaking change! Let’s break down what’s happening and how to fix it.
The Root Cause
Your script uses table.getn(task) to check if the ZPOPMAX result is empty, but starting in Redis 6.2.7 (and all Redis 7 versions), Redis returns read-only tables for command results in Lua. The table.getn() function in Redis’s Lua environment (based on Lua 5.1 under the hood) actually tries to modify the table’s metadata (like setting a __len metamethod implicitly) to calculate the length, which triggers the readonly protection and throws that error.
Step-by-Step Troubleshooting & Fixes
- Replace
table.getn()with the#operator:This is the standard, non-modifying way to get a table’s length in Lua. Swap outif table.getn(task) == 0 thenwithif #task == 0 then—this alone should resolve the error. - Verify the readonly behavior:Test this directly in redis-cli to confirm:
# This will fail in 6.2.7+/7, work in 6.2.6 EVAL "local t = redis.call('ZPOPMAX', 'test_key'); table.getn(t)" 0 - Check for other table-modifying operations:If your script has other code that modifies Redis command results (like
table.insert,table.remove, or custom mutations), you’ll need to copy the result to a writable table first:local readOnlyTask = redis.call('ZPOPMAX', tenantQueue) local writableTask = {} for idx, val in ipairs(readOnlyTask) do writableTask[idx] = val end -- Use writableTask for any modifications - Understand the hidden Redis change:While it’s not in the release notes, this readonly table restriction was added to prevent Lua scripts from accidentally corrupting Redis internal data structures. It’s a stability/security improvement that slipped under the documentation radar.
Fixed Version of Your Script
-- returns valid task ID if successful, nil if no tasks local tenantId = unpack(ARGV) local activeTenantsSet, activeTenantsList, tenantQueue = unpack(KEYS) -- next task lua based function - return nil or taskId function next () local task = redis.call('ZPOPMAX', tenantQueue) if #task == 0 then redis.call('SREM', activeTenantsSet, tenantId) redis.call('LREM', activeTenantsList, 0, tenantId) return nil end redis.call('SADD', activeTenantsSet, tenantId) redis.call('RPUSH', activeTenantsList, tenantId) return task[1] end return next()
内容的提问来源于stack exchange,提问作者David
相关产品推荐
相关产品推荐

