Friday, June 6, 2014

How to prevent a package from upgrading in Debian

Often times, when you are having a server with a package installed, example database package and you want the rest of the packages in the system upgraded but not the database package. How can you do it?

With Debian, you can! You can set a package not to upgrade by setting flag to the package with dpkg. The followings are parameters that you need to be use.
--get-selections [package-name-pattern...]
Get list of package selections, and write it to stdout. Without a pattern, non-installed packages (i.e. those which have been previously
purged) will not be shown.

--set-selections
Set package selections using file read from stdin. This file should be in the format 'package state', where state is one of install, hold,
deinstall or purge. Blank lines and comment lines beginning with '#' are also permitted.

The available file needs to be up-to-date for this command to be useful, otherwise unknown packages will be ignored with a warning. See the
--update-avail and --merge-avail commands for more information.

Let's check what is the state of a current package. To narrate better, I will use mongodb.
jason@localhost:~$ dpkg --get-selections mongodb-org
mongodb-org install

If you decided to stop this package from the next upgrade, you should set flag hold to this package.
jason@localhost:~$ echo "mongodb-org hold" | sudo dpkg --set-selections
jason@localhost:~$ $ dpkg --get-selections mongodb-org
mongodb-org hold

Notice that the package state has transitioned into hold. Okay, let's say we want the package to be upgrade in the next upgrade, then we can set install flag to this package.
jason@localhost:~$ echo "mongodb-org install" | sudo dpkg --set-selections
jason@localhost:~$ dpkg --get-selections mongodb-org
mongodb-org install

So, that's it, simple and quick.

Sunday, May 25, 2014

Learning continous integration with jenkins

All these while, I have been testing using junit test, manual testings, write script to test, or even trigger test from maven, but as many mentioned, even my buddy recommend me to look into jenkins, a continous integration for software development. Today, we are going to look into it.

So what is jenkins?

Jenkins is an open source continuous integration tool written in Java. The project was forked from Hudson after a dispute with Oracle. Jenkins provides continuous integration services for software development. It is a server-based system running in a servlet container such as Apache Tomcat. It supports SCM tools including AccuRev, CVS, Subversion, Git, Mercurial, Perforce, Clearcase and RTC, and can execute Apache Ant and Apache Maven based projects as well as arbitrary shell scripts and Windows batch commands. The primary developer of Jenkins is Kohsuke Kawaguchi.[2] Released under the MIT License, Jenkins is free software.[3]

Before we continue, let's understand the terminologies of jenkins. Below is the term which you will come across when you started with jenkins but for full list, please check out the link.



















JobA runnable task that is controlled / monitored by Jenkins
Completed BuildA build is completed, if it was started and finished with any result, including failed builds.
Successful buildA build is successful when the compilation reported no errors.
NodeRepresents the physical computer running

I guess the simplest way to get yourself into jenkins is just with the command
$ java -jar jenkins.war

from terminal. However, you can also deploy within a servlet container like apache tomcat. Once started, just point your browser to jenkins with url localhost:8080. If default port 8080 is not available, you can specify --httpPort and you can find out other parameters using --help

You can also install jenkins via distribution , e.g.
rpm based distribution
deb based distribution

This article continue with the simple setup. So jenkins stores its files in $HOME/.jenkins. Right now we will create a simple project.

1. click on 'New Item' on the left navigation button.
2. add your project name. example videoOnCloud
3. select 'Build a free-style software project'
4. configure the project. See the attachment.



As a start, let's not configure any version controls system but just a simple script. We wanna learn how jenkins perform its duty. As seen here, there are a few variable configured and if you click on the link 'See the list of available environment variables', it should explain the parameters that I have configured. Go to the landing page and click build now, you should get a blue circle! Of cause, you can configure the build process using maven, ant or windows batch commands. You can also trigger remote build by executing http get to the link http://localhost:8080/job/videoOnCloud/build

So that's it. You should now have very basic using jenkins, try enable version controls system in your build configuration.

Saturday, May 24, 2014

Load balancing policy in datastax java driver

Today we are going to explore LoadBalancingPolicy in datastax java driver for apache cassandra.

