H2 performance degrade using remote tcp server - database

I have a simple application that uses a single connection, the flow is something like this:
SELECT QUERY
CONNECTION CLOSE
for(..) //thousands of iterations
{
SIMPLE SELECT QUERY
}
BUSINESS LOGIC
CONNECTION CLOSE
for(..) //thousands of iterations
{
SIMPLE SELECT QUERY
}
BUSINESS LOGIC
CONNECTION CLOSE
When i use the embedded connection mode, the application ends in about 20 seconds, but when i switch to the server mode, the performances deteriorate:
localhost: 110 seconds
remote server (LAN): more than 30 minutes
Each query retrieves a small amount of data.
Is there an explanation for such a poor performance? How can i speed up the application without rewriting the code?
Any help is appreciated

compare your problem like accessing a cup of coffee.
If you access your coffe on your table/workdesk it is comparable like a in memory access to your database (H2 embedded)
It takes about seconds (coffee) and microseconds (h2 embedded)
if you need to go to the kitchen to access a cup of coffe it will take you the travel time from your chair to the kitchen + back (response).
The kitchen is comparable like TCP accessing or file accessing your local database.
It takes you minutes (coffee) and one digit milliseconds (h2 tcp localhost or local file)
if you need to go to a coffeshop outside to access a cup of coffee it will take you at least 15 mins to get a cup of coffee (and ages ( at least 2 digits milliseconds) in h2 tcp on remote machine)
now the question, how many times do you want to travel to a coffeshop?
If I give you a iteration (for loop) over 1000 times to the coffeshop, would you just asking me after the second or the third time if I am kidding? Why do you bother a IO dependent system on long travel times over the network?
So in your case, if you reduce your second for-loop into one SQL query, you will get a great performance locally and of course especially on the remote way.
I hope I could explain you the situation with the coffee, since it explains the problem better.
So to answer the last question, you have to rewrite your "thousands of iterations" for-loops.
Update 1
Forgot, if your for-loops are writing loops (update/insert) you can use batch queries. How to use them depends on your language you are using. Batches are to provide the database a bunch (e.g. multiple hundreds) of insert/updates before the operation takes in place on the database.

I did following test with your provided H2 version.
create a test database with 500.000 records, database size 990 MB
java -cp h2-1.3.168.jar;. PerfH2 create
select random 8.000 records on the indexed column
in embedded mode -> 1.7 sec
java -cp h2-1.3.168.jar;. PerfH2 embedded
in server (localhost) mode -> 2.6 sec
java -cp h2-1.3.168.jar;. PerfH2 server
If you face the problem already using server mode as jdbc:h2:tcp://localhost/your_database, then something with your environment or the way you access the server mode seems to be wrong. Try with a stripped done application and check if the problem still exists. If you have can reproduce the problem also with a stripped down version please post the code.
Find the code used for the test below.
public class PerfH2 {
public static void main(String[] args) throws SQLException {
if (null == args[0]) {
showUsage();
return;
}
long start = System.currentTimeMillis();
switch (args[0]) {
case "create":
createDatabase();
break;
case "embedded":
try (Connection conn = getEmbeddedConnection()) {
execSelects(conn);
}
break;
case "server":
try (Connection conn = getServerConnection()) {
execSelects(conn);
}
break;
default:
showUsage();
}
System.out.printf("duration: %d%n", System.currentTimeMillis() - start);
}
private static Connection getServerConnection() throws SQLException {
return DriverManager.getConnection("jdbc:h2:tcp://localhost/perf_test", "sa", "");
}
private static Connection getEmbeddedConnection() throws SQLException {
return DriverManager.getConnection("jdbc:h2:d:/temp/perf_test", "sa", "");
}
private static void execSelects(final Connection conn) throws SQLException {
Random rand = new Random(1);
String selectSql = "SELECT * FROM TEST_TABLE WHERE ID = ?";
PreparedStatement selectStatement = conn.prepareStatement(selectSql);
int count = 0;
for (int i = 0; i < 8000; i++) {
selectStatement.setInt(1, rand.nextInt(500_000));
ResultSet rs = selectStatement.executeQuery();
while (rs.next()) {
count++;
}
}
System.out.printf("retrieved rows: %d%n", count);
}
private static void createDatabase() throws SQLException {
try (Connection conn = DriverManager.getConnection("jdbc:h2:d:/temp/perf_test", "sa", "")) {
String createTable = "CREATE TABLE TEST_TABLE(ID INT, NAME VARCHAR(1024))";
conn.createStatement().executeUpdate(createTable);
String insertSql = "INSERT INTO TEST_TABLE VALUES(?, ?)";
PreparedStatement insertStmnt = conn.prepareStatement(insertSql);
StringBuilder sb = new StringBuilder(1024);
for (int i = 0; i < 1024 / 10; i++) {
sb.append("[cafebabe]");
}
String value = sb.toString();
int count = 0;
for (int i = 0; i < 50; i++) {
insertStmnt.setInt(1, i);
insertStmnt.setString(2, value);
count += insertStmnt.executeUpdate();
}
System.out.printf("inserted rows: %d%n", count);
conn.commit();
String createIndex = "CREATE INDEX TEST_INDEX ON TEST_TABLE(ID)";
conn.createStatement().executeUpdate(createIndex);
}
}
private static void showUsage() {
System.out.println("usage: PerfH2 [create|embedded|server]");
}
}

