Spring synchronize saving to database with another instances - spring-boot

I have 2 instances of my Spring boot application, I am using spring JPA.
Cron scheduler run method every one hour which
first, check if record is already updated, if it's updated it should skip and don't update, but it's updated anyway on both instances.
How to implement, something like synchronized, allow to read only when something is done?
#Scheduled(cron = "0 0 * * * ?", zone = "Europe/Paris")
public void updateServers() {
applicationServerRepository.findAll().forEach(this::updateServerInfo);
}

Please explain the problem more detailed&exact.
But as I understand, you should:
Or run the cron jobs only in one instance. (Back in 90ies we did it "manually", but spring-boot-profiles offer here great opportunity: decorate "all #scheduled beans" (re-factoring!?) with #Profile("cronFooBar"), and activate/add it only on one instance.)
Or, if you also want cron jobs "load balanced", then you should find a way to synchronize/lock. (probably best in updateServerInfo or updateServers. (more details!? ..I am sure both instances will find "some object to lock on", at least the database (table, row, ..)))
As proposed by Erkan/found on internet, with "little setup" (and discarding quartz!), you can have a handy annotation for this, like:
#SchedulerLock(name = "...",
lockAtLeastForString = "...", lockAtMostForString = "...")
But I suppose, it is also possible with spring-data-jpa (only & quartz) resources, like:
Add (pessimistic) locking:
interface ApplicationServerRepository ... {
#Lock(LockModeType.PESSIMISTIC_READ)
#Query("select as from ApplicationService ...")
findAllForCron();
...
}
Catch it:
#Scheduled(cron = "0 0 * * * ?", zone = "Europe/Paris")
public void updateServers() {
try {
applicationServerRepository
.findAllForCron()
.forEach(this::updateServerInfo);
​}
catch​ (javax.persistence.PessimisticLockException plex) {
logger.info("i get some coffee");
return;
}
}

Related

Schedule a method dinamically using cron of the annotation #Scheduled

