I. Background overview

As one of the applicable scenarios of VoltDB, transaction anti-fraud is a typical event-driven business. Its core is to absorb high-frequency transaction data and verify a series of complex anti-fraud rules for transactions one by one, and finally generate a score to judge the suspicious degree of transactions and send it to the downstream business system to trigger the transaction interception action. Anti-fraud rules involve a large number of indicators generated by analyzing historical transactions. Streaming calculation in VoltDB enables event analysis and decision-making based on locally stored rich context data, making real-time computing close to context data and gaining performance advantages.

Second, instance callback

Here we show how VoltDB implements a simple anti-fraud use case using a credit card swipe app. To make the code simpler and highlight VoltDB’s features, a subway card swipe is used instead of a financial transaction (such as a credit card swipe) to avoid introducing too much professional knowledge of financial business. At the same time, the transaction throughput generated by a busy metro system is significant and the anti-fraud rules defined are easier to understand. The detailed code can be accessed at github.com/ssomagani/e… In this application, the following scenarios are simulated:

  1. Multiple trains run between subway stations, generating train arrival events. See how to post data to VoltDB Topic, and how to consume it.
  2. Bus card recharge operation. From this scenario, you can see how a PROCEDURE containing custom business rules can be used to process data in a Topic, while a Stream object can be used to export data to a Topic, and a view can be used to count data flows in the Stream to generate real-time statistical reports. The view counts the Stream data in the Stream one by one and saves the results to the view, which is one of VoltDB’s ways of implementing streaming computing.
  3. Passengers swipe their cards to ride, generating high-frequency trading data. See how you can use VoltDB’s database client API to manipulate tables directly (as opposed to sending data to a Topic) and save transaction data. How to customize Anti-fraud verification rules with VoltDB’s Java Procedure and call Java Procedure for transaction verification and anti-fraud behavior. Let’s take a closer look at the process of running this use case in VoltDB.

2.1 Preparations

VoltDB provides a unified profile, in which the main features can be defined, such as persistence, high availability, security and so on. This section mainly introduces the features of VoltDB Topic related to this case. The following configuration enables the Topic service and port 9999 on the server to receive messages from the client.

<Topics enabled="true">
        <properties>
            <property name="port">9999</property>
            <property name="group.initial.rebalance.delay.ms">0</property>
            <property name="retention.policy.threads">1</property>
        </properties>
        <profiles>
            <profile name="retain_compact">
                <retention policy="compact" limit="2048" />
        </profile>
        </profiles>
    </Topics>
Copy the code

Launch VoltDB according to a specific configuration file. 3. Create a Topic. The purpose of Topic is mentioned in the code analysis later

CREATE Topic TRAINTOPIC execute procedure train_events.insert;
CREATE TOPIC RECHARGE execute procedure RechargeCard;
CREATE TOPIC using stream CARD_ALERT_EXPORT properties(topic.format=avro);
create topic using stream FRAUD properties(topic.format=avro,consumer.keys=TRANS_ID);
Copy the code

4. Create data table When processing real-time event flow, you can make full use of the underlying database engine, make full use of local relational data for data analysis, and get anti-fraud business indicators. In this example, the following tables and views will be created (omitting the specific DDL)

5. Initialize data Initialize sites and trains from CSV files using VoltDB’s data import feature

csvloader --file $PROJ_HOME/data/redline.csv --reportdir log stations
csvloader --file $PROJ_HOME/data/trains.csv --reportdir log trains
Copy the code

2.2 Code analysis – Train operation

In this scenario, the client simulates eight trains running between 17 stations, generating inbound events and sending them to the Topic. Due to the short time set for the train to exit and exit the station (in microseconds), high-frequency event flow will be generated. On the server, VoltDB Completed: 1. Message Reception 2. Message consumption 3. Record the train arrival event into the database. On the client, generate multiple train arrival events through The Java class TrainProducer and send the events to VoltDB Topic. TrainProducer’s execution commands are as follows:

java metro.pub.TrainProducer localhost:9999 TRAINTOPIC 8
Copy the code