So what is load balancing policy in datastax java driver? From code description :

The policy that decides which Cassandra hosts to contact for each new query.

Two methods need to be implemented:

  • LoadBalancingPolicy.distance : returns the "distance" of an host for that balancing policy.

  • LoadBalancingPolicy.newQueryPlan: it is used for each query to find which host to query first, and which hosts to use as failover.


The LoadBalancingPolicy is a com.datastax.driver.core.Host.StateListener and is thus informed of hosts up/down events. For efficiency purposes, the policy is expected to exclude down hosts from query plans.

The default policy for java driver version 2.0.2, is TokenAwarePolicy() and with child policy DCAwareRoundRobinPolicy().

Below are a list of policies available in this version of driver.

RoundRobinPolicy 

This policy queries nodes in a round-robin fashion. For a given query, if an host fail, the next one (following the round-robin order) is tried, until all hosts have been tried. This policy is not datacenter aware and will include every known Cassandra host in its round robin algorithm. If you use multiple datacenter this will be inefficient and you will want to use the DCAwareRoundRobinPolicy load balancing policy instead.

DCAwareRoundRobinPolicy

This policy provides round-robin queries over the node of the local data center. It also includes in the query plans returned a configurable number of hosts in the remote data centers, but those are always tried after the local nodes. In other words, this policy guarantees that no host in a remote data center will be queried unless no host in the local data center can be reached.

If used with a single data center, this policy is equivalent to the RoundRobin policy, but its DC awareness incurs a slight overhead so the RoundRobin policy could be preferred to this policy in that case.

TokenAwarePolicy

