2020import org .csstudio .alarm .beast .SeverityLevel ;
2121import org .csstudio .alarm .beast .TimestampHelper ;
2222import org .csstudio .alarm .beast .TreeItem ;
23+ import org .csstudio .alarm .beast .server .AlarmServer .Update ;
2324import org .csstudio .platform .utility .rdb .RDBUtil ;
2425
2526/** Alarm RDB Handler
2627 * @author Kay Kasemir
2728 * @author Lana Abadie - Disable autocommit as needed.
29+ * @author Jaka Bobnar - RDB batching
2830 */
2931@ SuppressWarnings ("nls" )
3032public class AlarmRDB
@@ -283,7 +285,7 @@ public void readConfigurationUpdate(final AlarmPV pv) throws Exception
283285 * @param timestamp
284286 * @throws Exception on error
285287 */
286- public void writeStateUpdate (final AlarmPV pv , final SeverityLevel current_severity ,
288+ private void writeStateUpdate (final AlarmPV pv , final SeverityLevel current_severity ,
287289 String current_message , final SeverityLevel severity , String message ,
288290 final String value , final org .epics .util .time .Timestamp timestamp ) throws Exception
289291 {
@@ -293,12 +295,6 @@ public void writeStateUpdate(final AlarmPV pv, final SeverityLevel current_sever
293295 if (current_message == null || current_message .isEmpty ())
294296 current_message = SeverityLevel .OK .getDisplayName ();
295297
296- // According to JProfiler, this is the part of the code
297- // that uses most of the CPU:
298- // Compared to receiving updates from PVs and sending them
299- // to JMS clients, the (Oracle) RDB update dominates
300- // the combined time spent in CPU usage and network I/O.
301-
302298 // These are usually quick accesses to local caches,
303299 // but could fail when trying to add new values to RDB, so give detailed error
304300 final int current_severity_id ;
@@ -337,41 +333,88 @@ public void writeStateUpdate(final AlarmPV pv, final SeverityLevel current_sever
337333 {
338334 throw new Exception ("Failed to map alarm message " + message + ": " + ex .getMessage (), ex );
339335 }
336+
337+ updateStateStatement .setInt (1 , current_severity_id );
338+ updateStateStatement .setInt (2 , current_message_id );
339+ updateStateStatement .setInt (3 , severity_id );
340+ updateStateStatement .setInt (4 , message_id );
341+ //Truncate the value to avoid Truncation Exception thrown by some SQL Servers
342+ String newValue = value ;
343+ if (newValue != null && newValue .length () > 100 )
344+ {
345+ newValue = newValue .substring (0 ,99 );
346+ Activator .getLogger ().log (Level .WARNING ,
347+ "Value truncated. Too many characters: " + pv .getName () + "; " + timestamp .toDate () + " " + value );
348+ }
349+ updateStateStatement .setString (5 , newValue );
350+ Timestamp sql_time = TimestampHelper .toSQLTime (timestamp );
351+ if (sql_time .getTime () == 0 )
352+ { // MySQL will throw Data Truncation exception on 0 time stamps
353+ sql_time = new Timestamp (new Date ().getTime ());
354+ Activator .getLogger ().log (Level .INFO ,
355+ "State update for {0} corrects time stamp {1} to now" ,
356+ new Object [] { pv .getPathName (), timestamp });
357+ }
358+ updateStateStatement .setTimestamp (6 , sql_time );
359+ updateStateStatement .setInt (7 , pv .getID ());
360+ updateStateStatement .addBatch ();
361+ }
340362
341- // The isConnected() check in here is expensive, but what's
342- // the alternative if we want convenient auto-reconnect?
363+ /** Persists all the updates into DB.
364+ * @param updates the updates to persist
365+ * @param batchSize maximum batch size
366+ *
367+ * @throws Exception
368+ */
369+ public void persistAllStates (final Update [] updates , final int batchSize ) throws Exception
370+ {
343371 final Connection actual_connection = rdb .getConnection ();
344372 actual_connection .setAutoCommit (false );
373+
374+ // New or changed connection?
375+ if (actual_connection != connection || updateStateStatement == null )
376+ {
377+ connection = actual_connection ;
378+ updateStateStatement = null ;
379+ updateStateStatement = actual_connection .prepareStatement (sql .update_pv_state );
380+ }
345381 try
346382 {
347- if (actual_connection != connection || updateStateStatement == null )
348- { // (Re-)create statement on new connection
349- connection = actual_connection ;
350- updateStateStatement = null ;
351- updateStateStatement = connection .prepareStatement (sql .update_pv_state );
352- }
353- // Bulk of the time is spent in execute() & commit
354- updateStateStatement .setInt (1 , current_severity_id );
355- updateStateStatement .setInt (2 , current_message_id );
356- updateStateStatement .setInt (3 , severity_id );
357- updateStateStatement .setInt (4 , message_id );
358- updateStateStatement .setString (5 , value );
359- Timestamp sql_time = TimestampHelper .toSQLTime (timestamp );
360- if (sql_time .getTime () == 0 )
361- { // MySQL will throw Data Truncation exception on 0 time stamps
362- sql_time = new Timestamp (new Date ().getTime ());
363- Activator .getLogger ().log (Level .INFO ,
364- "State update for {0} corrects time stamp {1} to now" ,
365- new Object [] { pv .getPathName (), timestamp });
383+ int count = 0 ;
384+ for (Update u : updates )
385+ {
386+ try
387+ {
388+ writeStateUpdate (u .pv , u .currentSeverity , u .currentMessage , u .alarmSeverity ,
389+ u .alarmMessage , u .value , u .timestamp );
390+ count ++;
391+ }
392+ catch (Exception e )
393+ {
394+ //this is about 4-times faster than StringBuilder
395+ String s = "Error updating state: current severity=" + u .currentSeverity +
396+ "; current message=" + u .currentMessage + "; severity=" + u .alarmSeverity +
397+ "; message=" + u .alarmMessage + "; value=" + u .value + "; timestamp=" + u .timestamp +
398+ "; pv=" + u .pv .getName () + '(' + u .pv .getID () + "). Message skipped." ;
399+ Activator .getLogger ().log (Level .SEVERE , s );
400+ }
401+ if (count == batchSize )
402+ { // Periodically submit as batch
403+ updateStateStatement .executeBatch ();
404+ actual_connection .commit ();
405+ count = 0 ;
406+ }
366407 }
367- updateStateStatement .setTimestamp (6 , sql_time );
368- updateStateStatement .setInt (7 , pv .getID ());
369- updateStateStatement .execute ();
370- actual_connection .commit ();
408+ // Submit remaining statements
409+ if (count > 0 )
410+ {
411+ updateStateStatement .executeBatch ();
412+ actual_connection .commit ();
413+ }
371414 }
372- catch (Exception e )
415+ catch (Exception e )
373416 {
374- actual_connection . rollback ( );
417+ rollbackBatchUpdate ( actual_connection , updateStateStatement );
375418 throw e ;
376419 }
377420 finally
@@ -380,33 +423,63 @@ public void writeStateUpdate(final AlarmPV pv, final SeverityLevel current_sever
380423 }
381424 }
382425
383- /** Update 'global' alarm indicator in RDB
384- * @param pv
385- * @param active Is there an active 'global' alarm on the PV?
386- * @throws Exception on error
426+ /** Persists all the global updates in batches of the given size.
427+ *
428+ * @param updates the updates to persist
429+ * @param batchSize maximum batch size
430+ *
431+ * @throws Exception
387432 */
388- public void writeGlobalUpdate (final AlarmPV pv , final boolean active ) throws Exception
433+ public void persistGlobalUpdates (final Update [] updates , final int batchSize ) throws Exception
389434 {
390- // The isConnected() check in here is expensive, but what's
391- // the alternative if we want convenient auto-reconnect?
392435 final Connection actual_connection = rdb .getConnection ();
393436 actual_connection .setAutoCommit (false );
437+
394438 try
395439 {
440+ int count = 0 ;
396441 if (actual_connection != connection || updateGlobalStatement == null )
397442 { // (Re-)create statement on new connection
398443 connection = actual_connection ;
399444 updateGlobalStatement = null ;
400445 updateGlobalStatement = connection .prepareStatement (sql .update_global_state );
401446 }
402- updateGlobalStatement .setBoolean (1 , active );
403- updateGlobalStatement .setInt (2 , pv .getID ());
404- updateGlobalStatement .execute ();
405- actual_connection .commit ();
447+
448+ for (Update u : updates )
449+ {
450+ try
451+ {
452+ updateGlobalStatement .setBoolean (1 , u .currentSeverity .isActive ());
453+ updateGlobalStatement .setInt (2 , u .pv .getID ());
454+ updateGlobalStatement .addBatch ();
455+ count ++;
456+ }
457+ catch (Exception e )
458+ {
459+ //this is about 4-times faster than StringBuilder
460+ String s = "Error updating global state: current severity=" + u .currentSeverity +
461+ "; current message=" + u .currentMessage + "; severity=" + u .alarmSeverity +
462+ "; message=" + u .alarmMessage + "; value=" + u .value + "; timestamp=" + u .timestamp +
463+ "; pv=" + u .pv .getName () + '(' + u .pv .getID () + "). Message skipped." ;
464+ Activator .getLogger ().log (Level .SEVERE , s );
465+ }
466+ if (count == batchSize )
467+ {
468+ updateGlobalStatement .executeBatch ();
469+ actual_connection .commit ();
470+ count = 0 ;
471+ }
472+ }
473+ // Submit remaining batch
474+ if (count > 0 )
475+ {
476+ updateGlobalStatement .executeBatch ();
477+ actual_connection .commit ();
478+ }
406479 }
407- catch (Exception e )
480+ catch (Exception e )
408481 {
409- actual_connection . rollback ( );
482+ rollbackBatchUpdate ( actual_connection , updateGlobalStatement );
410483 throw e ;
411484 }
412485 finally
@@ -447,6 +520,19 @@ public void writeEnablementUpdate(final AlarmPV pv, final boolean enabled) throw
447520 }
448521 }
449522
523+ private void rollbackBatchUpdate (final Connection actualConnection , final PreparedStatement statement )
524+ {
525+ try
526+ {
527+ statement .clearBatch ();
528+ actualConnection .rollback ();
529+ }
530+ catch (Exception ex )
531+ {
532+ Activator .getLogger ().log (Level .SEVERE , "State update rollback error." , ex );
533+ }
534+ }
535+
450536 /** Must be called to release resources */
451537 public void close ()
452538 {
0 commit comments