The TrainProducer class accepts four parameters:

  1. . Specifies the VoltDB server port that receives train arrivals and departures. This assumes that client code and VoltDB are running on the same machine, and the Topic listening port we specified in the VoltDB profile is 9999.
  2. Specify the VoltDB broker
  3. Specify the Topic name to which the data is sent.
  4. Specifies the number of trains to simulate.

Analyze the main method of TrainProducer. The main method generates 10 threads, executes the publish () method every 50 milliseconds, and sends the train in and out of the station time to Topic “TRAINTOPIC”.

public static void main(String[] args) {
        ScheduledExecutorService EXECUTOR = Executors.newScheduledThreadPool(10);
        TrainProducer producer = new TrainProducer(args[0], args[1], Integer.parseInt(args[2]));
        System.out.println("Scheduling trains");
        EXECUTOR.scheduleAtFixedRate (
                () -> {
                    producer.publish(producer.getNewEvents());
                }, 1, 50, MILLISECONDS);
    }
Copy the code

Follow the code to find the definition of Producer, which is actually the native KafkaProducer, so see VoltDB Topic fully compatible with the Kafka API. Brokers is the upload parameter localhost:9999 in main, so the data generated by producer.getnewevents () above will be sent to VoltDB Topic.

private Producer<String, TrainEvent> createProducer() {
        Properties props = new Properties();
        props.put("bootstrap.servers", brokers);
        props.put("acks", "all");
        props.put("retries", 0);
        props.put("batch.size", 16384);
        props.put("linger.ms", 1);
        props.put("buffer.memory", 33554432);
        props.put("key.serializer",
           "org.apache.kafka.common.serialization.StringSerializer");

        props.put("value.serializer",
           "metro.serde.TrainEventSer");
        Producer<String, TrainEvent> producer = new KafkaProducer
           <String, TrainEvent>(props);
        return producer;
    }
Copy the code

The Publish method sends a message generated by the producer.getnewevents () method. It’s worth taking a look at the Stations class, which defines 17 train Stations, including each Station’s travel time to the next Station (station. nextStnDuration) and its stop duration (station. stnWaitDuration) in microseconds. All trains will run through these stations in turn.

static HashMap<Integer, Station> idToStationMap = new HashMap<>(); static { idToStationMap.put(1, new Station(1, 1200000, 450000)); idToStationMap.put(2, new Station(2, 1050000, 250000)); idToStationMap.put(3, new Station(3, 850000, 300000)); idToStationMap.put(4, new Station(4, 900000, 350000)); idToStationMap.put(5, new Station(5, 500000, 260000)); idToStationMap.put(6, new Station(6, 950000, 190000)); idToStationMap.put(7, new Station(7, 450000, 130000)); idToStationMap.put(8, new Station(8, 200000, 280000)); idToStationMap.put(9, new Station(9, 200000, 110000)); idToStationMap.put(10, new Station(10, 450000, 300000)); idToStationMap.put(11, new Station(11, 550000, 200000)); idToStationMap.put(12, new Station(12, 550000, 200000)); idToStationMap.put(13, new Station(13, 800000, 150000)); idToStationMap.put(14, new Station(14, 950000, 100000)); idToStationMap.put(15, new Station(15, 1000000, 130000)); idToStationMap.put(16, new Station(16, 1200000, 220000)); idToStationMap.put(17, new Station(17, 1500000, 500000)); } public static class Station { public final int stationId; public final int nextStnDuration; public final int stnWaitDuration; public Station(int stationId, int nextStnDuration, int stnWaitDuration) { this.stationId = stationId; this.nextStnDuration = nextStnDuration; this.stnWaitDuration = stnWaitDuration; }}Copy the code

So the main logic of getNewEvents is to first randomly set the train to depart from any station, Then call next () to determine which Station each train is currently running to according to the system’s current time and the Station’s station. nextStnDuration, station. stnWaitDuration. Then judge that the train has entered the next station, and put the trainEvent into records for sending to Topic. (Note: train scheduling is not the focus of this sample, so the next method does not consider collision of trains and assumes that there are enough tracks between stations for multiple trains to run in parallel).

