1
votes

We are considering using an In-Memory database (such as Apache Ignite) to deal with performance intense BI-like operations. So as a (very primitive) example, I filled Apache Ignite with 250.000 records from a csv-file (14 columns) and did some group-by operations. Previously, I also used the same data to do some performance-tests with MS SQL-Server.

Interestingly and unexpected, MS SQL-Server need about 0.25 seconds to perform this operations, while it takes 1-2 seconds with Apache Ignite.

1, I always was under the impression that Apache Ignite is not only a good option for distributed computing, but also leads to a performance gain compared to a conventional relational database due to its memory oriented architecture. Is that true? Why is it that slow in my example?

2, Did I use Apache Ignite in a wrong way or are there some additional tuning options that I should use?

Here is the source-code I used in my example:

  private static Connection conn = null;
  private static Statement stmt = null;
  private static ResultSet rs = null;

  private static void initialize() throws ClassNotFoundException, SQLException

  {

       // Register JDBC driver.
    Class.forName("org.apache.ignite.IgniteJdbcThinDriver");

    // Create database tables.
    stmt = conn.createStatement();

    // Create table
    stmt.executeUpdate("CREATE TABLE PIVOT_TEST (" +
    " REGION VARCHAR, COUNTRY VARCHAR, ITEM_TYPE VARCHAR, SALES_CHANNEL VARCHAR, ORDER_PRIORITY VARCHAR, ORDER_DATE VARCHAR, ORDER_ID VARCHAR PRIMARY KEY, "
    + "SHIP_DATE VARCHAR, UNITS_SOLD NUMERIC, UNIT_PRICE NUMERIC, UNIT_COST NUMERIC, TOTAL_REVENUE NUMERIC, TOTAL_COST NUMERIC, TOTAL_PROFIT NUMERIC )");

  }



  private static void fill() throws ClassNotFoundException, SQLException
  {
        // Register JDBC driver
        Class.forName("org.apache.ignite.IgniteJdbcThinDriver");

        // Populate table
        PreparedStatement stmt =
        conn.prepareStatement("COPY FROM 'LINK_TO_CSV_FILE'" +

        "INTO PIVOT_TEST (REGION , COUNTRY , ITEM_TYPE , SALES_CHANNEL , ORDER_PRIORITY , ORDER_DATE , ORDER_ID , SHIP_DATE , UNITS_SOLD , UNIT_PRICE , UNIT_COST , TOTAL_REVENUE , TOTAL_COST , TOTAL_PROFIT ) FORMAT CSV");
        stmt.executeUpdate();

        stmt = conn.prepareStatement("CREATE INDEX index_name ON PIVOT_TEST(COUNTRY)");
        stmt.executeUpdate();
  }



  private static void getResult() throws ClassNotFoundException, SQLException
  {
        // Register JDBC driver
        Class.forName("org.apache.ignite.IgniteJdbcThinDriver");

        // Get data
        stmt = conn.createStatement();

        rs =
            stmt.executeQuery("SELECT AVG(UNIT_PRICE) AS AVG_UNIT_PRICE, MAX(UNITS_SOLD) AS MAX_UNITS_SOLD, SUM(UNIT_COST) AS SUM_UNIT_COST, AVG(TOTAL_REVENUE) AS AVG_TOTAL_REVENUE , AVG(TOTAL_COST) AS AVG_TOTAL_COST, AVG(TOTAL_PROFIT) as AVG_TOTAL_PROFIT  FROM PIVOT_TEST GROUP BY COUNTRY;");

        retrieveResultSet();      

  }



  private static void retrieveResultSet() throws SQLException
  {
        while (rs.next())
        {
                for(int i=0; i<rs.getMetaData().getColumnCount(); i++)
                {
                    rs.getObject(i+1);
                }
        }
  }



  public static void main(String[] args) throws SQLException, ClassNotFoundException

  {

        Ignite ignite = null;

        try

        {

            //--------------------------------CONNECTION-------------------//

            IgniteConfiguration configuration = new IgniteConfiguration();

            ignite = Ignition.start(configuration);

            conn = DriverManager.getConnection("jdbc:ignite:thin://127.0.0.1/");

            initialize();

            fill();


            long endPrepTable = System.currentTimeMillis();

            getResult();

            long endGetResult = System.currentTimeMillis();

            System.out.println("Get Result (s)" + " " + (endGetResult - endPrepTable)*1.0/1000);

        }

        catch(Exception e)
        {
            e.printStackTrace();
        }

        finally
        {
          ignite.close();

          conn.close();

          rs.close();
        }

  }

Thank you for your help!

3

3 Answers

5
votes

There are several things to consider when Ignite is compared to a relational database:

  • Ignite SQL engine is optimized for multi-nodes deployments with the RAM as primary storage. Don't try to compare a single-node Ignite cluster to a relational database that was optimized for such configurations. Have a multi-nodes cluster deployed with a whole copy of data in RAM.
  • Take into account basic recommendations during data modeling and optimizations like affinity collocation, secondary indexes and others listed here.
  • Plus, keep in mind that relational databases leverage from local caching techniques and depending on the total data size, and a type of a query can complete some queries even faster than Ignite in a multi-node configuration. For instance, I've seen a SQL server completing a query below in 5 ms while Ignite single node cluster in 8 ms and 4-nodes cluster in 20 ms:

    SELECT * FROM Input i JOIN Party pr ON (pr.prt_id) = (i.mbr_id) order by i.input_id offset 0 limit 100

    It was expected because the data set size was around 64GB, and SQL Server could cache a lot in local RAM. Plus, the costs for intra-node communication affected the numbers for 4 nodes cluster in comparison to the single node one.

    To unleash the power of the distributed in-memory computing, preload more data to your cluster or/and force SQL Server to go to disk by checking more complicated queries like the one below:

    SELECT * FROM Input i INNER JOIN Product p ON (i.product_id) = (p.product_id) INNER JOIN Party pr ON (pr.prt_id) = (i.mbr_id) and (pr.session_id=i.session_id) WHERE I.PRODUCT_ID=5 and I.SOURCE_ID=6

    In my case, it took 510 seconds for SQL Server in the same configuration and 64GB of data to finish the query (it had to go to disk). Ignite's 4 nodes cluster finished in 32 seconds and 8-nodes cluster completed in 8 seconds.

0
votes

You could apply the following turning points:

  1. Use the collocated flag[1] :

    jdbc:ignite:thin://127.0.0.1;collocated=true

  2. Introduce a variable for rs.getMetaData().getColumnCount():

    int count = rs.getMetaData().getColumnCount(); while (rs.next()) { for(int i=0; i< count; i++) rs.getObject(i+1); }

[1] https://apacheignite-sql.readme.io/docs/jdbc-driver#section-parameters

[2] https://apacheignite.readme.io/docs/affinity-collocation#collocate-data-with-data

0
votes

As with any database, there are many ways to tune and optimise it. And Ignite is designed with different trade-offs than SQL Server -- it's not possible to guarantee that it'll be faster in every case.

Having said that, there is some documentation on improving performance.

Things to consider: quarter of a million records isn't that many. Ignite is optimised to work in a cluster where operations can be parallelised. With a single, "hard" query, you might need to increase queryParallelism otherwise you're going to be limited to a single thread in each node.

Of course you can also do things like EXPLAIN PLAN to make sure it's using the right indexes, etc. As with any optimisation exercise, it's as much an art as a science.