Redis的Pipeline、事务和lua
1. Pipeline
1.1 Pipeline概念
Redis客户端执行一条命令分别为如下4个过程:
1) 发送命令
2) 命令排队
3) 命令执行
4) 返回结果
其中1)+4)称为Round Trip Time(RTT,往返时间)。
Redis提供了批量操作命令(例如mget、mset等),有效地节约RTT。但大部分命令是不支持批量操作的,例如要执行n 次 hgetall命令,并没有mhgetall命令存在,需要消耗n次RTT。Redis的客户端和服务端可能部署在不同的机器上。例如客户端在北京,Redis服务端在上海,两地直线距离约为1300公里,那么1次 RTT时间=1300 x 2/ ( 300000 x 2/3 ) =13毫秒(光在真空中传输速度为每秒30万公里,这里假设光纤为光速的2/3),那么客户端在1秒内大约只能执行80次左右的命令,这个和Redis的高并发高吞吐特性背道而驰。
Pipeline (流水线)机制能改善上面这类问题,它能将一组Redis命令进行组装,通过一次RTT传输给Redis, 再将这组 Redis命令的执行结果按顺序返回给客户端,图3-5为没有使用Pipeline执行了n条命令,整个过程需需要n次RTT。
图3-6为使用Pipeline执行了n次命令,整个过程需要1次 RTT。
Pipeline并不是什么新的技术或机制,很多技术上都使用过。而且RTT在不同网络环境下会有不同,例如同机房和同机器会比较快,跨机房跨地区会比较慢。Redis命令真正执行的时间通常在微秒级别,所以才会有Redis性能瓶颈是网络这样的说法。
redis-cli的--pipe选项实际上就是使用Pipeline机制,例如下面操作将set hello world和incr counter两条命令组装:
echo -en ' *3\r\n$3\r\nSET\r\n$5\r\nhello\r\n$5\r\nworld\r\n*2\r\n$4\r\nincr\r\n$7\r\ncounter\r\n' | redis-cli --pipe
但大部分开发人员更倾向于使用高级语言客户端中的Pipeline,目前大部分Redis客户端都支持Pipeline。
1.2 性能测试
表3-1给出了在不同网络环境下非Pipeline和 Pipeline执行10000次 set操作的效果,可以得到如下两个结论:
□ Pipeline执行速度一般比逐条执行要快。
□ 客户端和服务端的网络延时越大,Pipeline的效果越明显。
表 3-1 在不同网络下,10000条set非Pipeline和 Pipeline的执行时间对比 |
|||
网 络 |
延 迟 |
非 Pipeline |
Pipeline |
本机 |
0.17ms |
573ms |
134ms |
内网服务器 |
0.41ms |
1610ms |
240ms |
异地机房 |
7ms |
78499ms |
1104ms |
1.3 原生批量命令与Pipeline对比
可以使用Pipeline模拟出批量操作的效果,但是在使用时要注意它与原生批量命令的区别,具体包含以下几点:
口原生批量命令是原子的,Pipeline是非原子的。
口原生批量命令是一个命令对应多个key, Pipeline支持多个命令。
□原生批量命令是Redis服务端支持实现的,而 Pipeline需要服务端和客户端的共同实现。
1.4 最佳实践
Pipeline虽然好用,但是每次Pipeline组装的命令个数不能没有节制,否则一次组装Pipeline数据量过大,一方面会增加客户端的等待时间,另一方面会造成一定的网络阻塞,可以将一次包含大量命令的Pipeline拆分成多次较小的Pipeline来完成。
Pipeline只能操作一个Redis实例,但是即使在分布式Redis场景中,也可以作为批量操作的重要优化手段。
2.事务与Lua
为了保证多条命令组合的原子性,Redis提供了简单的事务功能以及集成Lua脚本来解决这个问题。本节首先简单介绍Redis中事务的使用方法以及它的局限性,之后重点介绍Lua语言的基本使用方法,以及如何将Redis和Lua脚本进行集成,最后给出Redis管理Lua脚本的相关命令。
2.1 事务
熟悉关系型数据库的读者应该对事务比较了解,简单地说,事务表示一组动作,要么全部执行,要么全部不执行。例如在社交网站上用户A 关注了用户B, 那么需要在用户A 的关注表中加入用户B,并且在用户B 的粉丝表中添加用户A, 这两个行为要么全部执行,要么全部不执行,否则会出现数据不一致的情况。
Redis提供了简单的事务功能,将一组需要一起执行的命令放到multi和exec两个命令之间。multi命令代表事务开始,exec命令代表事务结束,它们之间的命令是原子顺序执行的,例如下面操作实现了上述用户关注问题。
127.0.0.1:6379> multi
OK
127.0.0.1:6379> sadd user:a :follow user:b
QUEUED
127.0.0.1:6379> sadd user:b:fans user:a
QUEUED
可以看到sadd命令此时的返回结果是QUEUED, 代表命令并没有真正执行,而是暂时保存在Redis中。如果此时另一个客户端执行sismember user:a:follow user:b 返回结果应该为0。
127.0.0.1:6379> sismember user:a:follow user:b
(integer) 0
只有当exec执行后,用户A 关注用户B 的行为才算完成,如下所示返回的两个结果对应 sadd命令。
127.0.0.1:6379> exec
1) (integer) 1
2) (integer) 1
127.0.0.1:6379> sismember user:a:follow user:b
(integer) 1
如果要停止事务的执行,可以使用discard命令代替exec命令即可。
127.0.0.1:6379> discard
OK
127.0.0.1:6379> sismember user:a:follow user:b
(integer) 0
如果事务中的命令出现错误,Redis的处理机制也不尽相同。
1.命令错误
例如下面操作错将set写成了 sett, 属于语法错误,会造成整个事务无法执行,key和counter的值未发生变化.
127.0.0.1:6388> mget key counter
1) "hello "
2) "100 "
127.0.0.1:6388> multi
OK
127.0.0.1:6388> sett key world
(error) ERR unknown command ' sett'
127.0.0.1:6388> incr counter
QUEUED
127.0.0.1:6388> exec
(error) EXECABORT Transaction discarded because of previous errors.
127.0.0.1:6388> mget key counter
1) "hello "
2) "100
2.运行时错误
例如用户B 在添加粉丝列表时,误把sadd命令写成了 zadd命令,这种就是运行时命令,因为语法是正确的:
127.0.0.1:6379> multi
OK
127.0.0.1:6379> sadd user:a:follow user:b
QUEUED
127.0.0.1:6379> zadd user:b:fans 1 user:a
QUEUED
127.0.0.1:6379> exec
1) (integer) 1
2) (error) WRONGTYPE Operation against a key holding the wrong kind of value
127.0.0.1:6379> sismember user:a:follow user:b
(integer) 1
可以看到Redis并不支持回滚功能,sadd user:a:follow user:b命令已经执行成开发人员需要自己修复这类问题。
有些应用场景需要在事务之前,确保事务中的key没有被其他客户端修改过,才执行事务,否则不执行(类似乐观锁)。Redis提供了 watch命令来解决这类问题,表 3-2展示两个客户端执行命令的时序。
表 3-2事务中 watch命令演示时序 |
||
时间点 |
客户端-1 |
客户端-2 |
T1 |
set key "java" |
|
T2 |
watch key |
|
T3 |
multi |
|
T4 |
append key python |
|
T5 |
append key jedis |
|
T6 |
exec |
|
T7 |
get key |
可以看到“客户端-1”执行multi之前执行了watch命令,“客户端-2”在“客户端-1”执行exec之前修改了key值,造成事务没有执行(exec结果为nil),整个代码如下所示:
#T1:客户端 1
127.0.0.1:6379 > set key "java"
OK
#T2:客户端 1
127.0.0.1:6379> watch key
OK
#T3:客户端 1
127.0.0.1:6379> multi
OK
#T4:客户端 2
127.0.0.1:6379> append key python
(integer) 11
#T5:客户端 1
127.0.0.1:6379> append key jedis
QUEUED
#T6:客户端 1
127.0.0.1:6379> exec
(nil)
#T7:客户端 1
127.0.0.1:6379> get key
"javapython"
Redis提供了简单的事务,之所以说它简单,主要是因为它不支持事务中的回滚特性,同时无法实现命令之间的逻辑关系计算,当然也体现了 Redis的 “keep it simple”的特性,下一小节介绍的Lua脚本同样可以实现事务的相关功能,但是功能要强大很多。
2.2 Lua用法简述
Lua语言是在1993年由巴西一个大学研究小组发明,其设计目标是作为嵌人式程序移植到其他应用程序,它是由C语言实现的,虽然简单小巧但是功能强大,所以许多应用都选用它作为脚本语言,尤其是在游戏领域,例如大名鼎鼎的暴雪公司将Lua语言引入到“魔兽世界”这款游戏中,Rovio公司将Lua语言作为“愤怒的小鸟”这款火爆游戏的关卡升级引擎,Web服务器Nginx将 Lua语言作为扩展,增强自身功能。Redis将 Lua作为脚本语言可帮助开发者定制自己的Redis命令,在这之前,必须修改源码。在介绍如何在Redis中使用Lua脚本之前,有必要对Lua语言的使用做一个基本的介绍。
1.数据类型及其逻辑处理
Lua语言提供了如下几种数据类型:booleans (布尔)、numbers (数值)、strings (字符串)、tables(表格),和许多髙级语言相比,相对简单。下面将结合例子对Lua的基本数据类型和逻辑处理进行说明。
(1) 字符串
下面定义一个字符串类型的数据:
local strings val = "world"
其中,local代表val是一个局部变量,如果没有local代表是全局变量。print函数可以打印出变量的值,例如下面代码将打印world, 其中是Lua语言的注释。
- - 结果是 "world"
print (hello)
(2) 数组
在 Lua中,如果要使用类似数组的功能,可以用tables类型,下面代码使用定义了一个 tables类型的变量myArray,但和大多数编程语言不同的是,Lua的数组下标从1开始计算:
local tables myArray = {"redis", "jedis", true, 88.0}
—true
print(myArray[3])
如果想遍历这个数组,可以使用for和 while, 这些关键字和许多编程语言是一致的。
(a) for
下面代码会计算1到 100的和,关键字for以 end作为结束符
local int sum = 0
for i = 1, 100
do
sum = sum + i
end
- - 输出结果为 5050
print(sum)
要遍历myArray, 首先需要知道tables的长度,只需要在变量前加一个# 号即可:
for i = 1, #myArray
do
print(myArray[ i ])
end
除此之外,Lua还提供了内置函数ipairs, 使用for index,value ipairs(tables)可以遍历出所有的索引下标和值。
for index,value in ipairs(myArray)
do
print(index)
print(value)
end
(b) while
下面代码同样会计算1到100的和,只不过使用的是While循环,while循环同样以end作为结束符。
local int sum = 0
local int i = 0
while i <= 100
do
sum = sum +i
i = i + 1
end
- - 输出结果为 5050
print(sum)
(c) ifelse
要确定数组中是否包含了jedis,有则打印true,注意if以end结尾,if后紧跟then:
local tables myArray = {" redis ", "jedis" , true, 88.0}
for i = 1, #myArray
do
if myArray[i] == "jedis"
then
print ( "true" )
break
else
--do nothing
end
end
(3) 哈希
如果要使用类似哈希的功能,同样可以使用tables类型,例如下面代码定义了一个ta bles,每个元素包含了key和value,其中stringsl .. string2是将两个字符串进行连接:
local tables user_l = {age = 28, name = "tome"}
--user_1 age is 28
print ( "user_1 age is" .. user_1[ "age"])
如果要遍历user_l,可以使用Lua的内置函数pairs:
for key, value in pairs (user_1)
do print(key .. value)
end
2.函数定义
在Lua中,函数以function开头,以end结尾,funcName是函数名,中间部分是函数体:
function funcName()
...
end
contact函数将两个字符串拼接:
function contact(str1, str2 )
return str1 .. str2
end
- - " hello world"
print (contact ( "hello", "world"))
2.3 Redis与Lua
1.在Redis中使用Lua
在Redis中执行lua脚本有两种方法:eval和 evalsha。
(1) eval
eval 脚本内容 key个数 key列表 参数列表
下面例子使用了key列表和参数列表来为Lua脚本提供更多的灵活性:
127.0.0.1:6379> eval 'return "hello "...KEYS[1] ... ARGV[1]' 1 redis world
"hello redisworld"
此时 KEYS[l]=”redis",ARGV[l]="world",所以最终的返回结果是"hello redis world"。
如果Lua脚本较长,还可以使用 redis-cli--eval直接执行文件。
eval 命令和--eval参数本质是一样的,客户端如果想执行Lua脚本,首先在客户端编写好Lua脚本代码,然后把脚本作为字符串发送给服务端,服务端会将执行结果返回给客户端,整个过程如图3-7所示。
(2) evalsha
除了使用 eval, Redis 还提供了evalsha 命令来执行 Lua 脚本。如图 3-8 所示,首先要将Lua脚本加载到Redis服务端,得到该脚本的SHA1校验和 ,evalsha 命令使用 SHA1作为参数可以直接执行对应 Lua 脚本,避免每次发送 Lua 脚本的开销。这样客户端就不需要每次执行脚本内容,而脚本也会常驻在服务端,脚本功能得到了复用。
加载脚本:script load命令可以将脚本内容加载Redi内存中,例如下面将lua_get.lua 加载到 Redis中,得到SHA1为:”7413dc2440dblfea7c0a0bde841fa68eefafl49c"
# redis-cli script load " $ (cat lua_get.lua )"
"7413dc2440dblfea7c0a0bde841fa68eefaf149c"
执行脚本:evalsha的使用方法如下,参数使用SHAl值,执行逻辑和eval—致。
evalsha 脚本 SHA1 值 key 个数 key 列表 参数列表
所以只需要执行如下操作,就可以调用lua_get.lua脚本:
127.0.0.1:6379> evalsha 7413dc2440dblfea7c0a0bde841fa68eefaf149c 1 redis world
"hello redisworld"
2.Lua的Redis API
Lua可以使用redis.call函数实现Redis的访问,例如下面代码是Lua使用redis.ca ll调用了Redis的set和get操作:
redis.call ( "set" , "hello", "world")
redis.call ( "get", "hello" )
放在Redis的执行效果如下:
127.0.0.1:6379> eval 'return redis.call ( "get", KEYS[1] )' 1 hello
"world"
除此之外Lua还可以使用redis.pcall函数实现对Redis的调用,redis.call和red is.pcall的不同在于,如果redis.call执行失败,那么脚本执行结束会直接返回错误,而 redis.pcall会忽略错误继续执行脚本,所以在实际开发中要根据具体的应用场景进行函数的选择。
2.4 案例
Lua脚本功能为Redis开发和运维人员带来如下三个好处:
口 Lua脚本在Redis中是原子执行的,执行过程中间不会插人其他命令。
□ Lua脚本可以帮助开发和运维人员创造出自己定制的命令,并可以将这些命令常驻在
Redis内存中,实现复用的效果。
□ Lua脚本可以将多条命令一次性打包,有效地减少网络开销。
下面以一个例子说明Lua脚本的使用,当前列表记录着热门用户的id, 假设这个列表有5个元素,如下所示:
127.0.0.1:6379> lrange hot:user:list 0 -1
1) "user:1:ratio"
2) "user:8:ratio"
3) "user:3:ratio"
4) "user:99:ratio"
5) "user:72:ratio"
user:{id}:ratio代表用户的热度,它本身又是一个字符串类型的键:
127.0.0.1:6379> mget user:1:ratio user:8:ratio user:3:ratio user:99:ratio user:72:ratio
1) "986"
2) "762"
3) "w556"
4) "400"
5) "101"
现要求将列表内所有的键对应热度做加1操作,并且保证是原子执行,此功能可以利用Lua脚本来实现。
1) 将列表中所有元素取出,赋值给mylist:
local mylist = redis.call ( "lrange", KEYS[1 ], 0, -1)
2) 定义局部变量count= 0,这个count就是最后incr的总次数:
local count = 0
3) 遍历mylist中所有元素,每次做完count自增,最后返回count:
for index,key in ipairs (mylist)
do
redis.call ( "incr" ,key)
count = count + 1
end
return count
将上述脚本写人lrange_and_mincr.lua文件中,并执行如下操作,返回结果为5。
redis-cli --eval lrange_and_mincr.lua hot:user:list
(integer) 5
执行后所有用户的热度自增1:
127.0.0.1:6379> mget user:1:ratio user:8:ratio user:3:ratio user:99:ratio user:72:ratio
1) "987"
2) "763"
3) "557"
4) "401"
5) "102
本节给出的只是一个简单的例子,在实际开发中,开发人员可以发挥自己的想象力创造出更多新的命令。
2.5 Redis 如何管理Lua 脚本
Redis提供了4个命令实现对Lua脚本的管理,下面分别介绍。
(1) script load
script load script
此命令用于将Lua脚本加载到Redis内存中,前面已经介绍并使用过了,这里不再赘述
(2) script exists
scripts exists sha1 [sha1 ...]
此命令用于判断shal是否已经加载到Redis内存中:
127.0.0.1:6379> script exists a5260dd66ce02462c5b5231c727b3f7772c0bcc5
1) (integer) 1
返回结果代表shal [shal ...]被加载到Redis内存的个数。
(3) script flush
script flush
此命令用于清除Redis内存已经加载的所有Lua脚本,在执行script flush后,a5260dd66ce02462c5b5231c727b3f7772c0bcc5不再存在:
127.0.0.1:6379> script exists a5260dd66ce02462c5b5231c727b3f7772c0bcc5
1) (integer) 1
127.0.0.1:6379> script flush
OK
127.0.0.1:6379> script exists a5260dd66ce02462c5b5231c727b3f7772c0bcc5
1) (integer) 0
(4) script kill
此命令用于杀掉正在执行的Lua脚本。如果Lua脚本比较耗时,甚至Lua脚本存在问题,那么此时Lua脚本的执行会阻塞Redis, 直到脚本执行完毕或者外部进行干预将其结束。下面我们模拟一个Lua脚本阻塞的情况进行说明。
下面的代码会使Lua进人死循环:
while 1 == 1
do end
执行Lua脚本,当前客户端会阻塞:
127.0.0.1:6379> eval 'while 1==1 do end' 0
Redis提供了一个lua-time -limit参数,默认是5 秒,它是Lua脚本的“超时时间”,但这个超时时间仅仅是当Lua脚本时间超过lua-time-limit后,向其他命令调用发送BUSY的信号,但是并不会停止掉服务端和客户端的脚本执行,所以当达到lua-time-limit值之后,其他客户端在执行正常的命令时,将会收到“ Busy Redis is busy running ascript”错误,并且提示使用script kill或者shutdown nosave命令来杀掉这个busy的脚本:
127.0.0.1:6379> get hello
(error) BUSY Redis is busy running a script . You can only call SCRIPT KILL or SHUTDOWN NOSAVE.
此时Redis已经阻塞,无法处理正常的调用,这时可以选择继续等待,但更多时候需要快速将脚本杀掉。使用shutdown save显然不太合适,所以选择script kill,当script
kill执行之后,客户端调用会恢复:
127.0.0.1:6379> script kill
OK
127.0.0.1:6379> get hello
"world"
但是有一点需要注意,如果当前Lua脚本正在执行写操作,那么script kill将不会生效。例如,我们模拟一个不停的写操作:
while 1==1
do
redis.call( "set" ,"k","v")
end
此时如果执行script kill,会收到如下异常信息:
(error) UNKILLABLE Sorry the script already executed write commands against the
dataset. You can either wait the script termination or kill the server in a
hard way using the SHUTDOWN NOSAVE command.
上面提示Lua脚本正在向Redis执行写命令,要么等待脚本执行结束要么使用shutdown save停掉Redis服务。可见Lua脚本虽然好用,但是使用不当破坏性也是难以想象的。
Redis的Pipeline、事务和lua的更多相关文章
- Redis篇:事务和lua脚本的使用
现在多数秒杀,抽奖,抢红包等大并发高流量的功能一般都是基于 redis 实现,然而在选择 redis 的时候,我们也要了解 redis 如何保证服务正确运行的原理 前言 redis 如何实现高性能和高 ...
- redis中的事务、lua脚本和管道的使用场景
参考文章 : https://blog.csdn.net/fangjian1204/article/details/50585080
- 【redis 学习系列08】Redis小功能大用处02 Pipeline、事务与Lua
3.Pipeline 3.1 Pipeline概念 Redis客户端执行一条命令分为如下四个过程: (1)发送命令 (2)命令排队 (3)命令执行 (4)返回结果 其中(1)和(4)称为Round T ...
- 高性能伪事务之Lua in Redis
EVAL简介 Redis2.6加入了对Lua脚本的支持.Lua脚本可以被用来扩展Redis的功能,并提供更好的性能. 在<Redis拾遗>中曾经引用了<Redis in Action ...
- 大数据学习day34---spark14------1 redis的事务(pipeline)测试 ,2. 利用redis的pipeline实现数据统计的exactlyonce ,3 SparkStreaming中数据写入Hbase实现ExactlyOnce, 4.Spark StandAlone的执行模式,5 spark on yarn
1 redis的事务(pipeline)测试 Redis本身对数据进行操作,单条命令是原子性的,但事务不保证原子性,且没有回滚.事务中任何命令执行失败,其余的命令仍会被执行,将Redis的多个操作放到 ...
- Redis 中的事务
Redis支持简单的事务 Redis与mysql事务的对比 Mysql Redis 开启 start transaction muitl 语句 普通sql 普通命令 失败 rollback 回滚 di ...
- Redis数据库 02事务| 持久化| 主从复制| 集群
1. Redis事务 Redis不支持事务,此事务不是关系型数据库中的事务: Redis事务是一个单独的隔离操作:事务中的所有命令都会序列化.按顺序地执行.事务在执行的过程中,不会被其他客户端发送来的 ...
- Redis的“假事务”与分布式锁
关注公众号:CoderBuff,回复"redis"获取<Redis5.x入门教程>完整版PDF. <Redis5.x入门教程>目录 第一章 · 准备工作 第 ...
- redis源码学习之lua执行原理
聊聊redis执行lua原理 从一次面试场景说起 "看你简历上写的精通redis" "额,还可以啦" "那你说说redis执行lua脚本的原理&q ...
随机推荐
- QT 资源链家暂存
1.Qt右击菜单栏中文化 链接:https://blog.csdn.net/yangxiao_0203/article/details/7488967
- 【转载】Linux踢出其他正在SSH登陆用户
Linux踢出其他正在SSH登陆用户 在一些生产平台或者做安全审计的时候往往看到一大堆的用户SSH连接到同一台服务器,或者连接后没有正常关闭进程还驻留在系统内.限制SSH连接数与手动断开空闲连 ...
- php-round()四舍六入
今天被问到了四舍六入的问题,好吧,第一次听说.后来查询之后说是银行家算法用的 摘自PHP官方文档.http://php.net/manual/zh/function.round.php (PHP 4, ...
- 1.5 RPM红帽软件包1.6 Yum软件仓库
1.5 RPM红帽软件包 在RPM(红帽软件包管理器)公布之前,要想在Linux系统中安装软件只能采取源码包的方式安装.早期在Linux系统中安装程序是一件非常困难.耗费耐心的事情,而且大多数的服务程 ...
- 就算是3.0的U盘,写入速度10M及以下也是正常的,U盘用很差的闪存颗粒的话就算10Gbps的USB3.1也是很慢的。
作者:范德成链接:https://www.zhihu.com/question/56251636/answer/157021710来源:知乎著作权归作者所有.商业转载请联系作者获得授权,非商业转载请注 ...
- ltp 测试流程及测试脚本分析
LTP介绍 (2011-03-25 18:03:53) 转载▼ 标签: ltp linux 压力测试 杂谈 分类: linux测试 LTP介绍 一.LTP介绍1.简介LTP(Linux Test Pr ...
- 云计算OpenStack---云计算、大数据、人工智能(14)
一.互联网行业及云计算 在互联网时代,技术是推动社会发展的驱动,云计算则是一个包罗万象的技术栈集合,通过网络提供IAAS.PAAS.SAAS等资源,涵盖从数据中心底层的硬件设置到最上层客户的应用.给我 ...
- 8.4 parted:磁盘分区工具
parted 对于小于2TB的磁盘可以用fdisk和parted命令进行分区,这种情况一般采用flisk命令,但对于大于2TB的磁盘则只能用parted分区,且需要将磁盘转换为GPT格式. p ...
- 离散傅里叶变换的衍生,负频率、fftshift、实信号、共轭对称
封面是福州的福道,从高处往下看福道上的人在转圈圈.从傅里叶变换后的频域角度来看,我们的生活也是一直在转圈圈,转圈圈也是好事,说明生活有规律,而我们应该思考的是,如何更有效率地转圈圈--哦别误会,我真不 ...
- MongoDB(7)- 文档插入操作
插入方法 db.collection.insertOne() 插入单条文档到集合中 db.collection.insertMany() 插入多条文档到集合中 db.collection.insert ...