public List<TrainEvent> getNewEvents() { ArrayList<TrainEvent> records = new ArrayList<>(); for(TrainEvent trainEvent : idToTrainMap.values()) { LastKnownLocation prevLoc = trainEvent.location; LastKnownLocation curLoc = next(prevLoc, LocalDateTime.now()); if(! prevLoc.equals(curLoc)) { trainEvent = new TrainEvent(trainEvent.trainId, curLoc); idToTrainMap.put(trainEvent.trainId, trainEvent); records.add(trainEvent); } } return records; }Copy the code

Topic TRAINTOPIC is defined as follows, train_events.insert is the default stored procedure created by VoltDB for the table named [tablename].insert. Topic is used with a stored procedure train_events. Insert consumes the trainEvent data in the TRAIN_Events table.

CREATE Topic TRAINTOPIC execute procedure train_events.insert;
Copy the code

2.23 Code analysis – Recharge bus card

In this scenario, the client will finish sending the recharge message. On the server, VoltDB completed:

  1. The message received
  2. News consumption
  3. The recharge data is updated to the database using custom logic processing messages
  4. Generates a recharge message and writes the data to the stream object
  5. Create a view based on the Stream object to generate real-time recharge statistics reports
  6. Post top-up messages from the stream to a Topic for subsequent (outside VoltDB) data processing logic to consume. For example, it is consumed by Spark because of the subsequent batch processing logic.

On the client side, Java class CardsProducer is executed to initialize the bus card record and write the record into the database table. Then generate card RECHARGE events randomly and send events to Topic RECHARGE. CardsProducer’s execution commands are as follows:

java metro.pub.CardsProducer --mode=recharge --servers=localhost:9999 --Topic=RECHARGE
Copy the code

The CardsProducer class accepts three arguments:

  1. Execution mode, which specifies whether to initialize the bus card record or generate a recharge event.

  2. Specify the VoltDB broker

  3. Analyze the main method of CardsProducer. The main method generates 10 threads, publish () every 5 milliseconds, and send the time of train in and out of the station to the Topic “RECHARGE”.

    public static void main(String[] args) throws IOException { CONFIG.parse(“CardsProducer”, args); if(CONFIG.mode.equals(“new”)) { genCards(CONFIG); return; } ScheduledExecutorService EXECUTOR = Executors.newScheduledThreadPool(10); CardsProducer producer = new CardsProducer(CONFIG.servers, CONFIG.Topic); System.out.println(“Recharging Cards”); EXECUTOR.scheduleAtFixedRate ( () -> { producer.publish(producer.getRechargeActivityRecords(1)); }, 1, 5, MILLISECONDS); }

Just like TrainProducer, CardsProducer is KafkaProducer. GetRechargeActivityRecords method used to generate a random top-up event, including the card number, top-up amount and prepaid phone site. This is done every 5 milliseconds.

public List<CardEvent> getRechargeActivityRecords(int count) { final ArrayList<CardEvent> records = new ArrayList<>(); int amt = (ThreadLocalRandom.current().nextInt(18)+2)*1000; int stationId = ThreadLocalRandom.current().nextInt(1, 18); ThreadLocalRandom.current().ints(count, 0, CONFIG.cardcount).forEach((cardId) -> { records.add(new CardEvent(cardId, amt, stationId)); }); return records; }Copy the code

In this scenario, the client-side code is very simple and ends there. For more logic defined on the server side, see below. Topic is used to receive recharge events and is defined as follows:

CREATE TOPIC RECHARGE execute procedure RechargeCard;
Copy the code

The RechargeCard is used to consume data in a Topic, and the RechargeCard is a Java Procedure that customizes the business logic through Java + SQL. Java Procedure, which VoltDB often uses when dealing with streaming data, is a Java class that runs on the VoltDB server, rather than client code. It needs to be pre-compiled into a JAR package (like procs.jar) and loaded into the VoltDB Java runtime environment. Then use the following DDL definitions. The RechargeCard can only be referenced in the CREATE TOPIC above once it is defined.

sqlcmd --query="load classes $PROJ_HOME/dist/procs.jar"
CREATE PROCEDURE PARTITION ON TABLE cards COLUMN card_id PARAMETER 0 FROM CLASS metro.cards.RechargeCard;
Copy the code