This policy encapsulates another policy. The resulting policy works in the following way:

  • the distance method is inherited from the child policy.

  • the iterator return by the newQueryPlan method will first return the LOCAL replicas for the query (based on Statement.getRoutingKey if possible (i.e. if the query getRoutingKey method doesn't return null and if Metadata.getReplicas returns a non empty set of replicas for that partition key). If no local replica can be either found or successfully contacted, the rest of the query plan will fallback to one of the child policy.


Do note that only replica for which the child policy distance method returns HostDistance.LOCAL will be considered having priority. For example, if you wrap DCAwareRoundRobinPolicy with this token aware policy, replicas from remote data centers may only be returned after all the host of the local data center.

WhiteListPolicy

A load balancing policy wrapper that ensure that only hosts from a provided white list will ever be returned.

This policy wraps another load balancing policy and will delegate the choice of hosts to the wrapped policy with the exception that only hosts contained in the white list provided when constructing this policy will ever be returned. Any host not in the while list will be considered IGNORED and thus will not be connected to.

This policy can be useful to ensure that the driver only connects to a predefined set of hosts. Keep in mind however that this policy defeats somewhat the host auto-detection of the driver. As such, this policy is only useful in a few special cases or for testing, but is not optimal in general. If all you want to do is limiting connections to hosts of the local data-center then you should use DCAwareRoundRobinPolicy and *not* this policy in particular.

LatencyAwarePolicy

A wrapper load balancing policy that adds latency awareness to a child policy.

When used, this policy will collect the latencies of the queries to each Cassandra node and maintain a per-node latency score (an average). Based on these scores, the policy will penalize (technically, it will ignore them unless no other nodes are up) the nodes that are slower than the best performing node by more than some configurable amount (the exclusion threshold).

The latency score for a given node is a based on a form of http://en.wikipedia.org/wiki/Moving_average#Exponential_moving_average exponential moving average.
In other words, the latency score of a node is the average of its previously measured latencies, but where older measurements gets an exponentially decreasing weight. The exact weight applied to a newly received latency is based on the time elapsed since the previous measure (to account for the fact that latencies are not necessarily reported with equal regularity, neither over time nor between different nodes).

Once a node is excluded from query plans (because its averaged latency grew over the exclusion threshold), its latency score will not be updated anymore (since it is not queried). To give a chance to this node to recover, the policy has a configurable retry period. The policy will not penalize a host for which no measurement has been collected for more than this retry period.

 

Of cause, not a single load balancing is perfect for one environment and thus you should evaluate the load balancing policy that suit your needs. Because of this, load balancing will be fine tune or more will be added in the future, so always check back in the next release for newly update driver.

Friday, May 23, 2014

Learning git remote

Hello everybody! Today, we will take a look into git remote. So why git remote? Ever wonder why everytime when you push, you only have one command to push to? What if you want to push to a few servers? But before we push into a few servers, let's take a look what is git remote actually?

From git remote documentation

git-remote - Manage set of tracked repositories

So let's explain using examples. Below is my git repository, we check what we have in our current project.
$ git remote -v
origin https://github.com/jasonwee/videoOnCloud.git (fetch)
origin https://github.com/jasonwee/videoOnCloud.git (push)

Okay, so we have a remote repository named origin and its url for fetch and push, all clear, we are tracing the remote repository on github. As you may notice, origin didn't explain much though, other than said, oh yea, this is where it begin. What if you want to use a more descriptive term?
$ git remote rename origin github
$ git remote -v
github https://github.com/jasonwee/videoOnCloud.git (fetch)
github https://github.com/jasonwee/videoOnCloud.git (push)

okay, so now it is very descriptive, our remote repository is github. Now, what if I would like to push to another remote server? what then? Can I do it?
$ git remote add production https://production.com/jasonwee/videoOnCloud.git
$ git remote -v
github https://github.com/jasonwee/videoOnCloud.git (fetch)
github https://github.com/jasonwee/videoOnCloud.git (push)
production https://production.com/jasonwee/videoOnCloud.git (fetch)
production https://production.com/jasonwee/videoOnCloud.git (push)

That's looks pretty easy. But over time, you may forget where is the branches set to.
$ git branch -r
github/master

So it is currently pointing to github/master. If you want to remove local branches which in  remote branches has been removed, you can use git remote prune. Note that, following --dry-run won't actually remove but just show you which is going to be removed. If you are sure, just remove the parameter --dry-run.
$ git remote prune --dry-run github
$

To fetch updates for a named set of remote in the repository, execute using remote update
$ git remote -v update github
Fetching github
From https://github.com/jasonwee/videoOnCloud
= [up to date] master -> github/master

To change a push url for a remote repository, and without --push, fetch url is changed.
$ git remote set-url --push production https://production1.com/jasonwee/videoOnCloud.git
$ git remote -v
github https://github.com/jasonwee/videoOnCloud.git (fetch)
github https://github.com/jasonwee/videoOnCloud.git (push)
production https://production.com/jasonwee/videoOnCloud.git (fetch)
production https://production1.com/jasonwee/videoOnCloud.git (push)

$ git remote set-url production https://production2.com/jasonwee/videoOnCloud.git
$ git remote -v
github https://github.com/jasonwee/videoOnCloud.git (fetch)
github https://github.com/jasonwee/videoOnCloud.git (push)
production https://production2.com/jasonwee/videoOnCloud.git (fetch)
production https://production1.com/jasonwee/videoOnCloud.git (push)

Though you can add more url for remote repositories, you can use set-url --add. If you notice, it won't be showing but you can check in .git/config to look at the url.
$ git remote set-url --add production https://production3.com/jasonwee/videoOnCloud.git
$ git remote -v
github https://github.com/jasonwee/videoOnCloud.git (fetch)
github https://github.com/jasonwee/videoOnCloud.git (push)
production https://production2.com/jasonwee/videoOnCloud.git (fetch)
production https://production1.com/jasonwee/videoOnCloud.git (push)
$ git remote set-url --delete production https://production3.com/jasonwee/videoOnCloud.git
$ git remote -v
github https://github.com/jasonwee/videoOnCloud.git (fetch)
github https://github.com/jasonwee/videoOnCloud.git (push)
production https://production2.com/jasonwee/videoOnCloud.git (fetch)
production https://production1.com/jasonwee/videoOnCloud.git (push)

Okay, because I don't host any remote production server, I will remove it.
$ git remote remove production
$ git remote -v
github https://github.com/jasonwee/videoOnCloud.git (fetch)
github https://github.com/jasonwee/videoOnCloud.git (push)

 

Sunday, May 18, 2014

Learning java native keyword

Today when I study into java code UnixNativeDispatcher.java, the code caught my attention. Snippet below
/**
* int openat(int dfd, const char* path, int oflag, mode_t mode)
*/
static int openat(int dfd, byte[] path, int flags, int mode) throws UnixException {
NativeBuffer buffer = NativeBuffers.asNativeBuffer(path);
try {
return openat0(dfd, buffer.address(), flags, mode);
} finally {
buffer.release();
}
}
private static native int openat0(int dfd, long pathAddress, int flags, int mode) throws UnixException;

So what happened actually? The static method openat is declared with package modifier which eventually do conversion before native private method openat0 is called. Note that the private method openat0 is actually a non java code execution.

native : Used in method declarations to specify that the method is not implemented in the same Java source file, but rather in another language.

Because it is executed in non java code, if you called native code, the associated method must be implemented in non java language, you can see example how is it done here with hello world example.

But with the native openat0, it came with the precompiled soname file, depending on which jvm you are using, to illustrate using this example. In the directory /usr/lib/jvm/jdk1.7.0_55/jre/lib/amd64/
$ ls /usr/lib/jvm/jdk1.7.0_55/jre/lib/amd64/
fxavcodecplugin-52.so libawt.so libgstreamer-lite.so libjava_crw_demo.so libjdwp.so libjsoundalsa.so libnpjp2.so libt2k.so
fxavcodecplugin-53.so libdcpr.so libhprof.so libjavafx-font.so libjfr.so libjsound.so libnpt.so libunpack.so
fxplugins.so libdeploy.so libinstrument.so libjavafx-iio.so libjfxmedia.so libkcms.so libprism-es2.so libverify.so
headless libdt_socket.so libj2gss.so libjavaplugin_jni.so libjfxwebkit.so libmanagement.so libsaproc.so libzip.so
jli libfontmanager.so libj2pcsc.so libjava.so libjpeg.so libmlib_image.so libsctp.so server
jvm.cfg libglass.so libj2pkcs11.so libjawt.so libjsdt.so libnet.so libsplashscreen.so xawt
libattach.so libgstplugins-lite.so libjaas_unix.so libJdbcOdbc.so libjsig.so libnio.so libsunec.so

As you may notice, there are many precompiled soname files come with jre. To check which soname loaded,
static {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
System.loadLibrary("nio");
return null;
}});
int flags = init();

