μμ ν΄μ(Consistent Hashing)
by JiwonDev
π ν΄μλ₯Ό μ¬μ©ν΄ μμ²μ λΆμ°νλ λ°©λ²
Nκ°μ μΊμ μλ²κ° μμ λ λΆνλ₯Ό λλλ 보νΈμ μΈ λ°©λ²μΌλ‘λ ν΄μ ν¨μλ₯Ό λ§μ΄ μ¬μ©νλ€.
MD5 (2^128): μλκ° λ³΄μλ³΄λ€ μ€μν μ΄ν리μΌμ΄μ . SHA-256 μ λΉν΄ ν΄μ 곡κ°μ μλ€.
SHA-256 (2^256): λ κΈ΄ ν΄μ ν¬κΈ°μ λ κ°λ ₯ν μνΈν μμ±. μλλ MD5 μ λΉν΄ λ리λ€. λ§€μ° ν° ν΄μ 곡κ°μ κ°κΈ° λλ¬Έμ μΆ©λμ΄ κ±°μ λ°μνμ§ μλλ€.
serverIndex = hash(key) % N (μλ²κ°μ)
μμ κ°μ λ°©λ²μ λͺ¨λ μν©μμ μ λμν κ² κ°μ§λ§ μλ² κ°μ (server pool)μ΄ λ³κ²½λκ±°λ λ°μ΄ν°κ° νμͺ½μ λͺ°λ¦¬κ²λμμ λ λ¬Έμ κ° λ°μνλ€. ν€λ₯Ό μ μ νκ² μ¬λ°°μΉ νμ§ μλλ€λ©΄ λΆνκ° λͺ°λ¦¬κ² λκ³ λκ·λͺ¨μ μΊμ λ―Έμ€(cache miss)κ° λ°μν μλ μλ€.
μ΄λ₯Ό λ°©μ§νλ €λ©΄ μλ²κ° μΆκ°/μμ λλλΌλ key 맀ν λ³κ²½μ μ΅μν λμ΄μΌ νλ€. μ΄λ μ¬μ©νλ κ²μ΄ λ°λ‘ "μμ ν΄μ" μ΄λ€.
μμ ν΄μ(Consistent Hashing)
MITμμ μ²μ μ μ λμμΌλ©° ν΄μ ν μ΄λΈμ ν¬κΈ°κ° λ³κ²½λ λ, νκ· μ μΌλ‘ μ€μ§ k(ν€μκ°μ) / n(slotμ κ°μ) κ°μ ν€λ§ μ¬λ°°μΉνλ κΈ°μ μ΄λ€. μμ ν΄μλ‘ κ΅¬ννμ§ μλ ν΄μν¨μλ€μ λ°μ΄ν°λ₯Ό λ£λ μ¬λ‘―μ μκ° μ€μ΄κ±°λ λμ΄λλ©΄ λ§μ ν€κ° μ¬λ°°μΉ λμΌ νλ€.
π μμ ν΄μλ μ΄λ»κ² ꡬνν κΉ
μλ₯Ό λ€μ΄ ν΄μ ν¨μλ‘ SHA-1μ μ¬μ©νλ€κ³ κ°μ νλ©΄, ν΄μ 곡κ°μ 0 <= x < 2^160 κΉμ§ μ¬μ©ν μ μλ€. μ΄λ₯Ό μμκ³Ό λμ λΆμ΄κ²λλ©΄ μν 리μ€νΈλ‘ μ¬μ©ν μ μλ€.
class ConsistentHash<T>(
private val hashFunction: (String) -> Long, // μ¬μ©ν ν΄μ ν¨μ
private val circle: SortedMap<Long, T> = TreeMap() // ν΄μ κ°μ ν€λ‘ νκ³ λ
Έλλ₯Ό κ°μΌλ‘ νλ μ λ ¬λ 맡
){
/* ... */
}
// ν΄μ ν¨μμ μμ ꡬνμ
λλ€. Javaμ MessageDigestλ₯Ό μ¬μ©ν΄ λ¬Έμμ΄μ SHA-1 ν΄μ κ°μ μμ±ν©λλ€.
fun hashFunction(key: String): Long {
val md = MessageDigest.getInstance("SHA-1")
return md.digest(key.toByteArray())
.fold(0L, { acc, byte -> (acc shl 8) + (byte and 0xff) })
}
fun main() {
val consistentHash = ConsistentHash(::hashFunction)
}
μ΄λ κ² κ΅¬μ±ν μν 리μ€νΈμμ μλ²(IPλ μλ² μ΄λ¦)μ μ μ₯νκ³ μ¬μ©ν hash key λ€μ μνλ μ΄λ μ§μ μλ λ°°μΉν μ μκ² κ΅¬μ±νλ€.
- μ΄ λ κΈ°μ‘΄μ ν΄μκ° μλ κ· λ± λΆν¬ (uniform distribution) ν΄μ ν¨μλ₯Ό μ¬μ©ν΄μ μνμ κ³¨κ³ λ£¨ λΆν¬μν¨λ€.
μ΄λ° μμΌλ‘ μνμΌλ‘ ꡬμ±νκ²λλ©΄ μΆκ°/μμ μμ κΈ°μ‘΄μ ν€ λ§€ν λ³κ²½μ΄ μ΅μν λλλ‘ κ΅¬νν μ μλ€.
- μλ² μ‘°ν
ν΄λΉ hash keyλ‘λΆν° μκ³λ°©ν₯μΌλ‘ μλ²λ₯Ό νμνμ¬ λ§λ 첫λ²μ§Έ μλ²λ₯Ό μ‘°ννλ€. - μλ² μΆκ°
μλ²λ₯Ό μΆκ°νλλΌλ μκ³λ°©ν₯μΌλ‘ hash keyλ₯Ό μ¬μ©νλ κΈ°μ‘΄ 1κ°μ μλ²λ§ ν€κ° λ³κ²½λλ€. λ€λ₯Έ ν€λ μ¬λ°°μΉ λμ§ μλλ€. - μλ² μμ
μλ² μμ λ λ§μ°¬κ°μ§λ‘ κΈ°μ‘΄ μ‘°ν κ·μΉμ λ°λΌ κ°μ΄λ° μΌλΆλ§ ν€λ§ μ¬λ°°μΉ λκ² λλ€.
class ConsistentHash<T>(
private val hashFunction: (String) -> Long, // ν΄μ ν¨μ
private val circle: SortedMap<Long, T> = TreeMap() // ν΄μ κ°μ ν€λ‘ νκ³ λ
Έλλ₯Ό κ°μΌλ‘ νλ μ λ ¬λ 맡
) {
fun addNode(node: T) {
// λ
Έλλ₯Ό ν΄μλ§μ μΆκ°ν©λλ€.
val hash = hashFunction(node.toString())
circle[hash] = node
}
fun removeNode(node: T) {
// λ
Έλλ₯Ό ν΄μλ§μμ μ κ±°ν©λλ€.
val hash = hashFunction(node.toString())
circle.remove(hash)
}
fun getNode(key: String): T? {
// μ£Όμ΄μ§ ν€μ λν λ
Έλλ₯Ό λ°νν©λλ€.
if (circle.isEmpty()) return null
val hash = hashFunction(key)
// μ£Όμ΄μ§ ν΄μ κ°μ μ‘°ννμ§ λͺ»ν κ²½μ°, κ°μ₯ κ°κΉμ΄ λ€μ ν€λ₯Ό μ°Ύμ΅λλ€.
if (!circle.containsKey(hash)) {
// key λ³΄λ€ μμκ° κ°κ±°λ ν° μλΈ λ§΅ μμ±(tailMap)
val tailMap = circle.tailMap(hash)
// λ€μ μμκ° μλ€λ©΄ ν λ°ν΄ λμ 첫λ²μ§Έ μμ(circle.firstKey()) μ¬μ©
hash = tailMap.firstKey() ?: circle.firstKey()
}
return circle[hash]
}
}
π μ΄λ κ² κ΅¬νν μμ ν΄μλ λ¨μ μ΄ μμκΉ?
- μ κ·Έλ¦Όμ²λΌ κ° μλ² μ¬μ΄μ λΉ κ³΅κ°(partition)μ νμ κ· λ±νκ² μ μ§νλκ² λΆκ°λ₯νλ€.
- κ· λ± λΆν¬ ν΄μν¨μλ₯Ό μ¬μ©νλλΌλ ν€ λΆν¬ μμ²΄κ° νμͺ½ λΉ κ³΅κ°(partition)μ λͺ°λ¦΄ μλ μλ€.
π ν΄κ²°μ± : κ°μ λ Έλ(virtual node) μ¬μ©
μ€μ μλ²(λ Έλ)λ₯Ό 1:1λ‘ μ¬μ©νμ§ μκ³ , κ°μμ λ Έλλ₯Ό λ§λ€μ΄μ μ¬μ©νλ€. μλ² 1λκ° μ¬λ¬κ°μ κ°μ λ Έλλ₯Ό κ°μ§ μ μλ€.
- κ°μ λ Έλμ κ°μλ₯Ό λλ¦ΌμΌλ‘μ ν€μ λΆν¬λ₯Ό κ· λ±νκ² λ§λ€ μ μλ€.
- λ Έλμ κ°μκ° λ§μ μλ‘ νμ€νΈμ°¨κ° μ μ΄μ Έ λ κ· λ±ν΄μ§κ² μ§λ§ κ·Έλ§νΌ λ©λͺ¨λ¦¬κ° νμνκ³ μΆκ°/μμ μ κ°μλ Έλλ₯Ό λ§μ΄ μ 리ν΄μΌνλ€. νΈλ μ΄λ μ€νλ₯Ό κ³ λ €ν΄μΌνλ€.
- μ°Έκ³ λ‘ κ°μ λ Έλμ μλ 무νμ λ릴 μ μλ€. νμμ λ°λΌ μ 체 λ Έλ μλ μ€μ΄κ³ μ±λ₯μ΄ μ’μ μλ²κ° λ§μ λ Έλ μλ₯Ό κ°μ Έκ°κ²λ ꡬνν μλ μλ€.
// κ°μ λ
Έλλ₯Ό λνλ΄λ ν΄λμ€
class VirtualNode<T>(
private val physicalNode: T,
private val replicaIndex: Int
) {
fun getPhysicalNode(): T = physicalNode // 물리 λ
Έλ (μ€μ μλ²)
fun getKey(): String = "$physicalNode#$replicaIndex" // κ°μ λ
Έλμ κ³ μ ν€(ν΄μμ μ¬μ©)
fun isVirtualOf(node: T): Boolean = this.physicalNode == node
override fun toString(): String = getKey()
}
class ConsistentHash<T>(
private val hashFunction: (String) -> Long,
private val numberOfReplicas: Int,
private val circle: SortedMap<Long, VirtualNode<T>> = TreeMap()
) {
fun addNode(node: T) {
for (i in 0 until numberOfReplicas) {
val virtualNode = VirtualNode(node, i)
circle[hashFunction(virtualNode.getKey())] = virtualNode
}
}
fun removeNode(node: T) {
circle.entries.removeIf { it.value.isVirtualOf(node) }
}
fun getNode(key: String): T? {
if (circle.isEmpty()) return null
var hash = hashFunction(key)
if (!circle.containsKey(hash)) {
val tailMap = circle.tailMap(hash)
hash = tailMap.firstKey() ?: circle.firstKey()
}
return circle[hash]?.getPhysicalNode()
}
}
μ΄λ κ² κ΅¬μ±ν¨μΌλ‘μ μλμ κ°μ μ΄μ μ μ±κΈΈ μ μλ€.
- μλ² μΆκ°/μμ μλ ν€ λ§€ν λ³κ²½μ΄ μ΅μνλλ€. cache miss λΉμ¨μ μ€μΌ μ μλ€.
- μλ² μΆκ°/μμ λ₯Ό κ΅μ₯ν λ§μ΄ νλλΌλ νμͺ½μΌλ‘ λͺ°λ¦¬μ§ μκ³ λ°μ΄ν°κ° κ· λ±νκ² λΆν¬λλ€.
- μ μ₯μ μΌλ‘ μνμ κ·λͺ¨ νμ₯μ΄ μ¬μμ§κ³ hot spot key (νμͺ½μΌλ‘ ν€κ° λͺ°λ¦¬λ νμ)μ μ΅μν ν μ μλ€.
π μμΉμ μΌλ‘ μμ ν΄μλ μΌλ§λ μ’μμ§λκ±ΈκΉ?
λΌμ°ν
μ λ§λ λ€κ³ μκ°ν΄λ³΄μ. μ¬κΈ°μμ key λ request url, ip λ±μ΄ λ κ²μ΄κ³ ν€μ λ°λΌ μ μ ν μλ²λ₯Ό μ°Ύμμ£Όμ΄μΌνλ€.
- μ΄ λ key(μμ²)μ λ°λΌ μ΄λλ‘ λΌμ°ν λμλμ§ μΊμ±ν΄λλ€. μ΄ν λμΌ μμ²μ λΌμ°ν κ²°μ κ³Όμ μ μλ΅νκ³ λ°λ‘ μ‘°ννλ€.
- μΊμκ° μκ±°λ ν΄λΉ cache λ₯Ό μ¬μ©νλλ° μ λ³΄κ° μ ν¨νμ§ μμ κ²½μ° (λ Έλκ° λ³κ²½λ κ²½μ°) λ€μ λΌμ°ν κ²°μ κ³Όμ μ κ±°μΉλ€.
# κ·Έλ₯ ν΄μλ₯Ό μ¬μ©ν΄μ ꡬννμ λ
# μμ ν΄μλ₯Ό μ¬μ©νμ λ
# μμ ν΄μλ₯Ό μ¬μ©νκ³ , κ°μ λ Έλ μλ₯Ό λλ € νμ€νΈμ°¨λ₯Ό μ€μμ λ
- κ°μ λ Έλ μλ₯Ό λ리면 νμ€ν λ κ³¨κ³ λ£¨ λΆμ°λλ€. λ€λ§ κ°μ λ Έλ κ°μκ° μ΄λμ λ μ°¨κ²λλ©΄ μ΄ν μμλ§κ°λ₯Ό λλ €λ κ²°κ³Όλ ν° λ³νκ° μλ€λκ±Έ μΈμ§νμ. μ΄κ²λ νΈλ μ΄λ μ€νλ₯Ό κ³ λ €ν΄μΌνλ€.
π μ΄λ¬ν κΈ°μ λ€μ μ€μ λ‘ μ΄λμ λ§μ΄ μ°μΌκΉ?
- μλ§μ‘΄ λ€μ΄λλͺ¨ λ°μ΄ν°λ² μ΄μ€(DynamoDB)μ νν°μ
λ κ΄λ ¨ μ»΄ν¬λνΈ
Dynamoλ 리λκ° μλ 볡μ μμ€ν μΌλ‘ λͺ¨λ λ Έλμμ μ°κΈ° μμ²μ²λ¦¬κ° κ°λ₯νλ€. λ°μ΄ν°λ₯Ό μ¬λ¬ νν°μ μ λΆλ°°ν΄μ μ¬μ©ν λ μμ ν΄μλ₯Ό μ΄μ©ν΄ λ°μ΄ν° 리밸λ°μ±μ μ΅μννλ€. (λ€μ΄λλͺ¨κ° μ΄λ»κ² ꡬμ±λμλμ§ κΆκΈνλ€λ©΄ > μ’μκΈ1 / μ’μκΈ2 ) - μνμΉ μΉ΄μ°λλΌ(Apache Cassandra) ν΄λ¬μ€ν°μμμ λ°μ΄ν° νν°μ
λ
λ°μ΄ν°λ₯Ό ν΄λ¬μ€ν° λ΄μ μ¬λ¬ λ Έλμ λΆμ°μν€λλ° νν°μ λ ν€λ₯Ό ν΄μ±ν λ μμ ν΄μλ₯Ό μ¬μ©νλ€. - λμ€μ½λ(Discord)μ±ν
μ΄ν리μΌμ΄μ
- Elixirλ₯Ό λμ μ¬μ©μ μ€λ°±λ§λͺ
μΌλ‘ νμ₯ν λ°©λ²
μλ‘μ΄ μ μ κ° λ€μ΄μμ λ, μ μΈκ³μ κ±°μΉ μλ² ν΄λ¬μ€ν° λ΄μμ μ±λ, μ¬μ©μ κ·Έλ£Ήμ νΉμ λ Έλμ ν λΉνμ¬ μ¬μ©νλ€. μ΄ λ μμ ν΄μλ₯Ό μ¬μ©νμ¬ λ Έλμ μΆκ°/μμ μ λ°λ₯Έ 리밸λ°μ±μ μ΅μννλ€. - μμΉ΄λ§μ΄(Akamai) CDN
μ¬μ©μμ μμ²μ μ μΈκ³μ λΆμ°λ μΊμ(컨ν μΈ ) μλ²μ ν¨μ¨μ μΌλ‘ λΌμ°ν νλ€. μλ²κ° μΆκ°/μμ λλλΌλ μμ² λ¦¬λ°Έλ°μ±μ΄ μ΅μν λλλ‘ μμ ν΄μλ₯Ό μ¬μ©νλ€. - 맀그λν(Meglev) λ€νΈμν¬ λ‘λ λ°Έλ°μ
ꡬκΈμ΄ λ§λ λ€νΈμν¬ λ‘λλ°Έλ°μλ‘ νΈλν½μ κ· λ±νκ² λΆμ°μν¨λ€.
μ΄λ κ² λκ·λͺ¨ μΌμ΄μ€κ° μλλλΌλ λΉμ₯ μ¬μ©μμκ° λ§μμ Έ DB μ€μΌμΌμ μ κ³ λ €ν΄μΌνλ€λ©΄ νν°μ λμν μ§, μ€λ©μ ν μ§ κ³ λ―Όνκ³ νλ€λ©΄ μ΄λ€ keyλ₯Ό κΈ°μ€μΌλ‘ λλ μΌν μ§λ₯Ό κ³ λ―Όν΄λ³΄μμΌνλ€. MiRO μμ§λμ΄λ§ - λ°μ΄ν° μ€λ© λ¬Έμ λ₯Ό ν΄κ²°νκΈ° μν μ μ ν ν΄μν¨μ μ ν
'πκΈ°λ³Έ μ§μ > CS κΈ°λ³Έμ§μ' μΉ΄ν κ³ λ¦¬μ λ€λ₯Έ κΈ
λκ·λͺ¨ λΆμ° νκ²½ key-value μ μ₯μ μ€κ³ (1) | 2024.02.22 |
---|---|
DB μΈλ±μ€μμλ μ HashTableμ μ¬μ©νμ§ μμκΉ? (0) | 2024.01.10 |
λ€νΈμν¬ L4, L7 λ‘λλ°Έλ°μ± (Load balancing) (0) | 2021.09.08 |
μ μμΌκ³Ό μ΅μ€νΈλ¦Ό νλ‘κ·Έλλ°(Agile , XP) (0) | 2021.08.25 |
νΈλμμ 격리μμ€(DB Isolation Level) (0) | 2021.08.10 |
λΈλ‘κ·Έμ μ 보
JiwonDev
JiwonDev