Let’s take a look at the logic in RechargeCard, focusing on how Java business logic can be combined with SQL. It defines the run() method and four SQL statements. The RechargeCard consumes data from Topic RECHARGE, deserializes it, and passes the data (i.e., RECHARGE events) as arguments to the Run () method, which is the procedure entry method. VoltQueueSQL Is VoltDB’s server-side API for executing SQL and returning results. Sql getCard and getStationName firstly verify the validity of recharge event according to the data obtained from Topic. If there is no corresponding recharge station or bus card record in the database, execute Sql exportNotif to write an error message. Otherwise, update VoltDB database to the bus card, increase the balance, and execute SQL exportNotif to write a success message.

public class RechargeCard extends VoltProcedure { public final SQLStmt updateBalance = new SQLStmt("UPDATE cards SET balance = balance + ? WHERE card_id = ? AND card_type = 0"); public final SQLStmt getCard = new SQLStmt("SELECT * from cards WHERE card_id = ?" ); public final SQLStmt exportNotif = new SQLStmt("INSERT INTO CARD_ALERT_EXPORT values (? , NOW, ? ,? ,? ,? ,? ,?) "); public final SQLStmt getStationName = new SQLStmt("SELECT name FROM stations WHERE station_id = ?" ); public long run(int cardId, int amt, int stationId) { voltQueueSQL(getStationName, stationId); voltQueueSQL(getCard, cardId); String station = "UNKNOWN"; final VoltTable[] results = voltExecuteSQL(); if(results.length == 0) exportError(cardId, station); VoltTable stationResult = results[0]; if(stationResult.advanceRow()) station = stationResult.getString(0); VoltTable card = results[1]; if(card.advanceRow()) { voltQueueSQL(updateBalance, amt, cardId); String name = card.getString(5); String phone = card.getString(6); String email = card.getString(7); int notify = (int) card.getLong(8); voltQueueSQL(updateBalance, amt, cardId); voltQueueSQL(exportNotif, cardId, station, name, phone, email, notify, "Card recharged successfully"); voltExecuteSQL(true); } else { exportError(cardId, station); } return 0; } private void exportError(int cardId, String station) { exportError(cardId, station, "", "", "", 0, "Could not locate details of card for recharge"); } private void exportError(int cardId, String station, String name, String phone, String email, int notify, String msg) { voltQueueSQL(exportNotif, cardId, station, name, phone, email, notify, msg); voltExecuteSQL(true); }}Copy the code

“ExportNotif” is defined as follows, where CARD_ALERT_EXPORT is VoltDB’s Stream object, a data pipeline through which insert data flows one by one.

public final SQLStmt exportNotif = new SQLStmt("INSERT INTO CARD_ALERT_EXPORT values (? , NOW, ? ,? ,? ,? ,? ,?) ");Copy the code

You can add data processing logic to CARD_ALERT_EXPORT to achieve the effect of flow calculation. In this scenario, a view is simply created on the Stream to generate real-time statistics reports. A view is defined as follows:

CREATE VIEW card_export_stats(card_id, station_name, rechargeCount) AS 
	SELECT card_id, station_name, count(*) from CARD_ALERT_EXPORT 
	GROUP BY card_id, station_name;
Copy the code

Finally, we define that the data in Stream eventually flows to another Topic, which can be consumed by big data products other than VoltDB to complete downstream data processing logic.

CREATE TOPIC using stream CARD_ALERT_EXPORT properties(Topic.format=avro);
Copy the code

2.4 Code analysis – Passenger swipes card to ride

In this scenario, the client randomly generates a large number of passenger card entry records and sends them to the database for processing. The server completes the following operations: 1. Performs a series of anti-fraud operations, such as verifying card information, card balance, and whether swipes are stolen. 2. Record all credit card swipes in a spreadsheet. And the card swiping events of insufficient balance and compound fraud logic are published to different topics for subscription by other downstream systems. Unlike the other two scenarios, the Class RidersProducer directly connects to the VoltDB database and writes data to a table, rather than sending it to the VoltDB Topic, by executing the Java class RidersProducer on the client. Lots of ways to show VoltDB. VoltDB connectToOneServerWithRetry use VoltDB client API connection specified IP database.

