pglock v0.2.1

https://github.com/myyrakle/pglock
์—ฌ๋Ÿฌ ๋…ธ๋“œ์— ๊ฑธ์ณ์„œ ์‚ฌ์šฉํ•  ๋ถ„์‚ฐ ๋ฝ์ด ํ•„์š”ํ–ˆ๋Š”๋ฐ, Redis๋ฅผ ๊ตณ์ด ์ถ”๊ฐ€ํ•ด์„œ ๋ถ™์ด๊ธฐ๋Š” ์‹ซ์–ด์„œ PostgreSQL ๊ธฐ๋ฐ˜์œผ๋กœ ๋ฒ”์šฉ์ ์ธ ๊ตฌํ˜„์ฒด๋ฅผ ํ•˜๋‚˜ ๋งŒ๋“ค์–ด๋ดค๋‹ค.

์‚ฌ์šฉ๋ฒ•์€ ๊ฐ„๋‹จํ•˜๋‹ค.
์ผ๋‹จ ๋ชจ๋“ˆ์„ ์„ค์น˜ํ•œ๋‹ค.

go get github.com/myyrakle/pglock@v0.2.1

๊ทธ๋ฆฌ๊ณ  ์ด๋Ÿฐ ์‹์œผ๋กœ ํด๋ผ์ด์–ธํŠธ ์ดˆ๊ธฐํ™”ํ•ด์„œ ์“ฐ๋ฉด ๋œ๋‹ค.

๊ทธ๋Ÿผ ์ € intiailze ๊ณผ์ •์—์„œ lock ํ…Œ์ด๋ธ”์„ ์ž๋™์œผ๋กœ ๋งŒ๋“ ๋‹ค.

์ƒ์„ฑ๋˜๋Š” ํ…Œ์ด๋ธ”์˜ ์ด๋ฆ„์€ ์˜ต์…˜ LockTableName์„ ํ†ตํ•ด ์ปค์Šคํ…€ํ•  ์ˆ˜ ์žˆ๋‹ค.
๊ธฐ๋ณธ๊ฐ’์€ ๊ทธ๋ƒฅ lock์ด๋‹ค.




XLock

XLock์€ ๋™์‹œ์— ํ•˜๋‚˜๋งŒ ์ ์œ ํ•  ์ˆ˜ ์žˆ๋Š” ๋ถ„์‚ฐ ๋ฝ ๊ตฌํ˜„์ด๋‹ค.

๋™์ž‘ ์›๋ฆฌ ์ž์ฒด๋Š” Mutex์™€ ๋‹ค๋ฅผ ๋ฐ” ์—†๋‹ค.
๋™์‹œ์— ํ•˜๋‚˜์˜ ์‚ฌ์šฉ์ž๋งŒ ๋™์ผํ•œ Lock์„ ์ ์œ ํ•  ์ˆ˜ ์žˆ๊ณ , Lock ์ ์œ ์— ์‹คํŒจํ•  ๊ฒฝ์šฐ ๋‚ด๋ถ€์ ์œผ๋กœ TryXLock ๋ฃจํ”„๋ฅผ ๋Œ๋ฉด์„œ ๋ธ”๋Ÿญํ•œ๋‹ค.

์•„๋ž˜๋Š” ๊ฐ„๋‹จํ•œ ์˜ˆ์ œ๋‹ค. ๊ณ ๋ฃจํ‹ด์„ 5๊ฐœ ๋„์›Œ์„œ Lock์„ ๊ฒฝ์Ÿํ•œ๋‹ค.

package main

import (
	"context"
	"fmt"
	"log"
	"sync"

	"github.com/myyrakle/pglock"
)

func main() {
	lockClient := pglock.NewLockClient(pglock.LockClientOptions{
		DatabaseURL: "postgres://postgres@localhost:5432/postgres?sslmode=disable",
	})
	if err := lockClient.Initialize(); err != nil {
		log.Fatal(err)
	}

	ctx := context.Background()

	wg := sync.WaitGroup{}

	for i := 0; i < 5; i++ {
		wg.Add(1)
		go func(i int) {
			defer wg.Done()
			log.Printf("Goroutine %d: Attempting to acquire lock...", i)
			_, err := lockClient.XLock(ctx, pglock.XLockParams{
				Name:       "test_lock",
				LockID:     fmt.Sprintf("test_lock_%d", i),
				TTLSeconds: 60,
			})
			if err != nil {
				log.Printf("Goroutine %d: Failed to acquire lock: %v", i, err)
				return
			}
			log.Printf("Goroutine %d: Lock acquired!", i)

			// Simulate some work with the lock held
			// time.Sleep(2 * time.Second)

			if _, err := lockClient.Unlock(ctx, pglock.UnlockParams{
				Name:   "test_lock",
				LockID: fmt.Sprintf("test_lock_%d", i),
			}); err != nil {
				log.Printf("Goroutine %d: Failed to release lock: %v", i, err)
				return
			}
			log.Printf("Goroutine %d: Lock released!", i)
		}(i)
	}

	wg.Wait()
	log.Println("All goroutines have finished.")

}