hasAtSysCalls = (flags & HAS_AT_SYSCALLS) > 0;
}

obviously libnio.so is loaded, and to read the content of the soname file,
$ objdump -T /usr/lib/jvm/jdk1.7.0_55/jre/lib/amd64/libnio.so | grep openat
000000000000c350 g DF .text 00000000000000ac SUNWprivate_1.1 Java_sun_nio_fs_UnixNativeDispatcher_openat0
$ nm -D /usr/lib/jvm/jdk1.7.0_55/jre/lib/amd64/libnio.so | grep openat0
000000000000c350 T Java_sun_nio_fs_UnixNativeDispatcher_openat0

So that's it, I hope you learned java native keyword too!

Saturday, May 17, 2014

Getting familiar with Java FileChannel

When I was studying into lucene 4.8.0 codebase, one particular code that stumble upon was the use of FileChannel. So today, I'm spending time to play around this class FileChannel.

So you would ask, why use FileChannel instead of BufferedWriter?

From the FileChannel documentation:

In addition to the familiar read, write, and close operations of byte channels, this class defines the following file-specific operations:

Bytes may be read or written at an absolute position in a file in a way that does not affect the channel's current position.

A region of a file may be mapped directly into memory; for large files this is often much more efficient than invoking the usual read or write methods.

Updates made to a file may be forced out to the underlying storage device, ensuring that data are not lost in the event of a system crash.

Bytes can be transferred from a file to some other channel, and vice versa, in a way that can be optimized by many operating systems into a very fast transfer directly to or from the filesystem cache.

A region of a file may be locked against access by other programs.