void connectToOneServerWithRetry(String server, Client client) {
        int sleep = 1000;
        while (true) {
            try {
                client.createConnection(server);
                break;
            }
            catch (Exception e) {
                System.err.printf("Connection failed - retrying in %d second(s).\n", sleep / 1000);
                try { Thread.sleep(sleep); } catch (Exception interruted) {}
                if (sleep < 8000) sleep += sleep;
            }
        }
        System.out.printf("Connected to VoltDB node at: %s.\n", server);
    }
Copy the code

The RidersProducer class creates 100 threads that execute getEntryActivityRecords every 200 milliseconds in the runBenchmark method. GetEntryActivityRecords randomly generates a passenger arrival and ride record, including the card number, current time, arrival station ID, etc

private static final ScheduledExecutorService EXECUTOR = Executors.newScheduledThreadPool(100); public void runBenchmark() throws Exception { int microsPerTrans = 1000000/RidersProducer.config.rate; EXECUTOR.scheduleAtFixedRate ( () -> { List<Object[]> entryRecords = getEntryActivityRecords(config.cardcount); // Generate a random inbound record call(config.cardEntry, entryRecords); // Send data to VoltDB database}, 10000, microsPerTrans, MICROSECONDS); } public static List<Object[]> getEntryActivityRecords(int count) { final ArrayList<Object[]> records = new ArrayList<>(); long curTime = System.currentTimeMillis(); ThreadLocalRandom.current().ints(1, 0, count).forEach((cardId) -> { records.add(new Object[] {cardId, curTime, Stations.getRandomStation().stationId, ENTER.value, 0}); }); return records; }Copy the code

The call method is then called to send the data Records to the database for processing. The Call method is defined as follows. CallProcedure is VoltDB’s client API, which is used to send data to the procedure named by the specified name for processing. It can be called in two ways: synchronous and asynchronous IO. In this case, the BenchmarkCallback is customized.

protected static void call(String proc, Object[] args) { try { client.callProcedure(new BenchmarkCallback(proc, args), procName, args); } catch (IOException e) { e.printStackTrace(); }}Copy the code

The Call method sends the data to procedure, with the procedure name specified by the following code. Let’s look at the specific logic in procedure.

@Option(desc = "Proc for card entry swipes")
        String cardEntry = "ValidateEntry";
Copy the code

Procedure ValidateEntry is partially defined, starting with six SQL definitions.

Public final SQLStmt checkCard = new SQLStmt("SELECT enabled, card_type, balance, Expires, name, phone, email, notify FROM cards WHERE card_id = ? ;" ); Public final SQLStmt chargeCard = new SQLStmt("UPDATE cards SET balance =? WHERE card_id = ? ;" ); Public final SQLStmt checkStationFare = new SQLStmt("SELECT fare, name FROM stations WHERE station_id =? ;" ); Public final SQLStmt insertActivity = new SQLStmt("INSERT INTO card_events (card_id, date_time, station_id, activity_code, amount, accept) VALUES (? ,? ,? ,? ,? ,?) ;" ); // Again using the stream object card_alert_export, Public final SQLStmt exportActivity = new SQLStmt("INSERT INTO card_alert_export (card_id, export_time, station_name, name, phone, email, notify, alert_message) VALUES (? ,? ,? ,? ,? ,? ,? ,?) ;" ); Public final SQLStmt publishFraud = new SQLStmt("INSERT INTO fraud (trans_id, card_id, date_time, station, activity_type, amt) values (? ,? ,? ,? ,? ,?) ");Copy the code

It’s worth noting that the fraud used in the last SQL above is another stream object, which is used to insert card fraud events. The card fraud defined in the DDL will eventually be published in VoltDB Topic for downstream processing of product consumption.

CREATE STREAM FRAUD partition on column CARD_ID (
  TRANS_ID varchar not null,
  CARD_ID integer not null,
  DATE_TIME timestamp not null,
  STATION integer not null,
  ACTIVITY_TYPE TINYINT not null,
  AMT integer not null
);
create Topic using stream FRAUD properties(Topic.format=avro,consumer.keys=TRANS_ID);
Copy the code

As mentioned earlier, the Run method is the entry method to PROCEDURE, which is automatically called when VoltDB runs PROCEDURE. The records passed by the previous client are passed to the run method for processing one by one. The run method is defined as follows

public VoltTable run(int cardId, long tsl, int stationId, byte activity_code, Int amt) throws VoltAbortException {// Query whether the bus card exists voltQueueSQL(checkCard, EXPECT_ZERO_OR_ONE_ROW, cardId); VoltQueueSQL (checkStationFare, EXPECT_ONE_ROW, stationId); VoltTable[] checks = voltExecuteSQL(); VoltTable cardInfo = checks[0]; VoltTable stationInfo = checks[1]; byte accepted = 0; If (cardinfo.getrowcount () == 0) { VoltQueueSQL (insertActivity, cardId, TSL, stationId, ACTIVITY_ENTER, AMT, ACTIVITY_ENTER); voltExecuteSQL(true); // Returns a rejected message to the client. return buildResult(accepted,"Card Invalid"); } // If the card exists, fetch the card information. cardInfo.advanceRow(); Int enabled = (int) cardinfo.getLong (0); int cardType = (int)cardInfo.getLong(1); Int balance = (int) cardinfo.getLong (2); TimestampType expires = cardInfo.getTimestampAsTimestamp(3); String owner = cardInfo.getString(4); String phone = cardInfo.getString(5); String email = cardInfo.getString(6); int notify = (int)cardInfo.getLong(7); // Query the stationinfo.advancerow (); Int fare = (int) stationinfo.getLong (0); String stationName = stationInfo.getString(1); TimestampType ts = new TimestampType(TSL); If (enabled == 0) {return buildResult(accepted,"Card Disabled"); // If (enabled == 0) {return "this Card is not available"; } if (cardType == 0) {if (balance > fare) {// if (balance > fare) {//isFrand is an anti-fraud policy, If (isFraud(cardId, ts, stationId)) { Record type "Fraudulent Card Charge" voltQueueSQL(insertActivity, cardId, TS, stationId, ACTIVITY_ENTER, fare, ACTIVITY_FRAUD); // And write the fraud story into stream, which will eventually be published on VoltDB Topic. See STREAM FRAUD to DDL definition voltQueueSQL(publishFraud, generateId(cardId, TSL), cardId, TS, stationId, ACTIVITY_ENTER, AMT); voltExecuteSQL(true); Return buildResult(0, "Fraudulent ") Return voltQueueSQL(chargeCard, balance-fare, cardId); return voltQueueSQL(chargeCard, balance-fare, cardId); // Record normal card event voltQueueSQL(insertActivity, cardId, ts, stationId, ACTIVITY_ENTER, fare, ACTIVITY_ACCEPTED); voltExecuteSQL(true); Return buildResult(1, "Remaining Balance: "+ intToCurrency(balance-fare)); return buildResult(1, "Remaining Balance:" + intToCurrency(balance-fare)); }} else {// If the card balance is insufficient, record the card failure event. voltQueueSQL(insertActivity, cardId, ts, stationId, ACTIVITY_ENTER, 0, ACTIVITY_REJECTED); if (notify ! = 0) {// Again use the stream object card_alert_export, VoltQueueSQL (exportActivity, cardId, getTransactionTime().getTime(), stationName, owner, phone, email, voltQueueSQL(exportActivity, cardId, getTransactionTime(). notify, "Insufficient Balance"); } voltExecuteSQL(true); Return "Insufficient balance" message to a client. Return buildResult(0,"Card has insufficient Balance: "+intToCurrency(balance)); }}}Copy the code

In the code above there is an isFraud method to determine if a card has been fraudulently swiped. Some simple anti-fraud rules are defined here

  1. If the same card is swiped more than once in a second, it is considered fraudulent. Since it is impossible to swipe cards at such short intervals, it is possible that multiple counterfeit cards are swiped simultaneously.

  2. The same card has been swiped at five or more stations in the past hour. Suppose this is also thought to be due to multiple counterfeit cards swiping at the same time.

  3. The same card has been swiped into the station more than 10 times in the past hour. Too many times in and out of the station, suspended for a period of time.

    The isFraud method implements the above anti-fraud rules based on the data in the current card swipe records combined with the historical records in the database. Historical card swipes are stored in the card_events table, and a view is created based on this table to count whether or not each card has been swiped in a single second. CREATE VIEW CARD_HISTORY_SECOND as select card_id, TRUNCATE(SECOND, date_time) scnd from card_events group by card_id, scnd; Public final SQLStmt cardHistoryAtStations = new SQLStmt(“SELECT activity_code, COUNT(DISTINCT station_id) AS stations ” + “FROM card_events ” + “WHERE card_id = ? AND date_time >= DATEADD(HOUR, -1, ?) ” + “GROUP BY activity_code;” );

    public final SQLStmt cardEntries = new SQLStmt(
    "SELECT activity_code " +
    "FROM card_events " +
    "WHERE card_id = ? AND station_id = ? AND date_time >= DATEADD(HOUR, -1, ?) " +
    "ORDER BY date_time;"
    );
    
    public final SQLStmt instantaneousCardActivity = new SQLStmt(
            "SELECT count(*) as activity_count "
            + "FROM CARD_HISTORY_SECOND "
            + "WHERE card_id = ? "
            + "AND scnd = TRUNCATE(SECOND, ?) "
            + "GROUP BY scnd;"
            );
    Copy the code

    public boolean isFraud(int cardId, TimestampType ts, int stationId) { voltQueueSQL(instantaneousCardActivity, cardId, ts); voltQueueSQL(cardHistoryAtStations, cardId, ts); voltQueueSQL(cardEntries, cardId, stationId, ts); final VoltTable[] results = voltExecuteSQL(); final VoltTable cardInstantaneousActivity = results[0]; final VoltTable cardHistoryAtStationisTable = results[1]; final VoltTable cardEntriesTable = results[2]; // If the card has been swiped once within a second, Returns true while (cardInstantaneousActivity advanceRow ()) {if (cardInstantaneousActivity. GetLong (” activity_count “) > 0) { return true; }}

    while (cardHistoryAtStationisTable.advanceRow()) { final byte activity_code = (byte) cardHistoryAtStationisTable.getLong("activity_code"); final long stations = cardHistoryAtStationisTable.getLong("stations"); If (Activity_code == ACTIVITY_ENTER) {return true if (Stations >= 5) {return true; } } } byte prevActivity = ACTIVITY_INVALID; int entranceCount = 0; while (cardEntriesTable.advanceRow()) { final byte activity_code = (byte) cardHistoryAtStationisTable.getLong("activity_code"); if (prevActivity == ACTIVITY_INVALID || prevActivity == activity_code) { if (activity_code == ACTIVITY_ENTER) { prevActivity = activity_code; entranceCount++; } else { prevActivity = ACTIVITY_INVALID; }}} // Returns true if there have been 10 consecutive card swipes in the past hour. if (entranceCount >= 10) { return true; } return false; }Copy the code

Do you see VoltDB? Do it now! Welcome private letter, and more partners to discuss together.

About VoltDB VoltDB supports strong ACID and real-time intelligent decision making apps to enable connected worlds. No other database product like VoltDB can fuel an app that demands a combination of low latency, massive scale, high concurrency and accuracy at the same time. Founded by 2014 Turing Prize winner Dr Mike Stonebraker, VoltDB has redesigned relational databases to address today’s growing real-time manipulation and machine learning challenges. Dr. Stonebraker has been researching database technology for more than 40 years and has brought many innovations in fast data, streaming data and in-memory databases. During VoltDB’s development, he realized the full potential of using in-memory transaction database technology to mine streaming data, not only to meet the latency and concurrency requirements of processing data, but also to provide real-time analysis and decision-making. VoltDB is a trusted name in the industry and has worked with leading organisations such as Nokia, Financial Times, Mitsubishi Electric, HPE, Barclays and Huawei.