์‹ค์ œ๋กœ๋„ ์‹คํ–‰ํ•ด๋ณด๋ฉด Lock์„ ๊ธฐ๋ฐ˜์œผ๋กœ ์˜ค์™€ ์—ด์„ ๋งž์ถ”๋ฉด์„œ ๋™์ž‘ํ•˜๊ฒŒ ๋œ๋‹ค.

์œ ์˜ํ•  ๋ถ€๋ถ„์€ Unlock๊ณผ TTL์ด๋‹ค.
ํ•ญ์ƒ Unlock๊นŒ์ง€ ์ฑ…์ž„์ ธ์„œ ๋งˆ๋ฌด๋ฆฌํ•ด์ค˜์•ผ ํ•˜๊ณ , ํ˜น์‹œ๋‚˜ Lock์„ ์žก์€์ฑ„๋กœ ์ฃฝ์–ด๋ฒ„๋ฆด ๊ฒฝ์šฐ๋ฅผ ๋Œ€๋น„ํ•ด์„œ TTL ํ”Œ๋ž˜๊ทธ๋„ ์žˆ๋‹ค. TTL์ด ์ง€๋‚˜๋ฉด Lock์ด ๋‚จ์•„์žˆ๋”๋ผ๋„ ํ’€๋ฆฐ ๊ฒƒ์œผ๋กœ ๊ฐ„์ฃผํ•˜๊ณ  ์žฌ์ง„์ž…์ด ๋œ๋‹ค.




SLock

SLock - Shared Lock์€ ์ข€ ๋” ์œ ์—ฐํ•œ ๋ฒ„์ „์˜ ๊ตฌํ˜„์ฒด๋‹ค.
๋™์‹œ์— ์ ‘๊ทผ ๊ฐ€๋Šฅํ•œ ๋ฆฌ์†Œ์Šค๋ฅผ ์›ํ•˜๋Š”๋Œ€๋กœ ์กฐ์ •ํ•  ์ˆ˜ ์žˆ๋‹ค. ์„ธ๋งˆํฌ์–ด์™€ ๋น„์Šทํ•˜๋‹ค.
์ „๋ฐ˜์ ์ธ ์‚ฌ์šฉ๋ฒ•์ด๋‚˜ ์›๋ฆฌ๋Š” XLock๊ณผ ๋‹ค๋ฅด์ง€ ์•Š์œผ๋‚˜, ๋™์‹œ ์ ‘๊ทผ ์ˆ˜๋ฅผ ์ง€์ •ํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ๊ฒƒ์ด ๋‹ค๋ฅด๋‹ค.

-1๋กœ ์„ค์ •ํ•˜๋ฉด ๋ฌด์ œํ•œ์œผ๋กœ ๋“ค์–ด์˜ฌ ์ˆ˜ ์žˆ๊ณ , ์ €๋ ‡๊ฒŒ 2๋ฅผ ๋„ฃ์œผ๋ฉด ์ตœ๋Œ€ 2๊ฐœ์˜ ์‚ฌ์šฉ์ž๊ฐ€ Lock์„ ๋™์‹œ์— ์žก์„ ์ˆ˜ ์žˆ๋‹ค.

๊ทธ๋ž˜์„œ ์ €๋Œ€๋กœ ์‹คํ–‰ํ•˜๋ฉด

์ตœ๋Œ€ 2๊ฐœ๊นŒ์ง€๋งŒ ๋“ค์–ด์™”๋‹ค๊ฐ€ ๋Œ€๊ธฐํ•˜๋Š” ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ๋‹ค.

๊ทธ๋ฆฌ๊ณ  XLock๊ณผ SLock์€ ์ƒํ˜ธ๋ฐฐํƒ€์ ์ด๋‹ค.
SLock์„ ์žก์€ ์ƒํƒœ์—์„œ๋Š” XLock์ด ๋“ค์–ด์˜ฌ ์ˆ˜ ์—†๊ณ , XLock์„ ์žก์€ ์ƒํƒœ์—์„œ๋„ SLock์ด ๋“ค์–ด์˜ฌ ์ˆ˜ ์—†๋‹ค.


๊ธฐ๋Šฅ ์ •๋ฆฌ๋‚˜ ์ตœ์ ํ™” ์ข€ ํ•˜๊ณ , ์šฐ์„ ์ˆœ์œ„ ๋ฝ๋„ ๊ตฌํ˜„ํ•ด๋‘˜๊นŒ ์‹ถ๋‹ค.