Introduction
Common concerns developers might have when doing a database migration including having to remodel the existing database to fit with a NoSQL model. Another concern is having to figure a way to fetch and rearrange the relational data into the new database. Luckily in this post we will show a brief example of how to perform a migration of MySQL database to a new NoSQL GridDB database.
Industry Example
All of the data for our migration will relate to the maintenance of a Photovoltaic site, PV site . A PV site is an industrial site equipped with hundreds to thousands of solar panels that collect power and transmit it to a power grid. A PV site can also be known as a solar farm. There could be up to thousands of internet-capable sensors involved in this process. Data reports from these sensors could include measurements such as voltage readings and alerts. An industrial site like this could scale to many different sizes and involve various types of devices.
Depending on the scenario, fast performance and reliability might be needed as well. This makes GridDB with its high performance, scalability, and flexibility the best database to migrate to for this PV site.
Initial MySQL Database
The MySQL database for this site will have 4 Tables. These 4 tables will translate into 4 container schemas in GridDB. These schemas will be implemented as 4 Java classes since we will be performing the migration in Java.
The tables and their GridDB schemas are listed below:
- Facilities: Contains information and specifications for the facilities of the PV site.
- Sensor: Stores general information like sensor-type and name on the PV site’s sensors
- Readings: Stores the timestamps and values of the measurements recorded by the PV site’s sensors
- Alerts: Stores information related to alerts and notification sent by the sensors
CREATE TABLE IF NOT EXISTS facilities ( facilityId VARCHAR(40) NOT NULL, name VARCHAR(60) NOT NULL, specifications BLOB, PRIMARY KEY (facilityId) );
public class Facility{ @RowKey public String facilityId; public String name; public Blob specifications; }
CREATE TABLE IF NOT EXISTS sensors ( sensorId VARCHAR(40) NOT NULL PRIMARY KEY (sensorId), facilityId VARCHAR(40) NOT NULL, name VARCHAR(60) NOT NULL, type VARCHAR(30) NOT NULL, FOREIGN KEY (facilityId) REFERENCES facilities(facilityId) ON DELETE CASCADE ON UPDATE CASCADE );
//Row schema class similar to the 'Sensors' table public class Sensor { @RowKey public String sensorId; public String name; public String type; }
CREATE TABLE IF NOT EXISTS readings ( record_id INT AUTO_INCREMENT PRIMARY KEY(record_id), sensorId VARCHAR(40) NOT NULL, tstamp TIMESTAMP NOT NULL, value DECIMAL(19,4) NOT NULL, status VARCHAR(255) NOT NULL, FOREIGN KEY (sensorId) REFERENCES sensors(sensorId) ON DELETE CASCADE ON UPDATE CASCADE );
//Row schema class similar to the 'Readings' table public class Reading { @RowKey public Date tstamp; public double value; public String status; }
CREATE TABLE IF NOT EXISTS alerts ( id INT AUTO_INCREMENT PRIMARY KEY (id), tstamp TIMESTAMP NOT NULL, facilityId VARCHAR(40) NOT NULL, sensorId VARCHAR(40) NOT NULL, level INT NOT NULL, detail VARCHAR(255) NOT NULL, FOREIGN KEY (facilityId) REFERENCES facilities(facilityId) ON DELETE CASCADE ON UPDATE CASCADE, FOREIGN KEY (sensorId) REFERENCES sensors(sensorId) ON DELETE CASCADE ON UPDATE CASCADE );
//Row schema class similar to 'alerts' table public class Alert { @RowKey public int alertId; public String facilityId; public String sensorId; public Date tstamp; public int level; public String detail; }
Translating Tables to Containers
When translating relational data tables into the key-container model of GridDB, it is important to which tables should have a one-to-many-relation. This can involve splitting a table into many containers. Mapping one table to many containers is a way of improving scalability, especially for timeseries data. To elaborate, one container might have many rows. Each row key in each row will also be the name of other containers in GridDB. This allows for containers to relate to each other in a scalable way.
To elaborate, a row in the facilities
table corresponds to an individual facility in the PV site. Each facility holds many different sensors.
Now that we have our data model and schemas for GridDB created, we can now do the actual data migration. The approach we will use is to select all the rows from each table in MySQL and create a new row in GridDB and insert it into a GridDB container. We will use the JDBC
driver to access the MySQL data and the GridDB Java API to connect to GridDB. All the data for the PV site is held in a database named pv
in MySQL.
Connect to GridDB and MySQL in Java
// Connect to 'pv' database in MySQL Class.forName("com.mysql.jdbc.Driver"); Connection connection = \ DriverManager.getConnection("jdbc:mysql://localhost/pv?user=root&password=PASSWORD"); //(snip) //Connect to GridDB Cluster Properties properties = new Properties(); properties.setProperty("notificationAddress","239.0.0.1"); properties.setProperty("notificationPort","31999"); properties.setProperty("clusterName","defaultCluster"); properties.setProperty("user","admin"); properties.setProperty("password","admin"); gridstore = GridStoreFactory.getInstance().getGridStore(properties);
The first table to migrate from MySQL will be the facilites
table. We will migrate the facilities table to a Collection
container of the same name. It will have a similar schema to the MySQL table. As a way to improve scalability, we will create a Collection
(named with the facility’s facility id) with a schema similar to sensors
table from MySQL. Each facility container will be responsible storing the sensors for that facility.
Collection<String,Facility> collection = gridstore.putCollection("facilities",Facility.class);
To translate a row from MySQL to GridDB, simply fetch each row field from MySQL and map it to the corresponding column in GridDB. Translating Blob types in Java are not so intuitive but an example is provided below on how to translate a Blob data from MySQL to GridDB.
Statement statement = connection.createStatement(); //Obtain all rows from the 'facilities' table in MySQL ResultSet resultSet = statement.executeQuery("SELECT * FROM pv.facilities"); //For every row in the 'facilities' table while(resultSet.next()){ //Create a GridDB row for each MySQL row Facility facility = new Facility(); //Set facilityId column in GridDB to facilityId in MySQL column facility.facilityId = resultSet.getString("facilityId"); //First search if there is an existing Collection of that has the facilityId as its name. //If it doesn't exist, create it Collection<String,Sensor> sensorCollection = \ gridstore.getCollection(facility.facilityId,Sensor.class); if(sensorCollection == null){ gridstore.putCollection(facility.facilityId,Sensor.class); } //Set 'name' column in GridDB row to 'name' in MySQL row facility.name = resultSet.getString("name"); InputStream stream = null; //Stream to Create GridDB Blob Object ByteArrayOutputStream byteStream = new ByteArrayOutputStream(); try { //Get Blob column of MySQL row as a Binary stream stream = new BufferedInputStream(resultSet.getBinaryStream("specifications")); //Take binary stream and use it as an InputStream byte buffer[] = new byte[1024]; while(stream.read(buffer) != -1){ //Write blob data from MySQL to buffer stream for GridDB byteStream.write(buffer); } } finally { if(stream != null){ stream.close(); } } //Create GridDB Blob value from Inputstream facility.specifications = new SerialBlob(byteStream.toByteArray()); //Insert new row into GridDB container for facilites collection.put(facility.facilityId,facility);
The second table to migrate is the sensors
from MySQL. Each row in this table has a FOREIGN KEY
column, facilityId
. This column details which facility a sensor belongs to. When we migrate this row into GridDB, we will get that column value from the row and retrieve the Collection
container that corresponds to that facility. We then create a new Sensor
row from the rest of the column values in the MySQL row and insert it into the container. Just like the one-to-many mapping for facilities, we will create a similar mapping with sensors. Each sensor will get its own TimeSeries
container for storing the sensor’s measurements. (Its sensor id will be the Timeseries’s name).
//Get all rows from the 'sensors' table in MySQL ResultSet resultSet = statement.executeQuery("SELECT * FROM pv.sensors"); //For every in 'sensors' table while(resultSet.next()){ //Create a new row for Sensor storing container Sensor sensor = new Sensor(); //Get facilityId to find the corresponding Collection container in GridDB String facilityId = resultSet.getString("facilityId"); //Map sensorId, name, and type columns to the corresponding columns in the GridDB container sensor.sensorId = resultSet.getString("sensorId"); sensor.name = resultSet.getString("name"); sensor.type = resultSet.getString("type"); //Find the Collection container of the facility that the sensor belongs to Collection<String,Sensor> collection = gridstore.getCollection(facilityId,Sensor.class); //Insert 'sensor' row into Collection container collection.put(sensor.sensorId,sensor); //Check if there is an existing Timeseries container //that is responsible for storing that sensor's measurements TimeSeries<Reading> timeseries = gridstore.getTimeSeries(sensor.sensorId,Reading.class); //If the container doesn't exist create it and insert it into GridDB if(timeseries == null){ gridstore.putTimeSeries(sensor.sensorId,Reading.class); } }
The third table we migrate is the readings
table. Just like all the other tables we fetch all the other column values for our new row in GridDB from the column fields of the row in MySQL. We will use the sensorId
column value from the MySQL row to determine which Timeseries
container the new GridDB row will be inserted into. Note, in GridDB, Timestamp columns are represented from java.util.Date
while in MySQL JDBC it is java.sql.Timestamp
, convert the value accordingly.
//Function for translating a MySQL timestamp to GridDB Timestamp Date parseTimestamp(Timestamp timestamp){ Date date = new Date(timestamp.getTime()); return date; } //(snip) //Get and iterate through all measurements in the 'readings' table ResultSet resultSet = statement.executeQuery("SELECT * FROM pv.readings ORDER BY tstamp"); while(resultSet.next()){ //Find out which sensor the measurement belongs to String sensorId = resultSet.getString("sensorId"); //Obtain the Timeseries container that corresponds to that sensor TimeSeries<Reading> timeseries = gridstore.getTimeSeries(sensorId,Reading.class); //Obtain and parse the MySQL row's timestamp column Timestamp timestamp = resultSet.getTimestamp("tstamp"); Date rowKey = parseTimestamp(timestamp); //Create a new GridDB row and map all the columns accordingly Reading reading = new Reading(); reading.tstamp = rowKey; //Set row timestamp to parse timestamp from MySQL TIMESTAMP column reading.value = resultSet.getDouble("value"); //Parse double value from MySQL DECIMAL column reading.status = resultSet.getString("status"); //Insert timestamp row into Timeseries timeseries.put(reading.tstamp,reading); }
The last table we will migrate is the alerts
. This migration is rather simple and fairly akin to the migration for the facilities
table.
//Obtain and iterate through all the rows in the 'alerts' MySQL table ResultSet resultSet = statement.executeQuery("SELECT * FROM pv.alerts"); Collection<Integer,Alert> collection = gridstore.putCollection(containerName,Alert.class); while(resultSet.next()){ //Create a new GridDB row for 'alerts' container Alert alert = new Alert(); Timestamp timestamp = resultSet.getTimestamp("tstamp");//Obtain timestamp from TIMESTAMP column alert.alertId = resultSet.getInt("id"); //Parse integer id from INTEGER COLUMN of row alert.facilityId = resultSet.getString("facilityId"); alert.sensorId = resultSet.getString("sensorId"); //Translate SQL Timestamp to GridDB Timestamp format alert.tstamp = parseTimestamp(timestamp); alert.level = resultSet.getInt("level"); //Parse integer id from INTEGER COLUMN of row alert.detail = resultSet.getString("detail"); //Insert new row into 'alerts' Collection collection.put(alert.alertId,alert); }
Our migration is now complete, all data from our MySQL database is now in GridDB. Now you can drop your old MySQL database and continue with a new faster, more scalable GridDB database.
MySQL is a relational database used heavily in business intelligence, web development, and marketplace and inventory management. For the purpose of this tutorial we set up an initial relational database using MySQL Version 5.7 on CentOS 7. We used GridDB Community Edition Build 3.0.1 as our GridDB database.
If you have any questions about the blog, please create a Stack Overflow post here https://stackoverflow.com/questions/ask?tags=griddb .
Make sure that you use the “griddb” tag so our engineers can quickly reply to your questions.