много запросов в задаче для генерации JSON - PullRequest
1 голос
/ 05 марта 2011

Итак, у меня есть задача построить сборку, которая будет архивировать тонну данных в нашей БД в JSON.

Чтобы дать вам лучшее представление о том, что происходит;Х имеет 100 с Ys, а Y имеет 100 с Z и так далее.Я создаю файл json для каждого X, Y и Z. Но каждый файл X json имеет массив идентификаторов для дочерних Ys X, и аналогично Ys хранит массив дочерних Zs ..

Это сложнее, чем во многих случаях, но вы должны понять сложность этого примера, я думаю.

Я использовал ColdFusion, но, похоже, это плохой выбор для этой задачи, потому что этосбой из-за ошибок памяти.Мне кажется, что если бы он удалял из памяти запросы, на которые больше не ссылаются при выполнении задачи (т.е. сбор мусора), то у задачи должно быть достаточно памяти, но на самом деле ColdFusion вообще не выполняет никакой сборки мусора и долженделать это после того, как запрос завершен.

Поэтому я ищу либо совет о том, как лучше выполнить свою задачу в CF, либо рекомендации по использованию других языков ..

Спасибо.

Ответы [ 2 ]

5 голосов
/ 05 марта 2011

1) Если у вас включена отладка, coldfusion будет удерживать ваши запросы, пока страница не будет завершена. Выключи!

2) Вам может понадобиться structDelete () переменная запроса, чтобы разрешить ее сборку мусора, в противном случае она может сохраняться до тех пор, пока существует область действия, на которую имеется ссылка. например., <cfset structDelete(variables,'myQuery') />

3) cfquery вытягивает весь ResultSet в память. В большинстве случаев это нормально. Но для сообщения о большом наборе результатов вы не хотите этого. Некоторые драйверы JDBC поддерживают установку fetchSize, которая в режиме пересылки, только для чтения, позволит вам получить несколько результатов за раз. Таким образом, вы можете иметь дело с тысячами и тысячами строк, не забивая память. Я только что сгенерировал 1GB csv файл за ~ 80 секунд, используя менее 100 Мб кучи. Для этого нужно бросить учебу на Java. Но это убивает двух зайцев одним выстрелом. Это уменьшает объем данных, вносимых драйвером JDBC за раз, и, поскольку вы работаете непосредственно с ResultSet, вы не сталкиваетесь с проблемой cfloop, упомянутой @orangepips. Разумеется, это не для тех, у кого нет Java-отбивных.

Вы можете сделать это примерно так (вам нужен cfusion.jar в вашем пути сборки):

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.sql.ResultSet;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.Statement;

import au.com.bytecode.opencsv.CSVWriter;
import coldfusion.server.ServiceFactory;

public class CSVExport {
    public static void export(String dsn,String query,String fileName) {
        Connection conn = null;
        Statement stmt = null;
        ResultSet rs = null;
        FileWriter fw = null;
        BufferedWriter bw = null;


        try {
            DataSource ds = ServiceFactory.getDataSourceService().getDatasource(dsn);
            conn = ds.getConnection();
            // we want a forward-only, read-only result.
            // you may want need to use a PreparedStatement instead.
            stmt = conn.createStatement(
                ResultSet.TYPE_FORWARD_ONLY,
                ResultSet.CONCUR_READ_ONLY
            );
            // we only want to go forward!
            stmt.setFetchDirect(ResultSet.FETCH_FORWARD);
            // how many records to pull back at a time.
            // the hard part is balancing memory usage, and round trips to the database.
            // basically sacrificing speed for a lower memory hit.
            stmt.setFetchSize(256);
            rs = stmt.executeQuery(query);
            // do something with the ResultSet, for example write to csv using opencsv
            // the key is to stream it. you don't want it stored in memory.
            // so excel spreadsheets and pdf files are out, but text formats like 
            // like csv, json, html, and some binary formats like MDB (via jackcess)
            // that support streaming are in.
            fw = new FileWriter(fileName);
            bw = new BufferedWriter(fw);
            CSVWriter writer = new CSVWriter(bw);
            writer.writeAll(rs,true);
        }
        catch (Exception e) {
            // handle your exception.
            // maybe try ServiceFactory.getLoggingService() if you want to do a cflog.
            e.printStackTrace();
        }
        finally() {
            try {rs.close()} catch (Exception e) {}
            try {stmt.close()} catch (Exception e) {}
            try {conn.close()} catch (Exception e) {}
            try {bw.close()} catch (Exception e) {}
            try {fw.close()} catch (Exception e) {}
        }
    }
}

