First of all, let's split this up:
Resultset from database
<cfquery name="qry">
select * from #k#
</cfquery>
- Database server retrieves data and streams it via network to the ColdFusion server
- ColdFusion stores the data in a query object and stores it in the heap
Serializing the resultset from database
<cfset js= serializeJSON(qry,'struct')>
- ColdFusion recursively serializes the whole query object
- ColdFusion creates a string object that contains the serialized data and stores it in the heap
Writing the serialized resultset from memory onto the filesystem
<cffile action="write" file="#k#" output="#js#">
- ColdFusion writes the string object into a file on the filesystem
Doing all of this within the same request/thread
<cfloop list="t" index="k">
...
</cfloop>
Conclusion
Your code tortures the JVM heap, because references have to be kept until the end of each iteration. The GC can only clean up after a full table has been processed. Large tables (1.000.000+ rows) will likely kill the thread or even hang the JVM.
The Fix: Resultset from database
Retrieving large resultsets at once will always hurt performance. While streaming lots of data within a local network (assuming the database is in the same network) just takes a bit more time, the memory required to store the full resultset is going to be an issue for the JVM.
Instead of doing everything at once, consider splitting it up in smaller chunks of data. Use OFFSET
and FETCH
in the SQL statement to limit the number of rows per loop. Having multiple iterations will allow the Java GC to free up memory used by previous iterations, relieving the heap.
The Fix: Serializing the resultset from database
Same issue. Big datasets whill hurt performance. Split the resultset by serializing row by row instead of all rows at once.
Writing the serialized resultset from memory onto the filesystem
While this one probably doesn't need a fix, you eventually have to switch to writing line after line.
Some code
<cfset maxRowsPerIteration = 50000>
<cfloop list="t" index="k">
<!--- create empty file to append lines later --->
<cfset fileWrite(k, "")>
<cfset rowsOffset = 0>
<!--- NOTE: you might want to lock the table (prevent write access) here --->
<!--- infinite loop will be terminated as soon the query no longer returns any rows --->
<cfloop condition="true">
<!--- fetch a slice of the full table --->
<cfquery name="qry">
select * from #k# OFFSET #rowsOffset# ROWS FETCH NEXT #maxRowsPerIteration# ROWS ONLY
</cfquery>
<cfif not qry.recordCount>
<cfbreak>
</cfif>
<cfset rowsOffset += maxRowsPerIteration>
<cfloop query="qry">
<cfset rowJSON = serializeJSON(
queryRowToStruct(qry, qry.currentRow)
)>
<cfset fileAppend(k, rowJSON, "UTF-8", true)>
</cfloop>
</cfloop>
<!--- NOTE: if you locked the table previously, unlock it here --->
</cfloop>
For an reference implementation of queryRowToStruct
, check CFLib.