That sounds interesting, and to understand better, we will start to write code using class FileChannel. Below is one I wrote and explanation come after.
import java.io.File;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.file.StandardOpenOption;

public class FileChannelTest {

public static void main(String[] args) throws IOException {

try {
File aFile = new File("test.txt");
FileChannel fc = FileChannel.open(aFile.toPath(), StandardOpenOption.CREATE, StandardOpenOption.WRITE, StandardOpenOption.READ);
System.out.println("initialized is Open " + fc.isOpen()); // true

String data = "hello orld";
ByteBuffer buf = ByteBuffer.allocate(data.length());
buf.put(data.getBytes());
buf.flip();

System.out.println("initial size " + fc.size()); // 0
System.out.println("initial position " + fc.position()); // 0
fc.write(buf);
System.out.println("after write size " + fc.size()); // 10
System.out.println("after write position " + fc.position()); //10

ByteBuffer dst = ByteBuffer.allocate(200);
fc.read(dst, 0);
System.out.println("initial read " + new String(dst.array(), "UTF-8")); // hello orld

ByteBuffer newData = ByteBuffer.wrap("world\ndelete me".getBytes());
fc.write(newData, 6);
fc.position(21);

dst.clear();
fc.read(dst, 0);
System.out.println("read second write " + new String(dst.array(), "UTF-8")); //hello world
//delete me
System.out.println("after second write size " + fc.size()); // 21
System.out.println("after second write pos " + fc.position()); // 21

fc.truncate(12);
System.out.println("after truncate size " + fc.size()); // 12
System.out.println("after truncate pos " + fc.position()); // 12
dst = ByteBuffer.allocate(200);
fc.read(dst, 0);
System.out.println("after truncate " + new String(dst.array(), "UTF-8"));

newData.clear();
newData = ByteBuffer.wrap("a new line of text\n".getBytes());
fc.write(newData);
System.out.println("after second write size " + fc.size()); // 31
System.out.println("after second write pos " + fc.position()); // 31

fc.force(true);
fc.close();

System.out.println("after close " + fc.isOpen()); // false

} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}

}

}

It's a simple single threaded class. So we start with a File object with a test file. Then we create FileChannel object where we create test.txt if is not exists, then write and read it.

To dip our toe into the water, we start by checking if the file Channel is open. In order to write, we need ByteBuffer. We construct a new ByteBuffer object with the data length and write the data into the buffer array. In order for the channel to write this ByteBuffer, you must call the method flip().

We checked what is the current FileChannel size and position. Initially, they are zero. After data is written, note that size and position has been increased to 10. So in order to check the file channel written, we can invoke the method read(), hence the next few statements in the code.

To hold the data read from the file channel object, we create a new ByteBuffer object called dst, with a capacity of 200, so we can fit 200 bytes of data. We read the file channel object starting from file position 0 into the dst object. As expected in the print out, hello orld is written into file channel object. Interesting! It means we can write based on position we want to specify and that is exactly the next few lines of code did, we write "world\ndelete me" using method write but with position specify. If you notice carefully, unlike method write(), write() did not update file channel position, hence we are setting it explicitly to advance to position 21, that is, last position of character in the file. With this second write, we change the original data (hello orld) to (hello world\ndelete me), this overwrite the existing string than appending.

We check the file channel object after second write, we corrected the typo in the string and the position and size is as expected (21). Now we truncate the file channel up to 12bytes. That is, we start to truncate from position 0 until 12, which the byte array containing "hello world\n" survive and the remaining will be truncated. so the remaining size is 12 and position is set to 12 as well. As verified from the print out, delete me is no longer exist in the file channel.

As the file position is set end of file, we can simulate append effect by just writing and that is exactly what the next lines of code did. To ensure data and metadata is flush to the block device, we called force with parameter true. We end this example with invoking close method and check after that file channel is no longer opened.

When file channel object is created, we added open option, StandardOpenOption.READ is because when read into the file channel, this bit has to be set or else you will get exception. That's it about learning FileChannel.

Friday, May 16, 2014

Learn and experiment with cassandra trigger

