B4J Question java.exe memory growing dramatically when using Sqlite

kostefar

Active Member
Licensed User
Longtime User
Dear All,

I´m having a db with 417 rows and 19 columns, where 300 websocket inputs a sec (approx.) are being used to update the db + some processing is taking place, see code. I noticed that the size of java.exe is growing and growing, which I´m pretty sure is because of the SQLite db. The db is running in the memory (which is way more smooth), but when I tried using a file instead, it´s only taking up 50 kb of space, so it´s not the db table itself that causes this.

My idea is that so much is going on in SQLite that it cannot maintain the cache properly or something like that.

It sits for about ½ an hour at around 1 GB, then jumps to 2 GB and suddenly to 4 GB. I have seen it reaching 8 GB after 3 hours.

I could make a workaround, where the db gets cleared every hour or so, which would not be a big issue for the functionality, but I would rather that this memory problem gets fixed.
Any ideas?

The map that´s sent to insert_livedata contains parsed data from the websocket message.

B4X:
Sub Insert_livedata (m As Map)
    Dim pair As String = m.Get("pair")
    Dim rs As ResultSet
    Dim diff As Double
    Dim rowid As Int

    sql3.ExecNonQuery2("UPDATE table2 Set p1a = ?, p1b = ?, p1av = ?, p1bv = ? WHERE p1 = ?", Array As Object(m.Get ("a"), m.Get("b"), m.Get ("av"),m.Get("bv"),p))
    sql3.ExecNonQuery2("UPDATE table2 Set p2a = ?, p2b = ?, p2av = ?, p2bv = ? WHERE p2 = ?", Array As Object(m.Get ("a"), m.Get("b"), m.Get ("av"),m.Get("bv"),p))
    sql3.ExecNonQuery2("UPDATE table2 Set p3a = ?, p3b = ?, p3av = ?, p3bv = ? WHERE p3 = ?", Array As Object(m.Get ("a"), m.Get("b"), m.Get ("av"),m.Get("bv"),p))
    rs = sql3.ExecQuery2("SELECT rowid FROM table2 WHERE (p1 = ?) OR (p2 = ?) OR (p3 = ?)", Array As String(p, p, p))
    Do While rs.NextRow
        rowid = (rs.GetInt ("rowid"))
        Dim diffmap As Map = Find_difference (rowid)
        Update_diff (diffmap,rowid)
        Loop
    rs.Close
    End Sub


Sub Update_diff (m As Map,rowid As Int)
    Dim diff As Double = m.Get("diff")
    Dim p1 As String = m.Get("p1")
    Dim p2 As String = m.Get("p2")
    Dim p3 As String = m.Get("p3")
    Dim logic As String = sql3.ExecQuerySingleResult2 ("SELECT logic FROM table2 WHERE rowid = ?", Array As String (rowid))
        Dim c1 As String = sql3.ExecQuerySingleResult2 ("SELECT p1 FROM table2 WHERE rowid = ?", Array As String (rowid))
        Dim c2 As String = sql3.ExecQuerySingleResult2 ("SELECT p2 FROM table2 WHERE rowid = ?", Array As String (rowid))
        Dim c3 As String = sql3.ExecQuerySingleResult2 ("SELECT p3 FROM table2 WHERE rowid = ?", Array As String (rowid))
        diff = NumberFormat2(diff,1,8,8,False)
        Dim p As String = rowid & ": " &  c1 & "/" & c2 & "/" & c3
        sql3.ExecNonQuery2 ("UPDATE table2 Set difference = ? WHERE rowid = ?", Array As Object (diff, rowid))
    Dim writestring As String = (p  & " - " & " p1a: " & p1 &  " p2b: " & p2 & " p3a: " & p3   & " " & sql3.ExecQuerySingleResult ("SELECT difference FROM table2 WHERE rowid = " & (rowid)))
                tx.WriteLine (DateTime.Time(DateTime.Now) & " " &  writestring)
End Sub

