Estou usando o ExecutorService com um pool de threads fixo de 50 e um pool de conexão de banco de dados fixo de 50, usando HikariCP. Cada thread de trabalho processa um pacote (um "relatório"), verifica se ele é válido (onde cada relatório deve ter um unit_id, hora, latitude e longitude exclusivos), obtém uma conexão db do pool de conexões e, em seguida, insere o relatório no a tabela de relatórios. A restrição de exclusividade é criada com postgresql e chamada de "reports_uniqueness_index". Quando eu tenho alto volume, recebo uma tonelada do seguinte erro:
org.postgresql.util.PSQLException: ERROR: duplicate key value
violates unique constraint "reports_uniqueness_index"
Aqui está o que eu acredito que seja o problema. Antes da inserção do banco de dados, realizo uma verificação para determinar se já existe um relatório na tabela com o mesmo unit_id, hora, latitude e longitude. Caso contrário, o relatório é válido e eu realizo a inserção. Porém, acho que por estar usando simultaneidade tenho 50 threads cada verificando ao mesmo tempo se o relatório é válido e como nenhum deles foi inserido ainda, cada thread pensa que tem um relatório válido e quando vai inseri-los em mesmo momento, é quando o postgresql gera o erro.
Gostaria de uma solução que não gerasse latência com a simultaneidade. Tenho tentado evitar o uso de instrução sincronizada ou um bloqueio reentrante porque as inserções de banco de dados precisam ocorrer o mais rápido possível. Esta é a inserção aqui:
private boolean save(){
Connection conn=null;
Statement stmt=null;
int status=0;
DbConnectionPool dbPool = DbConnectionPool.getInstance();
String sql = = "INSERT INTO reports"
sql += " (unit_id, time, time_secs, latitude, longitude, speed, created_at)";
sql += " values (...)";
try {
conn = dbPool.getConnection();
stmt = conn.createStatement();
status = stmt.executeUpdate(sql);
} catch (SQLException e) {
return false;
} finally {
try {
if (stmt != null)
{
stmt.close();
}
if (conn != null)
{
conn.close();
}
} catch(SQLException e){}
}
if(status > 0){
return true;
}
return false;
}
Uma solução que pensei foi usar o próprio objeto Class como um objeto de bloqueio:
synchronized(Report.class) {
status = stmt.executeUpdate(sql);
}
Mas isso atrasará a inserção de outros segmentos. Existe uma solução melhor?