UDF atomicity when updating a simple bin and a large list bin


#1

Hi,

I am thinking to store a list of messages in a large list. The message is identified by an auto-incremented sequence number. I can see two approaches:

  1. Client firstly add the sequence bin to get the new seq, and then call llist_add to insert the message.
  2. Put the seq bin and the large list bin in the same record, and write a lua script to increment the seq and save the message with the new seq.

Approach 1 faces a potential issue: because incrementing seq and insert into largelist are two calls, it could happen that the seq get increased but the message with that seq failed to save. A gap of seq in the messages is not ideal in my application.

Approach 2 seems work if a record UDF is atomic even the UDF writes an int bin and a large list bin. From the lua API I saw aerospike:update(rec) and llist_lib.add(…). So I am wondering whether a UDF handles rollback if aerospike:update(rec) is executed but crashes before llist_lib.add(…) get called?

Thanks.


#2

I found llist_lib.add() internally calls aerospike:update(). Does it mean llist_lib.add() can update simple bins in topRec and the large list atomically?


#3

lming,

The way UDF is works is any changes that you may have done won’t be committed unless you call aerospike:update().

When list operation returns as you have pointed would commit all the changes. So way you should build your UDF is perform the llist operation in the end (and not commit anything before list operation). Given that you are working with only 1 LDT if LDT operation fails entire UDF operation would fail atomically.

As you would have figured if there were two LDT bins in the same record and want to manipulate both atomically it won’t work. Will be fixed …

HTH

– R


#4

Thank you R for the excellent explanation! Glad to have to atomicity that really solves my problem.

One more question, could it happen that the LDT is updated (and server crashes) but the simple integer bin failed to update?


#5

Hi R,

I wrote a simple UDF as below, but executing it with ascli always get errors, though the data is actually saved in db. Any hint?

Thanks!

[vagrant@localhost ~]$ ascli udf-put moo.lua 
[vagrant@localhost ~]$ ascli udf-get moo.lua 
local lll = require('ldt/lib_llist');

function put_message(topRec, msgBin, msg)
        if not aerospike:exists(topRec) then
                aerospike:create(topRec)
        end
        local seq = topRec['seq']
        if seq == nil then
                seq = 1
        else
                seq = seq + 1
        end
        topRec['seq'] = seq
        local v = map{
                key = seq,
                m = msg,
        }
        lll.add(topRec, msgBin, v, nil)
        return seq
end
[vagrant@localhost ~]$ ascli udf-record-apply test udf_set t1 moo put_message dd "m1"
1
Segmentation fault
[vagrant@localhost ~]$ ascli udf-record-apply test udf_set t1 moo put_message dd "m2"                 
2
Segmentation fault
[vagrant@localhost ~]$ ascli udf-record-apply test udf_set t1 moo put_message dd "m3"                 
3
Segmentation fault
[vagrant@localhost ~]$ ascli udf-record-apply 'test' 'udf_set' 't1' 'llist' 'scan' 'dd'               
[ { "m": "m1", "key": 1 }, { "m": "m2", "key": 2 }, { "m": "m3", "key": 3 } ]
*** glibc detected *** ascli: munmap_chunk(): invalid pointer: 0x00007fff62e55800 ***
======= Backtrace: =========
/lib64/libc.so.6(+0x75e66)[0x7ff740789e66]
ascli(as_string_val_destroy+0x32)[0x448aee]
ascli(as_val_val_destroy+0x39)[0x448dae]
ascli(as_arraylist_release+0x32)[0x44669e]
ascli(as_val_val_destroy+0x39)[0x448dae]
ascli[0x437069]
ascli(run+0x13e)[0x4387fe]
ascli(main+0x2cb)[0x4396eb]
/lib64/libc.so.6(__libc_start_main+0xfd)[0x7ff740732d5d]
ascli[0x4350e9]
======= Memory map: ========
00400000-0057e000 r-xp 00000000 fd:00 1969458                            /opt/aerospike/bin/ascli
0077e000-0079b000 rw-p 0017e000 fd:00 1969458                            /opt/aerospike/bin/ascli
0079b000-007a1000 rw-p 00000000 00:00 0 
01952000-01973000 rw-p 00000000 00:00 0                                  [heap]
7ff73fafd000-7ff73fb13000 r-xp 00000000 fd:00 524290                     /lib64/libgcc_s-4.4.7-20120601.so.1
7ff73fb13000-7ff73fd12000 ---p 00016000 fd:00 524290                     /lib64/libgcc_s-4.4.7-20120601.so.1
7ff73fd12000-7ff73fd13000 rw-p 00015000 fd:00 524290                     /lib64/libgcc_s-4.4.7-20120601.so.1
7ff73fd13000-7ff73fd14000 ---p 00000000 00:00 0 
7ff73fd14000-7ff740714000 rw-p 00000000 00:00 0 
7ff740714000-7ff74089e000 r-xp 00000000 fd:00 524301                     /lib64/libc-2.12.so
7ff74089e000-7ff740a9e000 ---p 0018a000 fd:00 524301                     /lib64/libc-2.12.so
7ff740a9e000-7ff740aa2000 r--p 0018a000 fd:00 524301                     /lib64/libc-2.12.so
7ff740aa2000-7ff740aa3000 rw-p 0018e000 fd:00 524301                     /lib64/libc-2.12.so
.....

#6

Use this to create entry into lllist

......
....
local val = map.new(1)
map["key"] = seq
map["m"] = msg
lll.add(topRec, msgBin, val)
return seq
....
...

About the crash… does aql work

aql > execute llist.scan('dd') on test.udf_set where PK='t1'

– R


#7

aql llist scan works. So it seems ascli has problem.

Sorry for my newbie Lua question, what’s the difference between your approach of creating the map (map.new) and my approach (which I copied from some example code).

Lastly, could it happen that the LDT is updated (and then db server power off) but the simple integer bin failed to update?

Thanks!


#8

All the bins are updated together. So either the entire change would make it or none …

In your approach default map will be created which is of large size. If you already know how big your map is going to be it is always better to specify it upfront. This should be higher performant. I will confirm

– R


#9

Thank you R. It’s very helpful!


#10

@lming,

Thank you for posting about LDTs in our forum. Please see the LDT Feature Guide for current LDT recommendations and best practices.


#11

@lming,

Effective immediately, we will no longer actively support the LDT feature and will eventually remove the API. The exact deprecation and removal timeline will depend on customer and community requirements. Instead of LDTs, we advise that you use our newer List and SortedMap APIs, which are now available in all Aerospike-supported clients at the General Availability level. Read our blog post for details.