Related

Elapsed time and CPU time are incorrect when run from external application

Is there any improvements or magical way when a SqlCommand is executed from a .net console application ?
I see huge improvements and consistent times when running the command from .net based application but its completely different when executed in Sql server management studio.
.NET Code
static void Main(string[] args)
{
try
{
string strConn = "Data Source=.;Initial Catalog=school;Integrated Security=True";
using (SqlConnection conn = new SqlConnection(strConn))
{
conn.Open();
SqlCommand command = new SqlCommand();
command.CommandType = System.Data.CommandType.Text;
command.CommandText = "SET NOCOUNT OFF;SET STATISTICS IO,TIME ON; Exec spStudents #type='SELECTALL';SET STATISTICS IO,TIME OFF;";
conn.InfoMessage += conn_InfoMessage;
command.Connection = conn;
using (var reader = command.ExecuteReader())
{
do
{
while (reader.Read())
{
//grab the first column to force the row down the pipe
var x = reader[0];
}
} while (reader.NextResult());
}
Console.Read();
}
}
catch (Exception ex)
{
throw;
}
}
static void conn_InfoMessage(object sender, SqlInfoMessageEventArgs e)
{
foreach (SqlError item in e.Errors)
{
Console.WriteLine(item.Message);
}
}
I see sql reports consistent times of CPU time = 130 ~ 150 ms and elapsed time = 250ms ~ 300 ms. But when running same in SSMS it reports me CPU time = 900ms, elapsed time = 1s. So which one is reliable result ?
another related question that i asked yesterday SQL query performance statistics messages returned multiple times

Load milion objects from DB

i am using Drools engine but only from the Implementation side. the frameWork is setup for me, and i can use only RULES side ( i hope i am able to explain myself).
that said - my problem is that i am trying to load about 1 milion row from Oracle DB into the WM, and i find that this task takes too long, here is the rule that i use to load the objects:
(BTW - the request to load the milion records into the WM is mandatory since i need to use these DB objects as part of my rules along with other objects that are injected at runtime into the engine)
rule "Load_CMMObject"
salience 10
no-loop
when
not CMMObjectShouldBeRefreshed() over window:time( 1500m )
then
log("Fetching Load_CMMObject");
ExecutionContext ctx = getExecutionContext();
String getObjectTypeSQL = "select AID , BC_OBJECT_ID , ALR_EQP_NAME , ALR_FROM_SITE from CMM_DB.COR_EQP_VW";
PreparedStatement pStmt = null;
try {
pStmt = MTServiceAccessor.getDBAccessService().prepareNativeStatement(ctx, getObjectTypeSQL, false);
ResultSet rs = pStmt.executeQuery();
while (rs.next()) {
String aid = rs.getString(1);
int objectID = rs.getInt(2);
String eqpName = rs.getString(3);
String fromSite = rs.getString(4);
CMMObject cmmObject = new CMMObject();
cmmObject.setIp(aid);
cmmObject.setObjectID(objectID);
cmmObject.setEqpName(eqpName);
cmmObject.setFromSite(fromSite);
insert(cmmObject);
//log("insert Object ---> " + cmmObject.getIp());
}
log("Finish Loading All cmm_db.BCMED_EQP_VW");
} catch (Exception e) {
log("Failed to Load ServiceName_TBL" + e);
} finally {
if (pStmt != null) {
try {
pStmt.close();
} catch (Exception e) {
log("Failed to close pstmt");
}
}
}
//log(" finished loading trails into the WM1");
CMMObjectShouldBeRefreshed cMMObjectShouldBeRefreshed = new CMMObjectShouldBeRefreshed();
//log(" finished loading trails into the WM2");
insert (cMMObjectShouldBeRefreshed);
//log("finished loading trails into the WM3");
end
i am using server that allocates about 20Gb RAM for the Drools engine, and it has 8 1.5GHZ Quad Core proccessors.
the problem is that it take me to load 5000 raws about 1 minute --> so if i want to load the 1 milion records from the DB it will take me 200 minutes to complete the task and this is too much.
i will appreciate any help here,
thanks alot!