In cassandra 2.0, an experimental trigger was introduced and this seem exciting to bring cassandra into a whole new level. Today, by using cassandra 2.0.7 , we are going to learn cassandra trigger. But first, let's understand what conventional database trigger is.

Excerpt from wikipedia,

A database trigger is procedural code that is automatically executed in response to certain events on a particular table or view in a database. The trigger is mostly used for maintaining the integrity of the information on the database. For example, when a new record (representing a new worker) is added to the employees table, new records should also be created in the tables of the taxes, vacations and salaries.

So let's create a table in cassandra and then create a trigger for the table. We will do these execution via cqlsh and the example we are going to follow available in this link. Below are the steps I have taken from studying into the example trigger code.

1. build cassandra jar files in cassandra base directory.
2. build trigger-example.jar from trigger example directory.
3. upload trigger-example.jar to cassandra node directory in /etc/cassandra/triggers
4. copy InvertedIndex.properties to cassandra node directory in /etc/cassandra/
5. make cassandra aware of this jar and properties file addition via nodetool reloadtriggers
nodetool -h localhost reloadtriggers
5. repeat step 3 and 4 for all the nodes in the cluster.
6. create column family invertedindex via cqlsh.
7. create column family standard1 via cqlsh.
8. create trigger via cqlsh CREATE TRIGGER test1 ON "Keyspace1"."Standard1" USING 'org.apache.cassandra.triggers.InvertedIndex';
note that you can also drop trigger via command drop trigger test1 on "Keyspace1"."Standard1"

So that exciting part comes, when I tried to insert, the response keep on complaining key may not be empty, it is strange that we does specify the user_id as our key but it keep on giving error. So what went wrong?
cqlsh:keyspace1> insert into standard1 (user_id, age) values (124, 11);
Bad Request: Key may not be empty

TRACE [Thrift:5] 2014-05-12 22:17:02,492 QueryProcessor.java (line 153) Process org.apache.cassandra.cql3.statements.UpdateStatement@164b11c @CL.ONE
DEBUG [Thrift:5] 2014-05-12 22:17:02,493 Tracing.java (line 159) request complete
ERROR [Thrift:5] 2014-05-12 22:17:02,493 CustomTThreadPoolServer.java (line 219) Error occurred during processing of message.
java.lang.RuntimeException: Exception while creating trigger on CF with ID: d04577ab-ecc0-3f57-bb01-6febc9d27803
at org.apache.cassandra.triggers.TriggerExecutor.executeInternal(TriggerExecutor.java:167)
at org.apache.cassandra.triggers.TriggerExecutor.execute(TriggerExecutor.java:91)
at org.apache.cassandra.service.StorageProxy.mutateWithTriggers(StorageProxy.java:525)
at org.apache.cassandra.cql3.statements.ModificationStatement.executeWithoutCondition(ModificationStatement.java:542)
at org.apache.cassandra.cql3.statements.ModificationStatement.execute(ModificationStatement.java:526)
at org.apache.cassandra.cql3.QueryProcessor.processStatement(QueryProcessor.java:158)
at org.apache.cassandra.cql3.QueryProcessor.process(QueryProcessor.java:175)
at org.apache.cassandra.thrift.CassandraServer.execute_cql3_query(CassandraServer.java:1959)
at org.apache.cassandra.thrift.Cassandra$Processor$execute_cql3_query.getResult(Cassandra.java:4486)
at org.apache.cassandra.thrift.Cassandra$Processor$execute_cql3_query.getResult(Cassandra.java:4470)
at org.apache.thrift.ProcessFunction.process(ProcessFunction.java:39)
at org.apache.thrift.TBaseProcessor.process(TBaseProcessor.java:39)
at org.apache.cassandra.thrift.CustomTThreadPoolServer$WorkerProcess.run(CustomTThreadPoolServer.java:201)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1110)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:603)
at java.lang.Thread.run(Thread.java:722)
Caused by: java.lang.NullPointerException
at org.apache.cassandra.db.RowMutation.addOrGet(RowMutation.java:133)
at org.apache.cassandra.db.RowMutation.addOrGet(RowMutation.java:128)
at org.apache.cassandra.db.RowMutation.addOrGet(RowMutation.java:123)
at org.apache.cassandra.db.RowMutation.add(RowMutation.java:149)
at org.apache.cassandra.db.RowMutation.add(RowMutation.java:159)
at org.apache.cassandra.triggers.InvertedIndex.augment(InvertedIndex.java:46)
at org.apache.cassandra.triggers.TriggerExecutor.executeInternal(TriggerExecutor.java:159)
... 15 more
TRACE [Thrift:5] 2014-05-12 22:17:02,495 ThriftSessionManager.java (line 74) ClientState removed for socket ad