Sub Find_difference (rowid As Int) As Map
    Dim diff As Double
    Dim p1 As Double
    Dim p2 As Double
    Dim p3 As Double
    Dim logic As String
    logic = sql3.ExecQuerySingleResult2 ("SELECT logic FROM table2 WHERE rowid = ?", Array As String (rowid))
    If logic.StartsWith ("da") Then
        p1 = sql3.ExecQuerySingleResult2 ("SELECT p1a FROM table2 WHERE rowid = ?", Array As String (rowid))
    Else
        p1 = sql3.ExecQuerySingleResult2 ("SELECT p1b FROM table2 WHERE rowid = ?", Array As String (rowid))
    End If
    If logic.SubString2(2,4) = "da" Then
        p2 = sql3.ExecQuerySingleResult2 ("SELECT p2a FROM table2 WHERE rowid = ?", Array As String (rowid))
    Else
        p2 = sql3.ExecQuerySingleResult2 ("SELECT p2b FROM table2 WHERE rowid = ?", Array As String (rowid))
    End If
    If logic.EndsWith ("da") Then
        p3 = sql3.ExecQuerySingleResult2 ("SELECT p3a FROM table2 WHERE rowid = ?", Array As String (rowid))
    Else
        p3 = sql3.ExecQuerySingleResult2 ("SELECT p3b FROM table2 WHERE rowid = ?", Array As String (rowid))
    End If
    diff = NumberFormat2(p1*p2*p3,1,8,8,False)
    Dim m As Map
m.Initialize
    m.Put ("diff",diff)
    m.Put ("p1", p1)
    m.Put ("p2", p2)
    m.Put ("p3", p3)
    Return m
End Sub
 
Last edited:

kostefar

Active Member
Licensed User
Longtime User
A coulple of things.

1. Use a try catch in all of your functions.
2. When creating a string to log use https://www.b4x.com/android/forum/threads/b4x-smart-string-literal.50135/#content, and create a logging funtion that writes to a log file
3. Explain or show your socket routines.
4. Is this UI or server app.
Thanks for your answer!

1. Would this help me to find out why I run out of memory?
2. Thanks, I will look into this. I can tell you though, that disabling the logging did not make any difference.
3. At this point I´m actually creating fake socket content, so that no sockets are open. I replicate the type of content that are received from the server with some randomization and then send the result over to be handled in the SQlite db. I had to insert a Sleep (1) in order to slow it down, or it would not be realistic. It´s actually slowing it down too much. Where I would have around 300 messages a second before, now it´s only about 100. But let´s see where it goes and if I hit the wall. I will post the result.
4. UI.
 
Upvote 0

kostefar

Active Member
Licensed User
Longtime User
Not sure how good SQLite performance in handling this, maybe you want to choose another database type such as MySQL with pooling.

I´m afraid I do not know what pooling in SQL is, and having a quick look on google did not help me much. If it´s an easy thing to do, then I may look into it, but if not, I´ll try to implement an hourly closure and reopening of the db, as I have mentioned before. I prefer to find out what the problem is, but the other way would be an acceptable workaround.
 
Upvote 0

aeric

Expert
Licensed User
Longtime User
I´m afraid I do not know what pooling in SQL is, and having a quick look on google did not help me much. If it´s an easy thing to do, then I may look into it, but if not, I´ll try to implement an hourly closure and reopening of the db, as I have mentioned before. I prefer to find out what the problem is, but the other way would be an acceptable workaround.
It is easy to implement.

 
Upvote 1

Erel

B4X founder
Staff member
Licensed User
Longtime User
I went over the heap dump. I don't see anything specific that I can point my finger, however I do see hundred or more WebSocket threads and I'm pretty sure that they are the cause for the memory problems.

Something with this design is problematic. Better to start a new thread and explain what you are trying to do with all these WebSockets.
 
Upvote 0

kostefar

Active Member
Licensed User
Longtime User
I went over the heap dump. I don't see anything specific that I can point my finger, however I do see hundred or more WebSocket threads and I'm pretty sure that they are the cause for the memory problems.

Something with this design is problematic. Better to start a new thread and explain what you are trying to do with all these WebSockets.
Thanks!

I saw the same, but since I´m not an experienced user of VisualVM, I thought that it indeed not reflecting the amount of websockets in b4j. There all I´m doing is building an array with 8 websockets that are connecting and receiving data. For now there is not even anything that restarts them upon errors or closures, so there is no way that my project could open 100´s of them.
Anyway, I tried the following:

I wrote the raw data from the websockets to a file for 5 hours without having them processed in SQLite. This did not create any heap growth.
Afterwards I loaded the file line by line into the project and processed them in SQLite. This also did not create any heap growth, but actually took 4,5 hours to process, with no insertions of Sleep or other ways of slowing it down.
In other words, to my understanding, this happens when the application is both receiving websockets and at the same time processes the content in SQLite.
So my idea is the following: There may be times where the amount of websocket messages received within a short amount of time is so high that the memory grows, because they also need to be processed in SQLite. How does that sound?
 
Last edited:
Upvote 0

kostefar

Active Member
Licensed User
Longtime User
It is easy to implement.

Perhaps it´s just me, but is this not ment for setting up a server? In my case, I´m only sitting on the client side of things and have no control over what´s being sent to me.
 