Выяснение того, как передать параметры, ведение журнала, превращение этого в фоновый процесс (подсказка: расширение Thread) и т. Д. - это отдельные проблемы, но если вы взломаете этот код, это не должно быть слишком сложно.

4) Возможно, посмотрите на Джексон для генерации вашего json. Он поддерживает потоковое и в сочетании с fetchSize и BufferedOutputStream позволяет сократить использование памяти.

4 голосов
/ 05 марта 2011

Эрик, вы абсолютно правы в отношении того, что сборщик мусора в ColdFusion не удаляет информацию о запросах из памяти до завершения запроса, и Я довольно подробно задокументировал ее в другом вопросе SO . Короче говоря, вы нажимаете OoM Exception, когда зацикливаетесь на запросах. Вы можете доказать это с помощью такого инструмента, как VisualVM , чтобы создать дамп кучи во время выполнения процесса, а затем запустить полученный дамп с помощью Eclipse Memory Analyzer Tool (MAT). Что бы MAT показало вам, это большая иерархия, начинающаяся с объекта с именем (я не придумываю это) CFDummyContent, который содержит, помимо прочего, ссылки на теги cfquery и cfqueryparam. Обратите внимание, что попытка изменить его до сохраненных процедур или даже взаимодействие с базой данных через JDBC не имеет значения.

Так. Какие. Для. У

Мне потребовалось некоторое время, чтобы понять, но у вас есть 3 варианта в возрастающем порядке сложности:

  1. <cthread/>
  2. асинхронный шлюз CFML
  3. http-запросы в гирляндной цепочке

Использование cfthread выглядит так:

<cfloop ...>
    <cfset threadName = "thread" & createUuid()>
    <cfthread name="#threadName#" input="#value#">
        <!--- do query stuff --->
        <!--- code has access to passed attributes (e.g. #attributes.input#) --->
        <cfset thread.passOutOfThread = somethingGeneratedInTheThread>
    </cfthread>
    <cfthread action="join" name="#threadName#">
    <cfset passedOutOfThread = cfthread["#threadName#"].passOutOfThread>
</cfloop>

Обратите внимание, что этот код не , использующий преимущества асинхронной обработки, то есть немедленного соединения после каждого вызова потока, а скорее побочный эффект, который выполняет cfthread в своей собственной области запроса, независимо от страницы.

Я не буду здесь описывать шлюзы ColdFusion. Последовательность HTTP-цепочек означает выполнение инкремента и в конце инкремента запуск запроса к тому же алгоритму с указанием выполнить следующий инкремент.

По сути, все три подхода позволяют собирать эти ссылки на память в середине процесса.

И да, для тех, кто спрашивает, ошибки были выявлены в Adobe, см. Вопрос, на который ссылаются. Кроме того, я считаю, что эта проблема является специфической для Adobe ColdFusion, но я не тестировал Railo или OpenDB.

Наконец-то надо разглагольствовать. Я потратил много времени на то, чтобы отследить это, исправить его в своей собственной большой базе кода, и несколько других, перечисленных в упомянутом вопросе, тоже. AFAIK Adobe не признает проблему, тем не менее стремится решить ее. И да, это ошибка, простая и понятная.

...