Tuesday, December 25, 2012

Technical Evalutions for a Real Time Searching System Part 1 -- Overview

I recently conducted a technical evaluations for a real time search system (RTSS hereafter). It primarily focuses on mature open source frameworks. Many technologies involved are quite new and very interesting.
I present my ideas in a 4-part series:
  • Part 1 -- Overview
  • Part 2 -- Solutions Based on SQL and NoSQL DB (H2 and MongoDB)
  • Part 3 -- Solutions Based on Index Search Engines (Lucene Solar and Sphinx);
  • Part 4 -- Solutions Based on XML DB (MarkLogic and Berkeley DB XML)

1 Main Business Requirements for RTSS

  • Supports auto-complete and spell checkers on customized list of names;
  • Supports advanced searching and browsing on many combinations of attributes;
  • Support ranking / relevance;
  • Supports different types of clients (Java/C/C#) by being service-oriented;
  • Supports huge user based by being reliable and scalable;

 2 Architecture Layers

The RTSS can be divided into 3 logic layers. From front-end to back-end, they are as follows:
  • The UI layer that includes web applications hosted on Jetty/Tomcat/JBoss, and non-web applications such as Java Swing and c/C++;
  • The service or business layer that provides results to the UI layer for auto-complete and spell-checker and all kinds of search requests either in a language neutral format such as XML, Fast Infoset, JSON or such descriptive binary protocol as Google Protocol Buffers;
  • The DAO layer that provides the service layer with search results. Data stores (SQL, NoSQL, index search engines and XML DB) will also be discussed along with DAO.
The 3 layers may be hosted on different servers for flexibility and scalability. However, they are usually combined together if possible for better performance. Data stores are usually on a separate server. However, in-memory DB can be embedded into the service layer.
In the following discussion, the lines or words in bold blue are viable options for final selection.

GPGPU High Performance Computing using OpenCL -- A Uniform and Portable API across Multi-core CPU and Many-core GPGPU

Although this post overlaps with this previous one, it shows helpful diagrams and its approach is more systematic. Here it is.

1         Multi-Core CPU and Many-Core GPGPU

CPUs began from single-threaded serial processing of general-purpose tasks such as arithmetic, logic and I/O operations.  For many years since then, CPUs improved the performance of single-threaded applications simply by reducing their latencies based on Moore’s Law [1]. Each new CPU provided increased clock frequency, which enabled applications to run faster.  Applications performance was improved by merely porting them to the new CPU even without code modification.
However, every increase in clock speed imposes more demands on power and cooling requirements, and also tends to increase the disparity between CPU and host memory access times.  To counter this, CPUs design employed complex low-level Instruction-Level Parallelism (ILP) [2] such as instruction pipelining, out-of-order execution and speculative execution while still keeping the frequency in economically allowed ranges.  But, efforts to further exploit ILP have stalled since the late-1990s due to the following reasons:
1.       ILP proves inadequate to keep CPU from stalling for host memory accesses because ILP often involves sophisticated hardware design and many applications have difficult-to-predict code;
2.       Many applications need to process independent large data sets;
3.       Many applications need to handle multiple concurrent tasks.
Instead, the industry shifted focus to applying Moore’s Law to implement more cores and larger caches per processor chip.  Larger caches help to reduce the access time gap between CPU and host memory, especially for applications with spatial locality. Cores are units that execute a single stream of program instructions.  A physical core, if it supports core-level multi-threading [3] such as Intel’s HyperThreading, is equivalent to two or more logic ones each of which supports one separate hardware thread (by hardware thread, we mean the execution context (program counters, registers, etc) is maintained on-chip during the entire lifetime of the thread so that context switching between threads can be done in just one cycle and virtually without cost. We will use “core” and “thread” hereafter unless otherwise noted). As you can easily reason, the more cores a system has, the greater parallel computing capacity. This is the case of GPGPU that can easily support tens of thousands threads.
However, these highly parallel computing processes do need us to parallelize application algorithms as much as possible in order to improve performance based on Amdahl’s Law [4]. Algorithm designers employ two commonly used Thread-Level Parallelism (TLP) models [5] to do so. One is Data Parallelism (DP) that decomposes data sets into multiple independent data elements to which a single stream of instructions are concurrently applied.  DP is often used at fine-grained level and supported by a processor’s SIMD function [6].  The other is Task Parallelism (TP) that decomposes applications into many independent tasks that can be concurrently executed across different cores. TP is often used at coarse-grained level and supported by a processor’s MIMD function [6]. TLP improves overall application performance through high throughput of all threads, though latency is actually increased at individual thread levels.
Thanks to the commoditization of many of these parallel processors in the last decade, they are inexpensively available to us as either a workstation or a desktop cluster.  Many large data set processing applications such as CT reconstruction, used to take many hours even several days on traditional serial CPU, can now finish in several minutes on these parallel processors without even resorting to any expensive proprietary hardware such as ASIC, FPGA, Cell BE or multi-node clusters.
Next, we will review two typical parallel processors: multi-core CPU and many-core GPU dedicated to General-Purpose Computing (GPGPU) and how OpenCL provides a uniform parallel computing paradigm for them. We conducted our researches on a Dell Precision T7500 Tower Workstation [7]. It features two Intel Xeon quad-core E5507 CPUs, and one Nvidia Tesla C2050 GPGPU.  Because both processors represent relatively latest developments on parallel computing on CPU and GPGPU, our discussions are around them without loss of generality.

1.1       Multi-Core CPU

From the first 32-bit Intel Core architecture introduced in Jan 2006 up to the latest 64-bit Intel  Xeon  octo-core architecture [8], all multi-core CPUs share the same principle that it is easier to improve overall application performance using throughput-oriented multiple cores than using a latency-oriented single core.  However CPU’s primary role as a general-purpose engine determines trade-off must be made among flow controls, caches and number of cores.
The 64-bit Xeon processors, as shown in Figure 1, represent Intel’s latest developments on multi-core and multi-processor. They target the server and workstation marets. Here are the highlights:
  1. Each processor supports a maximum of 4 threads. HyperThreading is not supported;
  2. Large on-chip L1,L2 and L3 caches (a total of 5.25M) and flow control (not shown in Figure 1);
  3. Each processor supports 3 parallel memory channels that are totally 192-bit wide;
  4. Each processor’s memory bandwidth is 19.2GB/s and peek single floating-point capability is about 36GFLOP/s;
  5. Each core supports 128-bit SIMD vectors through SSE; MIMD is supported at processor level through the 4 cores;
  6. NUMA supports – each processor accesses its locally attached host memory through the integrated memory controller much faster than access other remote host memory attached to the other processor through Quick Path Interconnect (QPI).

1.2       Many-Core GPGPU


From the first GPU invented in 1999 by Nvidia to process graphics only up to its latest Fermi-based Tesla products dedicated to general purpose computing [9] [10], GPU has evolved into a massively parallel processor that can run tens of thousands of threads on its hundreds of cores.
C2050, as shown in Figure 2, turns PCs and workstations into affordable small clusters. Here are the highlights:
  1. Each Streaming Multiprocessor (SM) schedules parallel threads in groups of 32 called warps (AMD’s FireStream GPGPU, C2050’s equivalent, uses wavefronts for the same purpose. So we will use warps hereafter without loss of generosity) to 32 scalar cores.  In other words, each SM maintains its own single stream of instructions of the kernel and each instruction is scheduled to 32 cores on different data in lockstep. This is how Nvidia implements SIMD using its so-called SIMT [11]. SM’s SIMD is 1024-bit wide that is much wider than E5507’s. MIMD is supported by scheduling concurrent kernels across the 14 SMs;
  2. The 14 SMs support a maximum of 21,504 (1,536 x 14) active (resident) threads on the 448 (32 x 14) scalar cores thanks to the large amounts of registers. These numbers of threads and cores are much larger than CPU’s; 
  3. Small on-chip caches (the total is about 1MB) and simple flow control (not shown in Figure 2), which means higher latency than CPU; 
  4. Each SM support 48KB shared memory whose access time is between that of cache and global memory.  This shared memory is explicitly available to programmers through API (on the other hand, caches are transparent to programmers). CPU doesn’t have such memory; 
  5.  All SMs access the off-chip global memory through a wide 384-bit interface that has 6 parallel channels; 
  6. The device connects to the host through a PCIe x 16 Gen 2 slot. Its bandwidth is 8GB/s that is much slower than its 144GB/s memory bandwidth.  The device’s peek single floating-point capability is about 1TFLOP/s that is much larger than E5507’s.

 

1.3       Different Workload Processing on CPU and GPGPU

Because today’s computer systems often form heterogeneous parallel computing ecosystems with CPUs, GPGPUs and / or other types of processors as shown in Figure 3 (T7500 ecosystem hereafter),  it is important to know the type of workloads that each processor does best so that you can partition workloads and schedule them to appropriate processors using OpenCL. By doing so, you can access all the computing power (serial and parallel) of your systems, achieving very high system utilization.


  1.  GPGPU is suited to process large date sets while CPU is better suited to low parallelism (also serial operations such as disk and network IOs, and hard-to-parallelized programs).
    This is because small data sets can’t justify the time incurred by GPGPU’s slow data transfer on PCIe and other setups. Large data sets also spawn a large number of threads that are required in order to take full advantage of GPGPU’s massively parallel structure and to hide its high latency on memory accesses and other operations; 
  2. GPGPU is suited to algorithms with high arithmetic density (the number of arithmetic operations per memory access).
    This is also to hide GPGPU’s high latency. When a warp is stalled due to latency, SM swaps in a ready warp for execution, if any, through its fast context switch to maximize GPGPU utilization.  Latency can be fully hidden if SM always has some arithmetic instructions to issue for some warp at every clock cycle during that latency period.  Obviously the more arithmetic instructions and threads, the more latency is hidden; 
  3. Data transfer on PCIe should be minimized due to PCIe’s slowness.
    In order to keep a high number of operations performed on GPGPU per data element transferred, you sometimes have to move more parts of your program to GPGPU even those parts perform faster on CPU; 
  4. GPGPU is suited to programs whose memory accesses have spatial locality.
    Because off-chip memory accesses have the highest latency, both CPU and GPGPU access a block of consecutive memory such as 32 bytes instead of just the requested single data such as a 4-byte integer. However, GPGPU further coalesces memory accesses by a warp into as few memory transactions as possible.  Better spatial locality means fewer memory transactions, higher cache hit rates and less waste of bandwidth. This is more important to GPGPU than to CPU because GPGPU has smaller caches and more threads; 
  5. GPGPU is suited to programs whose control flow logic is simple.
    This is because SM schedules a common instruction to a warp at a time. Full efficiency is realized when all 32 threads of a warp agree on their execution path. If threads of a warp diverge via a data-dependent conditional branch, the warp serially executes each branch path taken, disabling threads that are not on that path, and when all paths complete, the threads converge back to the same execution path. So divergence leaves SM underutilized.  This is also because CPU has more complex control flow logic and ILP mechanisms.

2         OpenCL – A Better Parallel Computing Paradigm

Parallel computing does need users to parallelize their algorithms using DP and / or TP of some programming paradigm.  However, parallel computing for heterogeneous processors is challenging as traditional programming paradigms for CPU and GPU are very different.
For example, there are Pthreads and OpenMP for CPU, and they all assume a single shared address space and require users to possess complex multi-threading skills.  GPGPU programming originally required users to possess intimate knowledge of graphics APIs (OpenGL or DirectX) and GPU architecture and to make their non-graphics applications look like graphics ones by mapping them into problems that drew triangles and polygons. This placed a great constraint for non-graphics domain users. This old approach was adopted by Fang et al in their CT reconstruction algorithm [12].
With the advent of Unified Shaders,  GPU dedicated to general purpose computing (GPGPU) such as the Tesla series from Nvidia and the FireStream series from AMD, and programming paradigms such as CUDA from Nvidia and APP from AMD, CPU-based non-graphics applications can now directly access the tremendous performance of GPGPU without the above limitations.
Unfortunately all of the above different programming paradigms have very sharp learning curves (it should be very formidable for a non-computer domain expert to program all the parallel processors in the T7500 ecosystem using these paradigms) and some are vendor-specific. Worse yet, none of them may be supported by future parallel processors.
OpenCL [13] is an open industry standard for general purpose parallel programming across CPU, GPGPU, and other processors. From the software perspective, it is a framework consisting of an API to create host programs to coordinate parallel computing across heterogeneous processors, and a subset of ISO C99 with parallelism extensions to create kernels. OpenCL supports both DP and TP without users even knowing complex multi-threading mechanism. This, along with its support of universal C language, can greatly shorten the learning curve of non-computer domain scientists and engineers.
From the hardware perspective, OpenCL enables users to design portable, efficient and high-performance applications through its low-level, close-to-metal abstractions over heterogeneous processors (Although many details of the underlying hardware are exposed, OpenCL can’t expose all details. Otherwise OpenCL’s portability and programmability will be restricted).
In the following sub sections, we introduce these abstractions by borrowing some figures from OpenCL 1.0 specification [13], and mapping OpenCL concepts to the hardware in the Dell T7500 workstation. Readers should be able to figure out the function of each OpenCL concept based on the mapping and our previous introduction on CPU and GPGPU.

2.1       Platform Abstraction


Table 1 shows how the various components in Figure 4 map to CPU and GPGPU.

E5507 CPU
C2050 GPGPU
Compute Device
the CPU chip
the GPGPU chip
Compute Unit (CU)
the 8 cores
the 14 SMs
Processing Element (PE)
the 8 cores
the 32 cores in each SM
Host
Dell T7500 workstation
Dell T7500 workstation
Table 1: Platform Mapping
OpenCL provides API to query all CUs connected to the host and to submit parallel computations on PEs.

2.2       Execution Abstraction

An OpenCL program consists of two parts: kernels that execute on one or more devices and a host program that executes on the host.  OpenCL defines an index space called NDRange (the grid on CUDA) on devices. An NDRange can have 1 to 3 dimensions.  Each point in the index space is called a work-item.  When the host program submits a kernel to a device, an instance of the kernel (a thread) executes for each work-item.
Work-items are organized into work-groups at coarse-grained level, each of which is scheduled to one CU independently. Because C2050’s smallest execution unit is a warp, the work-group size should be a multiple of 32 for best performance. At fine-grained level, each work-term in a work-group can synchronize with each other on shared data accesses.  Work-groups often work with local memory (defined in the next section) on GPGPU to eliminate redundant and stride access on global memory if your program possesses either of them and has spatial locality.
DP is supported by mapping each work-item to one or more data element(s). In strict DP, the mapping is one-to-one. OpenCL implements a relax version of DP where a strict one-to-one mapping is not required.  You should determine the dimension size and mapping based on your program logic and the device’s capability.  TP is supported by submitting multiple task kernels across CUs. Each of these kernels equivalently has only one work- item, which is wasting 31 cores on each SM of C2050. So TP is usually used on CPU each core of which executes one task kernel.
This execution abstraction is scalable because DP work-groups and TP task kernels can automatically and independently be scheduled to any available CU as the host scales to more CUs.

2.3       Memory Abstraction

Because C2050 caches global memory accesses into its L1 and L2 cache, the performance of applications whose memory accesses are not predicable (or possess poor spatial locality) can still be improved.
Because the on-chip local memory’s access time on GPGPU is close to caches, it should be used if a work-group’s spatial locality can be accommodated in it and the work-group items have redundant and /or stride access on global memory. 
Both CPU and GPGPU have registers that consume zero extra clock cycles per instruction. OpenCL doesn’t explicitly expose registers. Instead kernel compilers usually optimize automatic variables to use registers. However if there are more automatic variable than available, these extra automatic variables will be place in the off-chip private memory and this is known as register spilling.  The register usage usually has a significant impact on performance.
Since the constant cache on GPGPU is separate from the private and global caches in L1 and L2, it is not tainted by them. So it can improve performance if you can put constant data there, especially when your kernel doesn’t have good spatial locality.


E5507 CPU
C2050 GPGPU
Global Memory
The off-chip host memory
The off-chip global memory
Local Memory
implemented in host memory
The on-chip shared memory
Private Memory
implemented in host memory
The off-chip local memory
Constant Memory
implemented in host memory
implemented in global memory
Global Memory Cache
implemented in L1, L2 and L3 caches
Implemented in L1 and L2 caches
Constant Memory Cache
implemented in L1, L2 and L3 caches
The constant cache

Table 2:
Memory Mapping


Wednesday, May 16, 2012

My Recent Interview Experience in the Financial Industry -- Computer Science Fundamentals

A base salary around $150k seems to be the watershed between a regular job that can be easily landed and a better paid job that needs a lot of efforts.
If you want to get better compensations, it is mandatory that you master computer science fundamentals. Most of financial firms I dealt with just didn't ask me any financial domain knowledge. Nor did they ask so-called populate technical skills such as Ajax, j2ee web development, Java Swing or JDBC etc. Instead, they just focused on fundamentals such as thread, socket, data structures and algorithms. Especially they wanted to make sure you are good at language-agnostic commonly-used data structures and algorithms.

I used to think that only hedge funds, prop trading firms, and high-profile technology firms such as Google and Amazon asked data structures and algorithms. But now it seems that other firms are just following suit.
For thread, it is not enough to only know how to create them. You also need to know thread safety, thread pooling, memory model etc.
For socket, it is not enough to only know TCP and UDP. You also need to know the Select paradigm and how to fine tune performance and latency.
For data structures, you should be very familiar with collection, set, list, queue, hash and tree.
For algorithms, you should know very well complexity analysis (the Big-O notation),  all common sorting, median and order statistics, iterative and recursive, divide and conquer, exhaustive, back trace and dynamic programming.

There is a very well-known book "Introduction to Algorithms" from the MIT press. If you was not touch this book in school, you need to learn it by yourself unless you don't want to work for good companies.

If you have worked for many years, you feel it is not quite fare to be asked so many algorithms because new graduates usually have fresh memory on algorithms than you. But, if you think you are a smart guy who is willing to accept challenges, you should welcome any interview questions.

Speaking challenges, many firms also ask you some brain teaser questions. Basically the interviews wanted to test your analysis skills and logic reasoning capacities. Since most developers don't often face such problems at work, they feel it more difficult to work on them than on regular programming issues.
The good news is many brain teasers are relatively easier if you go through some training. A good source is the "Brain Teaser" link under my "INTERVIEW WEB SITES" section on the left.

Wednesday, November 2, 2011

Floating-Point Number Equality Comparison in Oracle, Sybase / SQL Server, Java and JMS

As you know that computer can't store all floating-point numbers exactly primarily due to memory constraint. A double value such as 0.1 is actually represented as 0.1000000000000000055511151231257827021181583404541015625 in computer memory. Due to rounding errors, your equality comparison on floating-point numbers will probably fail.
In this topic I will show how SQL and Java store exact floating-point numbers (or just decimal numbers), and how floating-point literals are represented.

SQL99 uses FLOAT, REAL and DOUBLE PRECISION for approximate floating-point numbers, and NUMERIC(p,s) / DECIMAL (p,s) for exact floating-point numbers. For floating-point literals, SQL99 stores them as approximate floating-point types if they use the E notation (scientific notation); otherwise as the exact floating-point type if the literals meet the precision and scale of the exact types.
Because the NUMERIC(p,s) / DECIMAL (p,s) stores values without loss of precision for your decimal numbers, it should be used if you want to compare the equality between a floating-point column (in DB) or variable (in Java) to a decimal literal such as 0.1.
Different database vendors may have a bit different variants from the above specification.

Oracle 10g uses NUMBER(p,s) for both floating-point types in addition to its BINARY_FLOAT, BINARY_DOUBLE and FLOAT(p) for approximate types. Make sure you always specify the precision (p) for exact types.
If a floating-point is sufficed by f  or F (for BINARY_FLOAT), or d or D (for BINARY_DOUBLE), it is of approximate types; otherwise of the exact NUMBER(p,s) type.

Sybase ESA 15 and MS SQl Server 2008 both comply with the above SQL99 standard very well. And both also have MONEY(SMALLMONEY) for exact float-point numbers.

Java has BigDecimal to store decimal values exactly. For example new BigDecimal("0.1")  stores 0.1 exactly.

JMS's message selector is a string whose syntax is based on a subset of SQL92. Unfortunately JMS doesn't support exact floating-point numbers and restricts its exact numeric literal only to those without a decimal.

Wednesday, August 17, 2011

Key Concepts to Understand Spring's Consistent Transaction Handling

Class FooBarService shows simplified pseudo code for different transaction handling paradigms using JDBC,Hibernate and JTA.
Class FooBarServiceSpring shows
simplified pseudo code for consistent transaction handling using Spring.
Obviously Spring's transaction handling is quite different from JDBC and Hibernate; but is closer to JTA. However, by no mean does Spring only support JTA. Instead, it is only a matter of configuring different PlatformTransactionManager (PTM hereafter) in your application context in order to support different transaction handling paradigms.  For example, Spring's DataSourceTransactionManager, HibernateTransactionManager and JTATransactionManager are for JDBC, Hibernate and JTA, respectively.

This discussion can help readers understand some internals of Spring's  PTM implementations on heterogeneous native transaction platforms.


//different transaction paradigms using JDBC,Hibernate and JTA
class FooBarService {
  //local transaction paradigm using JDBC
  public void foo() {
    //creates a session with the backend database
    Connection conn = dataSource.getConnection();
    //starts a new local transaction.
    conn.setAutoCommit(false);

    updates-data-through-this-conn

    //commits the local transaction
    conn.commit();
    //closes the session
    conn.close();
  }

  //local transaction paradigm using Hibernate
  public void bar() {
    //creates a session with the backend database
    Session session = sessionFactory.openSession();
    //starts a new local transaction.
    Transaction tx = session.beginTransaction();

    updates-data-through-this-session

    //commits the local transaction
    tx.commit();
    //closes the session
    session.close();
  }

  //Global transaction paradigm using JTA
  public void fooBar() {
    userTransaction.begin();

    //creates a session with the backend database1
    Connection conn1 = dataSource1.getConnection();
    updates-data-through-this-conn1

    //creates another session with the backend database2
    Connection conn2 = dataSource2.getConnection();
    updates-data-through-this-conn1

    //closes the two sessions
    conn1.close();
    conn2.close();

    userTransaction.commit();
  }
}


//consistent transaction handling paradigms using Spring
class FooBarServiceSpring {
  @Transactional
  public void foo() {
    //updates data through some data source connection
  }
 
  @Transactional(propagation=Propagation.REQUIRES_NEW)
  public void bar() {
    //updates data through some data source connection
  }
  @Transactional
  public void fooBar() {
    foo();
    bar();
  }
}


1.
Key Transaction Abstraction
The following 3 interfaces are key to understand Spring's transaction handling:
  • TransactionDefinition
    It allows you to define transaction requirements (isolation level, propagation behavior, timeout and read-only status) before your transaction is started.
    In FooBarServiceSpring, you specify your TransactionDefinition in @Transactional.
  • TransactionStatus
    Once your transaction is started, this interface allows you to query its current status including rollback flag and new vs existing flag, and to mark it to rollback only (for example when you encounter an exception).
    Spring's default TransactionStatus implementation also includes the underlying native transaction such as a connection for JDBC, a session for Hibernate and a user transaction for JTA.
    In FooBarServiceSpring, @Transactional allows you to specify your rollback rules. For other status information, you either implicitly knows(such as IsNewTransactio and IsComplete) or can retrieve from the underlying native transaction bound to the thread (Spring binds JDBC connection and Hibernate Session to the thread; JTA's UserTransaction is also bound to a standard JNDI name). Please note that Spring doesn't bind TransactionStatus to the thread because PTM can pass it around in your AOP proxied methods.
  • PlatformTransactionManager
    It wires together the above 2 interface and works as a coordinate overall.
    Specifically its getTransaction() method takes your TransactionDefinition and creates a TransactionStatus representing either a new or existing transaction.  Its commit() and rollback() methods take the returned TransactionStatus and commit and rollback the target transaction, respectively.
    It allows you to demarcate transactions as a singleton because it can get different transactions bound on different threads.
    In FooBarServiceSpring, @Transactional uses whatever transaction manager you configured in your application context such as DataSourceTransactionManager for JDBC.
When we say Spring transaction, we really mean TransactionDefinition and TransactionStatus. The PTM is more like a JTA UserTransaction / TransactionManager. But once again, it handles all types of native transaction API's behind the scene.
    2. Physical Transaction and Logical Transaction
    FooBarServiceSpring's foobar() calls foo() and bar(). Suppose there is no transaction before you call foobar(). So foobar() creates a new physical transaction while foo() starts a nested logical transaction due to its default propagation being REQUIRED; the logic transaction ends when foo() returns. 
    But bar() suspends the current transaction and creates a new physical transaction due to its propagation being REQUIRED_NEW. The new transaction ends and the suspended transaction is resumed when bar() exits.
    Because each (physical or logic) transaction always has a begin and end, we sometimes use transaction scope.

    For data persistence, only the outer physical transaction can commit or rollback. But
    on each individual inner logic transaction (method) level, you always specify your TransactionStatus and can mark transaction to rollback . However the embed native transaction in TransactionStatus is either new (if it is a physical one) or existing (if it participates in an existing one).
    You can also turn on the "validateExistingTransaction" flag in PTM
    so that the inner logic transaction will reject participation if its TransactionStatus is not compatible to the outer TransactionStatus.

    3. Resource, Connection, Session, Transaction and Resource Manager (RM)
    Resource can mean any valuable data; but in PlatformTransactionManager it is either a connection (for JDBC and JMS etc) or session (for Hibernate and JMS etc).

    Both connection and session represent a communication link with the some backend resource manager (RM for short). In the database world, both termscan be used interchangeably. Although Hibernate uses session, it uses a connection behind the scene.
     
    Transaction represents a unit of work. We mentioned in the above Section 1 that Spring's abstract transaction covers an underlying native transaction.
    For JDBC and Hibernate, you create local transactions using connections / sessions and because they have one-to-one relationship, you can sometimes interchange the 2 terms. In other words, if the inner logic transaction participants in the outer physical transaction, it must reuse the the same connection / session.
    For JTA, you creates global transactions using transaction managers; multiple connections can be enlisted in global transactions as transaction branches. One thing that is very different from JDBC and Hibernate is that you can conduct DML changes using different connections to the same backend RM instance in the same global transaction (This is because when a connection is enlisted, XID is created to represent this branch both in the JTA and the RM. Even you use a different connection, the RM can still identify the same transaction using the passed-in XID).

    RM managers resources (transaction) on behalf of you. It is commonly used in JTA/XA world. For most of us, databases are common RM.

    4. Transaction and Session -- Which Comes First?
    In the foo() and bar() of FooBarService, you first create a connection / session, then starts a transaction. Before you close the connection / session, you first commit / rollback the transaction.
    But in the foobar() of
    FooBarService, you first starts a transaction, then create a connection / session. Before you commit / rollback the transaction, you first close the connection / session.
    In FooBarServiceSpring, all you have is just @transactional and you are not allowed to open or close any connection / session explicitly. So how can Spring hide the difference between JDBC, Hibernate and JTA and whois taking care of connections / session anyway?
    Data access experience tells us that we always need a connection / session and its lifecycle methods (open, close etc) are just boilerplate and resource will leak if you forget to call the close method (actually this happens very often). But we must define transaction requirements by ourselves. For example, only you know that the bar() in
    FooBarServiceSpring requires REQUIRE_NEW propagation based on your business rules.

    So Spring is taking care of connections / sessions behind the scene. You just need to specify you transaction requirement using @transactional. The next section tell you more details. 
    5. Transaction and Session -- Resource Synchronization with Transaction
    Because you always explicitly specify your transaction requirements, Spring synchronizes the needed resource (connection or session) with the transaction at both transaction start time and ending time or at the transaction scope boundaries.
     

    For JDBC and Hibernate, Spring binds the connection / session (hence also local transaction) to the thread so that it is always available on the execution call stack. 
    Spring PTM automatically either retrieves an existing connection / session from the thread (if an outer transaction is already there) or creates a new connection / session and binds it to the thread before starting a transaction.
    When transaction is right before being committed or rolled back, Spring PTM also automatically closes the connection / session and unbinds it from the thread.


    For JTA, Spring gets the UserTransaction from the standard JNDI name, so the global transaction is also available on the execution call stack.
    When you retrieves a connection / session, it is automatically enlisted in the global transaction (Spring doesn't help here; the underlying XADataSource usually does the magic).

    When the JTA transaction is right before being committed or rolled back, Spring PTM once again automatically closes the connection / session .

    6. Nested Transaction and Transaction Suspend / Resume
    • For nested transaction, both  the outer and inner transaction are live in your thread while when a transaction is suspended, your thread work on another new transaction;
    • Nested transactions are dependent i.e. only the outer transaction is physical and can commit the change; but you can rollback the inner logic transaction.
      The suspended transaction and the new transaction are two physical transactions and are independent i.e. they can both commit or rollback independently;
    • Suspending tranasction is deadlock-prone because the backend RM still holds the resource locks on the suspended transaction even your JTA has switched to another new transaction. You should be very careful even your application also locks some resources in the suspended transaction that are needed by the new transaction;
    • Nested transactions are only supported by JDBC with the savepoint feature. Because XA doesn't support nested transactions, neither JTA can (This seems to be ironic because most databases support savepoint);
    • Suspending a JDBC transaction in Spring means first unbinds it from the thread, then creates a new connection and binds to the thread. For JTA, Spring just delegates the suspend/resume to the underlying UserTransaction in addition to its own bookkeeping for resource synchronizations.
    7. Propagation.SUPPORTS and No Transaction
    No transaction means your data changes are committed on each individual SQL statement level (it is autocomit for JDBC) instead of grouping several SQL statements in a transaction.

    Propagation.SUPPORTS also commits data changes on each individual SQL statement level if there is no existing transaction. But if you set your TransactionSynchronization in PTM to ALWAYS, it also supports resource synchronization on an empty transaction scope (empty transaction scope means no underlying native transaction).
    To make it concrete, the same JDBC connection or Hibernate Session will bind to the thread inside the empty transaction scope for reuse in the execution call stack until the scope ends.  Be aware that the connection is of course in autocommit mode.

    Please remember that if you empty transaction scope has active resource synchronization, DON'T nested Propagation.REQUIRED or Propagation.REQUIRED_NEW in it because resources synchronizations for the 2 transaction scopes will conflict (unfortunately you have to read the PMT source code in order  to get to the bottom).