Upvote 0

kostefar

Active Member
Licensed User
Longtime User
Are you creating a UI app which implement websocket (server)?
My app is with UI (but I could live with non-UI, if that would improve system overhead) and it´s retrieving data from a server (not at my end) which are being processed and inserted into a MySQL table. Further actions are to be taken whenever certain conditions are met, but I´m not even that far yet.
 
Upvote 0

aeric

Expert
Licensed User
Longtime User
My app is with UI (but I could live with non-UI, if that would improve system overhead) and it´s retrieving data from a server (not at my end) which are being processed and inserted into a MySQL table. Further actions are to be taken whenever certain conditions are met, but I´m not even that far yet.
I am confused with your setup.

You mean there is a third party remote server which you have no control that supply the data to be inserted to a MySQL database table. You are provided the information that this server uses websocket with the clients. You also provided information about the actual table structure or columns.

What you are building is a client UI app to connect to that server. I don't understand how you create 300 websockets per second to the server since your client app is only one client. Isn't it suppose to be one websocket?

Meanwhile your UI client app also have an SQLite database. It needs to make websocket connections to 300 clients per seconds ?
 
Upvote 0

kostefar

Active Member
Licensed User
Longtime User
I am confused with your setup.

You mean there is a third party remote server which you have no control that supply the data to be inserted to a MySQL database table. You are provided the information that this server uses websocket with the clients. You also provided information about the actual table structure or columns.

What you are building is a client UI app to connect to that server. I don't understand how you create 300 websockets per second to the server since your client app is only one client. Isn't it suppose to be one websocket?

Meanwhile your UI client app also have an SQLite database. It needs to make websocket connections to 300 clients per seconds ?

These are websocket messages received from a crypto exchange. There is a total of approximately 215 crypto pairs for which I receive updates continuously. The number of messages received per second amount to an average of 300, but I only have 8 websocket clients initiated (due to a limitation on the exchange for how many pairs that can be subscribed to per socket, otherwise I would only have one socket open), through which these are received.
 
Upvote 0

aeric

Expert
Licensed User
Longtime User
These are websocket messages received from a crypto exchange. There is a total of approximately 215 crypto pairs for which I receive updates continuously. The number of messages received per second amount to an average of 300, but I only have 8 websocket clients initiated (due to a limitation on the exchange for how many pairs that can be subscribed to per socket, otherwise I would only have one socket open), through which these are received.
Why not write directly to MySQL to do processing but use SQLite as intermediate ?
By the time SQLite finish writing the old data and need some time for processing, then continue to write to MySQL, it takes some time for a single row to update. Then you need to take into account for 300 pairs. I see there may be delays in I/O and network latency where 1 second window is too short.
 
Last edited:
Upvote 0

Daestrum

Expert
Licensed User
Longtime User
Could you not have 2 apps, one that receives the data and writes to db, the other simply displays the data from db.
It sounds like you are trying to receive the data , update the db and display it in one app.
 
Upvote 0

kostefar

Active Member
Licensed User
Longtime User
Why not write directly to MySQL to do processing but use SQLite as intermediate ?
By the time SQLite finish writing the old data and need some time for processing, then continue to write to MySQL, it takes some time for a single row to update. Then you need to take into account for 300 pairs. I see there may be delays in I/O and network latency where 1 second window is too short.
So are you saying that I should have two databases, one for MySQL and one for SQLite, both with the same content?
 
Upvote 0

kostefar

Active Member
Licensed User
Longtime User
Could you not have 2 apps, one that receives the data and writes to db, the other simply displays the data from db.
It sounds like you are trying to receive the data , update the db and display it in one app.
Thanks Daestrum.

In fact there is a part displaying certain data, which is just being used for monitoring a bit of what´s going on - at the moment only count of incoming messages. I actually thought about the same, having a separate app doing that part. It is however not essential to have it, especially as I got used to relying on logging because the UI froze due to the high load.
To test if the UI, although not being used at this point, could have any impact, I ported everything (completely leaving out the UI part) to a non-UI version of the app yesterday, but the same problem with the heap occurs.
 
Upvote 0

aeric

Expert
Licensed User
Longtime User
So are you saying that I should have two databases, one for MySQL and one for SQLite, both with the same content?
I mean only one database.
If I understand correctly, you mean the first entry is into SQLite then from this SQLite you will insert to a MySQL. This MySQL database is the final database to use for displaying the overall data. Am I correct?
So why not skip the SQLite part?