Different ways of performing bulk insert into database from a java application

I am looking for different ways of performing bulk insert into database (e.g. SQL Server 2012) from a Java application. I need to insert lot of entities into database very efficiently without making as many calls to database as there are entities.
My requirement is to perform a bulk insert of entities, where an insert of entity in database could involve inserting data into one or more tables. The following are the two ways which I can think of:
Dynamically generate a batch of SQL statements and execute it against the database by making use of native JDBC support.
Construct XML representation of all the entities and then invoke a stored procedure by passing the generated XML. The stored procedure takes care of parsing the XML and inserting the entities to database.
I am new to Java and not having enough knowledge of available frameworks. IMO, the above two approaches seems to be very naive and not leveraging the available frameworks. I am requesting experts to share different ways of achieving bulk insert along with its pros and cons. I am open to MyBatis, Spring-MyBatis, Spring-JDBC, JDBC, etc which solves the problem in an efficient manner.
Thanks.
I have a demo ,JDBC batch processing
file:demo.txt
The content
1899942 ,demo1
1899944 ,demo2
1899946 ,demo3
1899948 ,demo4
Insert the data reads the file content
my code:
public class Test2 {
public static void main(String[] args) {
long start = System.currentTimeMillis();
String sql = "insert into mobile_place(number,place) values(?,?)";
int count=0;
PreparedStatement pstmt = null;
Connection conn = JDBCUtil.getConnection();
try {
pstmt = conn.prepareStatement(sql);
InputStreamReader is = new InputStreamReader(new FileInputStream(new File("D:/CC.txt")),"utf-8");
BufferedReader br = new BufferedReader(is);
conn.setAutoCommit(false);
String s1 = null;
String s2 = null;
while(br.readLine() != null){
count++;
String str = br.readLine().toString().trim();
s1 = str.substring(0, str.indexOf(","));
s2 = str.substring(str.indexOf(",")+1,str.length());
pstmt.setString(1, s1);
pstmt.setString(2, s2);
pstmt.addBatch();
if(count%1000==0){
pstmt.executeBatch();
conn.commit();
conn.close();
conn = JDBCUtil.getConnection();
conn.setAutoCommit(false);
pstmt = conn.prepareStatement(sql);
}
System.out.println("insert "+count+"line");
}
if(count%1000!=0){
pstmt.executeBatch();
conn.commit();
}
long end = System.currentTimeMillis();
System.out.println("Total time spent:"+(end-start));
} catch (Exception e) {
e.printStackTrace();
}finally{
try {
pstmt.close();
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
//getConnection()//get jdbc Connection
public static Connection getConnection(){
try {
Class.forName("com.mysql.jdbc.Driver");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
try {
conn = DriverManager.getConnection(url, userName, password);
} catch (SQLException e) {
e.printStackTrace();
}
return conn;
}
Speak for the first time, I hope I can help
I am the demo above use PreparedStatement [Read data calls a PreparedStatement one-off inserted]
JDBC batch There are 3 ways
1.use PreparedStatement
demo:
try {
Class.forName("com.mysql.jdbc.Driver");
conn = DriverManager.getConnection(o_url, userName, password);
conn.setAutoCommit(false);
String sql = "INSERT adlogs(ip,website,yyyymmdd,hour,object_id) VALUES(?,?,?,?,?)";
PreparedStatement prest = conn.prepareStatement(sql,ResultSet.TYPE_SCROLL_SENSITIVE,ResultSet.CONCUR_READ_ONLY);
for(int x = 0; x < size; x++){
prest.setString(1, "192.168.1.1");
prest.setString(2, "localhost");
prest.setString(3, "20081009");
prest.setInt(4, 8);
prest.setString(5, "11111111");
prest.addBatch();
}
prest.executeBatch();
conn.commit();
conn.close();
} catch (SQLException ex) {
Logger.getLogger(MyLogger.class.getName()).log(Level.SEVERE, null, ex);
}
2.use Statement.addBatch methods
demo:
conn.setAutoCommit(false);
Statement stmt = conn.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_READ_ONLY);
for(int x = 0; x < size; x++){
stmt.addBatch("INSERT INTO adlogs(ip,website,yyyymmdd,hour,object_id) VALUES('192.168.1.3', 'localhost','20081009',8,'23123')");
}
stmt.executeBatch();
conn.commit();
3.Direct use of the Statement
demo:
conn.setAutoCommit(false);
Statement stmt = conn.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE,
ResultSet.CONCUR_READ_ONLY);
for(int x = 0; x < size; x++){
stmt.execute("INSERT INTO adlogs(ip,website,yyyymmdd,hour,object_id) VALUES('192.168.1.3', 'localhost','20081009',8,'23123')");
}
conn.commit();
Using the above method Insert the 100000 pieces of data Time consuming:
method 1:17.844s
method 2:18.421s
method 3:16.359s
MS JDBC versions later than 4.1 have SQLServerBulkCopy class that I assume is equivalent to one available in .Net and theoretically it should work as fast as bcp command line utility.
https://msdn.microsoft.com/en-us/library/mt221490%28v=sql.110%29.aspx
you can custom your code with JDBC,there is no framework support your requirement

Is using a SqlCacheDependency per user a good idea?

I'm thinking of caching permissions for every user on our application server. Is it a good idea to use a SqlCacheDependency for every user?
The query would look like this
SELECT PermissionId, PermissionName From Permissions Where UserId = #UserId
That way I know if any of those records change then to purge my cache for that user.
If you read how Query Notifications work you'll see why createing many dependency requests with a single query template is good practice. For a web app, which is implied by the fact that you use SqlCacheDependency and not SqlDependency, what you plan to do should be OK. If you use Linq2Sql you can also try LinqToCache:
var queryUsers = from u in repository.Users
where u.UserId = currentUserId
select u;
var user= queryUsers .AsCached("Users:" + currentUserId.ToString());
For a fat client app it would not be OK. Not because of the query per-se, but because SqlDependency in general is problematic with a large number of clients connected (it blocks a worker thread per app domain connected):
SqlDependency was designed to be used in ASP.NET or middle-tier
services where there is a relatively small number of servers having
dependencies active against the database. It was not designed for use
in client applications, where hundreds or thousands of client
computers would have SqlDependency objects set up for a single
database server.
Updated
Here is the same test as #usr did in his post. Full c# code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.SqlClient;
using DependencyMassTest.Properties;
using System.Threading.Tasks;
using System.Threading;
namespace DependencyMassTest
{
class Program
{
static volatile int goal = 50000;
static volatile int running = 0;
static volatile int notified = 0;
static int workers = 50;
static SqlConnectionStringBuilder scsb;
static AutoResetEvent done = new AutoResetEvent(false);
static void Main(string[] args)
{
scsb = new SqlConnectionStringBuilder(Settings.Default.ConnString);
scsb.AsynchronousProcessing = true;
scsb.Pooling = true;
try
{
SqlDependency.Start(scsb.ConnectionString);
using (var conn = new SqlConnection(scsb.ConnectionString))
{
conn.Open();
using (SqlCommand cmd = new SqlCommand(#"
if object_id('SqlDependencyTest') is not null
drop table SqlDependencyTest
create table SqlDependencyTest (
ID int not null identity,
SomeValue nvarchar(400),
primary key(ID)
)
", conn))
{
cmd.ExecuteNonQuery();
}
}
for (int i = 0; i < workers; ++i)
{
Task.Factory.StartNew(
() =>
{
RunTask();
});
}
done.WaitOne();
Console.WriteLine("All dependencies subscribed. Waiting...");
Console.ReadKey();
}
catch (Exception e)
{
Console.Error.WriteLine(e);
}
finally
{
SqlDependency.Stop(scsb.ConnectionString);
}
}
static void RunTask()
{
Random rand = new Random();
SqlConnection conn = new SqlConnection(scsb.ConnectionString);
conn.Open();
SqlCommand cmd = new SqlCommand(
#"select SomeValue
from dbo.SqlDependencyTest
where ID = #id", conn);
cmd.Parameters.AddWithValue("#id", rand.Next(50000));
SqlDependency dep = new SqlDependency(cmd);
dep.OnChange += new OnChangeEventHandler((ob, qnArgs) =>
{
Console.WriteLine("Notified {3}: Info:{0}, Source:{1}, Type:{2}", qnArgs.Info, qnArgs.Source, qnArgs.Type, Interlocked.Increment(ref notified));
});
cmd.BeginExecuteReader(
(ar) =>
{
try
{
int crt = Interlocked.Increment(ref running);
if (crt % 1000 == 0)
{
Console.WriteLine("{0} running...", crt);
}
using (SqlDataReader rdr = cmd.EndExecuteReader(ar))
{
while (rdr.Read())
{
}
}
}
catch (Exception e)
{
Console.Error.WriteLine(e.Message);
}
finally
{
conn.Close();
int left = Interlocked.Decrement(ref goal);
if (0 == left)
{
done.Set();
}
else if (left > 0)
{
RunTask();
}
}
}, null);
}
}
}
After 50k subscriptions are set up (takes about 5 min), here are the stats io of a single insert:
set statistics time on
insert into Test..SqlDependencyTest (SomeValue) values ('Foo');
SQL Server parse and compile time:
CPU time = 0 ms, elapsed time = 0 ms.
SQL Server Execution Times:
CPU time = 16 ms, elapsed time = 16 ms.
Inserting 1000 rows takes about 7 seconds, which includes firing several hundred notifications. CPU utilization is about 11%. All this is on my T420s ThinkPad.
set nocount on;
go
begin transaction
go
insert into Test..SqlDependencyTest (SomeValue) values ('Foo');
go 1000
commit
go
the documentation says:
SqlDependency was designed to be used in ASP.NET or middle-tier
services where there is a relatively small number of servers having
dependencies active against the database. It was not designed for use
in client applications, where hundreds or thousands of client
computers would have SqlDependency objects set up for a single
database server.
It tells us not to open thousands of cache dependencies. That is likely to cause resource problems on the SQL Server.
There are a few alternatives:
Have a dependency per table
Have 100 dependencies per table, one for every percent of rows. This should be an acceptable number for SQL Server yet you only need to invalidate 1% of the cache.
Have a trigger output the ID of all changes rows into a logging table. Create a dependency on that table and read the IDs. This will tell you exactly which rows have changed.
In order to find out if SqlDependency is suitable for mass usage I did a benchmark:
static void SqlDependencyMassTest()
{
var connectionString = "Data Source=(local); Initial Catalog=Test; Integrated Security=true;";
using (var dependencyConnection = new SqlConnection(connectionString))
{
dependencyConnection.EnsureIsOpen();
dependencyConnection.ExecuteNonQuery(#"
if object_id('SqlDependencyTest') is not null
drop table SqlDependencyTest
create table SqlDependencyTest (
ID int not null identity,
SomeValue nvarchar(400),
primary key(ID)
)
--ALTER DATABASE Test SET ENABLE_BROKER with rollback immediate
");
SqlDependency.Start(connectionString);
for (int i = 0; i < 1000 * 1000; i++)
{
using (var sqlCommand = new SqlCommand("select ID from dbo.SqlDependencyTest where ID = #id", dependencyConnection))
{
sqlCommand.AddCommandParameters(new { id = StaticRandom.ThreadLocal.GetInt32() });
CreateSqlDependency(sqlCommand, args =>
{
});
}
if (i % 1000 == 0)
Console.WriteLine(i);
}
}
}
You can see the amount of dependencies created scroll through the console. It gets slow very quickly. I did not do a formal measurement because it was not necessary to prove the point.
Also, the execution plan for a simple insert into the table shows 99% of the cost being associated with maintaining the 50k dependencies.
Conclusion: Does not work at all for production use. After 30min I have 55k dependencies created. Machine at 100% CPU all the time.

SQL Server CLR Integration enlisting in current transaction

I'm trying to use CLR integration in SQL Server to handle accessing external files instead of storing them internally as BLOBs. I'm trying to figure out the pattern I need to follow to make my code enlist in the current SQL transaction. I figured I would start with the simplest scenario, deleting an existing row, since the insert/update scenarios would be more complex.
[SqlProcedure]
public static void DeleteStoredImages(SqlInt64 DocumentID)
{
if (DocumentID.IsNull)
return;
using (var conn = new SqlConnection("context connection=true"))
{
conn.Open();
string FaceFileName, RearFileName;
int Offset, Length;
GetFileLocation(conn, DocumentID.Value, true,
out FaceFileName, out Offset, out Length);
GetFileLocation(conn, DocumentID.Value, false,
out RearFileName, out Offset, out Length);
new DeleteTransaction().Enlist(FaceFileName, RearFileName);
using (var comm = conn.CreateCommand())
{
comm.CommandText = "DELETE FROM ImagesStore WHERE DocumentID = " + DocumentID.Value;
comm.ExecuteNonQuery();
}
}
}
private class DeleteTransaction : IEnlistmentNotification
{
public string FaceFileName { get; set; }
public string RearFileName { get; set; }
public void Enlist(string FaceFileName, string RearFileName)
{
this.FaceFileName = FaceFileName;
this.RearFileName = RearFileName;
var trans = Transaction.Current;
if (trans == null)
Commit(null);
else
trans.EnlistVolatile(this, EnlistmentOptions.None);
}
public void Commit(Enlistment enlistment)
{
if (FaceFileName != null && File.Exists(FaceFileName))
{
File.Delete(FaceFileName);
}
if (RearFileName != null && File.Exists(RearFileName))
{
File.Delete(RearFileName);
}
}
public void InDoubt(Enlistment enlistment)
{
}
public void Prepare(PreparingEnlistment preparingEnlistment)
{
preparingEnlistment.Prepared();
}
public void Rollback(Enlistment enlistment)
{
}
}
When I actually try to run this, I get the following exception:
A .NET Framework error occurred during execution of user defined routine or aggregate 'DeleteStoredImages':
System.Transactions.TransactionException: The operation is not valid for the state of the transaction. ---> System.Transactions.TransactionPromotionException: MSDTC on server 'BD009' is unavailable. ---> System.Data.SqlClient.SqlException: MSDTC on server 'BD009' is unavailable.
System.Data.SqlClient.SqlException:
at System.Data.SqlServer.Internal.StandardEventSink.HandleErrors()
at System.Data.SqlServer.Internal.ClrLevelContext.SuperiorTransaction.Promote()
System.Transactions.TransactionPromotionException:
at System.Data.SqlServer.Internal.ClrLevelContext.SuperiorTransaction.Promote()
at System.Transactions.TransactionStatePSPEOperation.PSPEPromote(InternalTransaction tx)
at System.Transactions.TransactionStateDelegatedBase.EnterState(InternalTransaction tx)
System.Transactions.TransactionException:
at System.Transactions.TransactionState.EnlistVolatile(InternalTransaction tx, IEnlistmentNotification enlistmentNotification, EnlistmentOptions enlistmentOptions, Transaction atomicTransaction)
at System.Transactions.TransactionStateSubordinateActive.EnlistVolatile(InternalTransaction tx, IEnlistmentNotification enlistmentNotification, EnlistmentOptions enlistmentOptions, Transaction atomicTransaction)
at System.Transactions.Transaction.EnlistVolatile(IEnlistmentNotification enlistmentNotification, EnlistmentOptions enlistmentOptions)
at ExternalImages.StoredProcedures.DeleteTransaction.Enlist(String FaceFileName, String RearFileName)
at ExternalImages.StoredProcedures.DeleteStoredImages(SqlInt64 DocumentID)
. User transaction, if any, will be rolled back.
The statement has been terminated.
Can anyone explain what I'm doing wrong, or point me to an example of how to do it right?
You have hopefully solved this by now, but in case anyone else has a similar problem: the error message you are getting suggests that you need to start the Distributed Transaction Coordinator service on the BD009 machine (presumably your own machine).
#Aasmund's answer regarding the Distributed Transaction Coordinator might solve the stated problem, but that still leaves you in a non-ideal state: You are tying a transaction, which locks the ImagesStore table (even if it is just a RowLock), to two file system operations? And you need to BEGIN and COMMIT the transaction outside of this function (since that isn't being handled in the presented code).
I would separate those two pieces:
Step 1: Delete the row from the table
and then, IF that did not error,
Step 2: Delete the file(s)
In the scenario where Step 1 succeeds but then Step 2, for whatever reason, fails, do one or both of the following:
return an error status code and keep track of which DocumentIDs got an error when attempting to delete the file in a status table. You can use that to manually delete the files and/or debug why the error occurred.
create a process that can run periodically to find and remove unreferenced files.

Resources