I would Like to schedule a method using The annotation #Scheduled using cron, For example I want that the method should be executed everyday in the time specified by the client.
So I would like to get the cron value from the DB, in order to give the client the possibility of executing the method whenever he wants.
Here is my method, it sends emails automatically at 10:00 am to the given addresses, so my goal is to make the 10:00 dynamic.
Thanks for your help.
#Scheduled(cron = "0 00 10* * ?")
public void periodicNotification() {
JavaMailSenderImpl jms = (JavaMailSenderImpl) sender;
MimeMessage message = jms.createMimeMessage();
MimeMessageHelper helper;
try {
helper = new MimeMessageHelper(message, MimeMessageHelper.MULTIPART_MODE_MIXED_RELATED, StandardCharsets.UTF_8.name());
List<EmailNotification> emailNotifs = enr.findAll();
for (EmailNotification i : emailNotifs)
{
helper.setFrom("smsender4#gmail.com");
List<String> recipients = fileRepo.findWantedEmails(i.getDaysNum());
//List<String> emails = recipientsRepository.getScheduledEmails();
String[] to = recipients.stream().toArray(String[]::new);
helper.setTo(to);
helper.setText(i.getMessage());
helper.setSubject(i.getSubject());
sender.send(message);
System.out.println("Email successfully sent to: " + Arrays.toString(to));
}
}
catch (MessagingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
So I'm thinking at the next solution. ( + using the answer accepted here )
Let's say you have a class that imlpements Runnable interface -> this will be your job that gets executed. Let's call it MyJob
Also assume that we have a map that hold the id of the job and it's execution reference ( you'll see in a sec what i'm talking about). Call it something like currentExecutingJobs
Assume you have an endpoint that gets the name of the job and a cron expression from the client
When that endpoints gets called:
You'll look in the map above to see if there is any entry with that job id. If it exists, you cancel the job.
After that, you'll create an instance of that job ( You can do that by using reflection and having a custom annotation on your job classes in which you can provide an id. For example #MyJob("myCustomJobId" )
And from the link provided, you'll schedule the job using
// Schedule a task with the given cron expression
ScheduledFuture myJobScheduledFutere = executor.schedule(myJob, new CronTrigger(cronExpression));
And put the result in the above map currentExecutingJobs.put("myCustomJobId", myJobScheduledFutere)
ScheduledFuture docs
In case you want to read property from database you can implement the EnvironmentPostProcessor and read the necessary values from DB and add it to Environment object, more details available at https://docs.spring.io/spring-boot/docs/current/reference/html/howto.html#howto-spring-boot-application

Benchmarking spring data vs JDBI in select from postgres Database

I wanted to compare the performence for Spring data vs JDBI
I used the following versions
Spring Boot 2.2.4.RELEASE
vs
JDBI 3.13.0
the test is fairly simple select * from admin table and convert to a list of Admin object
here is the relevant details
with spring boot
public interface AdminService extends JpaRepository<Admin, Integer> {
}
and for JDBI
public List<Admin> getAdmins() {
String sql = "Select admin_id as adminId, username from admins";
Handle handle = null;
try {
handle = Sql2oConnection.getInstance().getJdbi().open();
return handle.createQuery(sql).mapToBean(Admin.class).list();
}catch(Exception ex) {
log.error("Could not select admins from admins: {}", ex.getMessage(), ex );
return null;
} finally {
handle.close();
}
}
the test class is executed using junit 5
#Test
#DisplayName("How long does it take to run 1000 queries")
public void loadAdminTable() {
System.out.println("Running load test");
Instant start = Instant.now();
for(int i= 0;i<1000;i++) {
adminService.getAdmins(); // for spring its findAll()
for(Admin admin: admins) {
if(admin.getAdminId() == 654) {
System.out.println("just to simulate work with the data");
}
}
}
Instant end = Instant.now();
Duration duration = Duration.between(start, end);
System.out.println("Total duration: " + duration.getSeconds());
}
i was quite shocked to get the following results
Spring Data: 2 seconds
JDBI: 59 seconds
any idea why i got these results? i was expecting JDBI to be faster
The issue was that spring manages the connection life cycle for us and for a good reason
after reading the docs of JDBI
There is a performance penalty every time a connection is allocated
and released. In the example above, the two insertFullContact
operations take separate Connection objects from your database
connection pool.
i changed the test code of the JDBI test to the following
#Test
#DisplayName("How long does it take to run 1000 queries")
public void loadAdminTable() {
System.out.println("Running load test");
String sql = "Select admin_id as adminId, username from admins";
Handle handle = null;
handle = Sql2oConnection.getInstance().getJdbi().open();
Instant start = Instant.now();
for(int i= 0;i<1000;i++) {
List<Admin> admins = handle.createQuery(sql).mapToBean(Admin.class).list();
if(!admins.isEmpty()) {
for(Admin admin: admins) {
System.out.println(admin.getUsername());
}
}
}
handle.close();
Instant end = Instant.now();
Duration duration = Duration.between(start, end);
System.out.println("Total duration: " + duration.getSeconds());
}
this way the connection is opened once and the query runs 1000 times
the final result was 1 second
twice as fast as spring
On the one hand you seem to make some basic mistakes of benchmarking:
You are not warming up the JVM.
You are not using the results in any way.
Therefore what you are seeing might just be effects of different optimisations of the VM.
Look into JMH in order to improve your benchmarks.
Benchmarks with an external resource are extra hard, because you have so many more parameters to control.
One big question is for example if the connection to the database is realistically slow as in most production systems the database will be on a different machine at least virtually, quite possibly on different hardware.
Is that true in your test as well?
Assuming your results are real, the next step is to investigate where the extra time gets spent.
I would expect the most time to be spent with executing the SQL statements and obtaining the result via the network.
Therefore you should inspect what SQL statements actually get executed.
This might point you to one possible answer that JPA is doing lots of lazy loading and hasn't even loaded most of you really need.

What to use instead of ScheduledLockConfiguration Bean , in shedlock-spring 3.0?

I have a ScheduledLockConfiguration bean configuration.
#Bean
public ScheduledLockConfiguration taskScheduler(LockProvider lockProvider) {
return ScheduledLockConfigurationBuilder
.withLockProvider(lockProvider)
.withPoolSize(5)
.withDefaultLockAtMostFor(Duration.ofMinutes(5))
.build();
}
I just upgraded to shedlock-spring 3.0, and I don't know what to use instead of this Bean?
We can configure like below.
#Component
class TaskScheduler {
#Scheduled(cron = "0 0 10 * * ?")
#SchedulerLock(name = "TaskScheduler_scheduledTask", lockAtLeastForString = "PT5M", lockAtMostForString = "PT14M")
public void scheduledTask() {
// ...
}
}
#Scheduled will support corn format.
#SchedulerLock, the name parameter has to be unique and ClassName_methodName is typically enough to achieve that. We don't want more than one run of this method happening at the same time, and ShedLock uses the unique name to achieve that.
First, we've added lockAtLeastForString so that we can put some distance between method invocations. Using “PT5M” means that this method will hold the lock for 5 minutes, at a minimum. In other words, that means that this method can be run by ShedLock no more often than every five minutes.
Next, we added lockAtMostForString to specify how long the lock should be kept in case the executing node dies. Using “PT14M” means that it will be locked for no longer than 14 minutes.
In normal situations, ShedLock releases the lock directly after the task finishes. Now, really, we didn't have to do that because there is a default provided in #EnableSchedulerLock, but we've chosen to override that here.

springs scheduing jobs for a given

Spring MVC:
how to schedule jobs at a specific time in a day. schedule time differs every day. time when these jobs needs to be run are available in a database table. I was able to read the data from table but not sure how to schedule them in spring mvc. can someone help.
The Spring scheduler requires that you know the time of day at compile time so this is going to get a little weird. But if you want to be creative you can schedule a job at midnight to query the database for the exact time the task should run, sleep until that time, and then execute the task. Something like this:
public abstract class DailyTaskRunner {
// Execute the specific task here
protected abstract void executeTask();
// Query the database here
// Return the number of milliseconds from midnight til the task should start
protected abstract long getMillisTilTaskStart();
// Run at midnight every day
#Scheduled(cron="0 0 * * *")
public void scheduledTask() {
long sleepMillis = getMillisTilTaskStart();
try {
Thread.sleep(sleepMillis);
} catch(InterruptedException ex) {
// Handle error
}
executeTask();
}
}
You can extend this class once for every job.

spring Quartz scheduler with cron expression

I want to schedule a job class that checks if a boolean var changed to true or , which is initially not set to any value, using cron expression every night at sometime(say 1'o clock).The scheduler should quit the job if var is set to true or false, otherwise continue running the job at schedule for the max of 15 days & then set it to true automatically. I think IoC container pattern is suitable to do this. Please provide a brief picture of the whole code to implement this.
Spring has built-in scheduling capabilities. While the full implementation is in your court, here is an example of a scheduled method, in this case for 1AM every day:
private Boolean scheduleToggle = null;
#Scheduled(cron = "0 0 01 * * ?")
public void myScheduledJob() {
if(scheduleToggle != null) {
return;
} else {
// run the job
scheduleToggle = true;
return;
}
}
For a full explanation and configuration details, see: http://static.springsource.org/spring/docs/current/spring-framework-reference/html/scheduling.html

Resources