Just curious. May I ask is the app able to catch up with the changes in this short time? So that the complete cycle for an update of 1 pair from starting the data come in, finished writing to SQLite, do extra processing, writing to MySQL and maybe returning a success, all these only takes a few milliseconds?
Do you use asynchronous function or Wait For?
 
Upvote 0

kostefar

Active Member
Licensed User
Longtime User
I mean only one database.
If I understand correctly, you mean the first entry is into SQLite then from this SQLite you will insert to a MySQL. This MySQL database is the final database to use for displaying the overall data. Am I correct?
So why not skip the SQLite part?

Just curious. May I ask is the app able to catch up with the changes in this short time? So that the complete cycle for an update of 1 pair from starting the data come in, finished writing to SQLite, do extra processing, writing to MySQL and maybe returning a success, all these only takes a few milliseconds?
Do you use asynchronous function or Wait For?
My bad! When you mentioned MySQL I wrote MySQL by mistake in that post instead of SQLite, but let me clarify: There´s only one db here, and it´s all SQLite. I have now corrected this in the post containing that mistake.
For the other part: That exactly seems to be the problem, catching up with the rapid changes in such a short time. If you are talking about the websockets, those are set up with Wait For, but I also tried to simply have them captured in a ws_textmessage event. The SQLite calls are not with Wait for, but perhaps that´s something worth trying. If that´s not what you ment, let me know. Thanks for the inspiration :)
 
Upvote 0

aeric

Expert
Licensed User
Longtime User
I tried minimize the query to 2 per pair. Try using other new JDK version and see if it helps.

B4X:
Public Sub InsertLiveData (m As Map)
    Try
        Dim c As String = m.Get("con")
        Dim p As String = m.Get("pair")
        Dim query1 As String = "UPDATE table2 SET p1a = ?, p1b = ?, p1av = ?, p1bv = ? WHERE p1 = ?"
        Dim query2 As String = "UPDATE table2 SET p2a = ?, p2b = ?, p2av = ?, p2bv = ? WHERE p2 = ?"
        Dim query3 As String = "UPDATE table2 SET p3a = ?, p3b = ?, p3av = ?, p3bv = ? WHERE p3 = ?"
        sql3.AddNonQueryToBatch(query1, Array(m.Get("a"), m.Get("b"), m.Get("av"), m.Get("bv"), p))
        sql3.AddNonQueryToBatch(query2, Array(m.Get("a"), m.Get("b"), m.Get("av"), m.Get("bv"), p))
        sql3.AddNonQueryToBatch(query3, Array(m.Get("a"), m.Get("b"), m.Get("av"), m.Get("bv"), p))
        Dim SenderFilter As Object = sql3.ExecNonQueryBatch("SQL")
        Wait For (SenderFilter) SQL_NonQueryComplete (Success As Boolean)
        If Not(Success) Then Log("Update failed: " & LastException)
    
        Dim query4 As String = $"SELECT rowid,
        CASE WHEN (substr(logic, 1, 2) = 'da') THEN p1a ELSE p1b END AS value1,
        CASE WHEN (substr(logic, 3, 2) = 'da') THEN p2a ELSE p2b END AS value2,
        CASE WHEN (substr(logic, -2, 2) = 'da') THEN p3a ELSE p3b END AS value3
        FROM table2
        WHERE p1 = ? OR p2 = ? OR p3 = ?"$
        Dim rs As ResultSet = sql3.ExecQuery2(query4, Array As String(p, p, p))
        Do While rs.NextRow
            Dim rowid As Int = rs.GetInt("rowid")
            Dim p1 As Double = rs.GetDouble("value1")
            Dim p2 As Double = rs.GetDouble("value2")
            Dim p3 As Double = rs.GetDouble("value3")
            Dim diff As String = NumberFormat2(p1*p2*p3, 1, 8, 8, False)
            Dim query5 As String = "UPDATE table2 SET difference = ? WHERE rowid = ?"
            sql3.AddNonQueryToBatch(query5, Array(diff, rowid)) ' assume there are more than 1 row
        Loop
        rs.Close
        'Dim rs As ResultSet = Null
        Dim SenderFilter As Object = sql3.ExecNonQueryBatch("SQL")
        Wait For (SenderFilter) SQL_NonQueryComplete (Success As Boolean)
        If Not(Success) Then
            Log("Update failed: " & LastException)
        Else
            Log($"Connection: ${c} Pair: ${p} updated"$)
        End If
    Catch
        Log(LastException)
    End Try
End Sub
 

Attachments

  • WSClient.zip
    11.5 KB · Views: 50
  • WSServer.zip
    10.6 KB · Views: 50
Upvote 0
Top