So I decided to go into further, and I got it to works after spending hours. Changes below.

1. change to lower letters for InvertedIndex.properties
$ cat /etc/cassandra/InvertedIndex.properties
keyspace=keyspace1
columnfamily=invertedindex

2. rebuild trigger-example.jar file with different augment method implementation and remember deploy this to every node in the cluster and execute command reloadtriggers using nodetool.
import org.apache.cassandra.utils.ByteBufferUtil;
import org.apache.cassandra.db.ArrayBackedSortedColumns;
import java.util.Collections;

public Collection<RowMutation> augment(ByteBuffer key, ColumnFamily update)
{
ColumnFamily extraUpdate = update.cloneMeShallow(ArrayBackedSortedColumns.factory, false);
extraUpdate.addColumn(new Column(update.metadata().comparator.fromString("v2"),
ByteBufferUtil.bytes(999)));
RowMutation rm = new RowMutation("keyspace1", key);
rm.add(extraUpdate);
return Collections.singletonList(rm);
}

3. drop both column family and recreate again, below are the schema.
cqlsh:keyspace1> desc table invertedindex;

CREATE TABLE invertedindex (
k int,
v1 int,
v2 int,
PRIMARY KEY (k)
) WITH
bloom_filter_fp_chance=0.010000 AND
caching='KEYS_ONLY' AND
comment='' AND
dclocal_read_repair_chance=0.000000 AND
gc_grace_seconds=864000 AND
index_interval=128 AND
read_repair_chance=0.100000 AND
replicate_on_write='true' AND
populate_io_cache_on_flush='false' AND
default_time_to_live=0 AND
speculative_retry='99.0PERCENTILE' AND
memtable_flush_period_in_ms=0 AND
compaction={'class': 'SizeTieredCompactionStrategy'} AND
compression={'sstable_compression': 'LZ4Compressor'};

cqlsh:keyspace1> desc table test_table;

CREATE TABLE test_table (
k int,
v1 int,
v2 int,
PRIMARY KEY (k)
) WITH
bloom_filter_fp_chance=0.010000 AND
caching='KEYS_ONLY' AND
comment='' AND
dclocal_read_repair_chance=0.000000 AND
gc_grace_seconds=864000 AND
index_interval=128 AND
read_repair_chance=0.100000 AND
replicate_on_write='true' AND
populate_io_cache_on_flush='false' AND
default_time_to_live=0 AND
speculative_retry='99.0PERCENTILE' AND
memtable_flush_period_in_ms=0 AND
compaction={'class': 'SizeTieredCompactionStrategy'} AND
compression={'sstable_compression': 'LZ4Compressor'};

and now when insert again into the cf, voila, 999 was auto created and no more exception in the log or cqlsh output!
cqlsh:keyspace1> select * from test_table;

(0 rows)

cqlsh:keyspace1> insert into test_table (k, v1) values (0, 0);
cqlsh:keyspace1> select * from test_table;

k | v1 | v2
---+----+-----
0 | 0 | 999

(1 rows)

Conclusion that we can draw is, since it is experimental, that means in the future, trigger is subject to many changes including API and chances that it could fail is higher ;-). It also need cassandra and java knowledge to build trigger at the mean time. Thus, you should not use this in production but that does not mean you cannot try this feature. In fact, cassandra would like to receive feedback  on the trigger to improve or make cassandra trigger production ready in the future.

That's it for this article, if you like, please go to the donation page to contribute back as funding will keep us continue to write in the future.