5.1 Eşzamanlılık sorunu
Biraz uzak bir teori ile başlayalım.
Programcıların oluşturduğu herhangi bir bilgi sistemi (veya basitçe bir uygulama), her biri gerekli işlevselliğin bir parçasını sağlayan birkaç tipik bloktan oluşur. Örneğin, önbellek, istemci tarafından verilerin daha hızlı okunmasını sağlamak için yoğun kaynak kullanan bir işlemin sonucunu hatırlamak için kullanılır, akış işleme araçları, eşzamansız işleme için diğer bileşenlere mesaj göndermenize izin verir ve toplu işleme araçları " birikmiş veri hacimlerini biraz periyodiklikle komisyonlayın. .
Ve hemen hemen her uygulamada, genellikle iki işlevi yerine getiren veritabanları (DB'ler) bir şekilde yer alır: sizden alındığında verileri depolar ve daha sonra talep üzerine size sağlar. Halihazırda pek çok hazır çözüm bulunduğundan, nadiren kimse kendi veritabanını oluşturmayı düşünür. Ancak uygulamanız için doğru olanı nasıl seçersiniz?
Öyleyse, daha önce kaydedilmiş bir görev listesini evin her yerine yüklemenize - yani veritabanından okumanıza ve onu yeni görevlerle tamamlamanıza ve ayrıca her birine öncelik vermenize olanak tanıyan bir mobil arayüze sahip bir uygulama yazdığınızı hayal edelim. görev - 1'den ( en yüksek) 3'e (en düşük). Diyelim ki mobil uygulamanız aynı anda sadece bir kişi tarafından kullanılıyor. Ama şimdi annene yaratımından bahsetmeye cesaret ettin ve o şimdi ikinci normal kullanıcı oldu. Aynı anda, aynı milisaniyede, bazı görevleri - "pencereleri yıka" - farklı bir öncelik derecesine ayarlamaya karar verirseniz ne olur?
Profesyonel anlamda sizin ve annenizin veri tabanı sorguları, veri tabanına sorgulama yapan 2 işlem olarak düşünülebilir. İşlem, bir bilgisayar programında bir veya daha fazla iş parçacığı üzerinde çalışabilen bir varlıktır. Tipik olarak, bir işlemin bir makine kodu görüntüsü, belleği, bağlamı ve diğer kaynakları vardır. Başka bir deyişle, süreç, program talimatlarının işlemci üzerinde yürütülmesi olarak karakterize edilebilir. Uygulamanız veritabanına bir istek yaptığında, veritabanınızın ağ üzerinden gelen isteği tek bir işlemden işlemesinden bahsediyoruz. Uygulamada aynı anda oturan iki kullanıcı varsa, herhangi bir anda iki işlem olabilir.
Bazı işlemler veritabanına bir istek yaptığında, onu belirli bir durumda bulur. Durum bilgisi olan bir sistem, önceki olayları hatırlayan ve "durum" adı verilen bazı bilgileri depolayan bir sistemdir. Olarak bildirilen bir değişkenin integer
durumu 0, 1, 2 veya 42 olabilir. Mutex (karşılıklı dışlama) iki duruma sahiptir: tıpkı bir ikili semafor gibi ("gerekli" ve "serbest bırakıldı") kilitli veya kilidi açık ve genellikle ikili Yalnızca iki durumu olabilen (ikili) veri türleri ve değişkenler - 1 veya 0.
Durum kavramına dayalı olarak, sonlu bir otomat - bir girdi ve bir çıktıya sahip olan ve her an sonlu bir durum kümesinden birinde olan bir model - ve "durum" gibi çeşitli matematiksel ve mühendislik yapıları temel alınır. ” bir nesnenin dahili duruma bağlı olarak davranışı değiştirdiği tasarım modeli (örneğin, bir veya başka bir değişkene hangi değerin atandığına bağlı olarak).
Dolayısıyla, makine dünyasındaki çoğu nesnenin zaman içinde değişebilen bazı durumları vardır: büyük bir veri paketini işleyen işlem hattımız bir hata atar ve başarısız olur veya kullanıcının cüzdanında kalan para miktarını depolayan Wallet nesne özelliği. hesap, bordro makbuzlarından sonra değişir.
Bir durumdan diğerine geçişe (“geçiş”) - örneğin devam eden durumdan başarısız duruma - işlem denir. CRUD işlemlerini - create
, , veya benzeri HTTP yöntemlerini - , , , muhtemelen herkes bilir read
. Ancak programcılar genellikle kodlarındaki işlemlere başka adlar verirler çünkü işlem, veritabanından belirli bir değeri okumaktan daha karmaşık olabilir - ayrıca verileri ve ardından bir işlev biçimini almış olan işlemimizi de kontrol edebilir. örneğin çağrılacak, Peki bu işlem-işlevleri kim gerçekleştiriyor? süreçler zaten açıklanmıştır.update
delete
POST
GET
PUT
DELETE
validate()
Biraz daha ve terimleri neden bu kadar detaylı anlattığımı anlayacaksınız!
Herhangi bir işlemin - ister bir işlev, ister dağıtılmış sistemlerde başka bir sunucuya istek göndermek olsun - 2 özelliği vardır: başlatma süresi ve tamamlama süresi (tamamlanma süresi) , bu kesinlikle başlatma süresinden (Jepsen'den araştırmacılar) daha büyük olacaktır. Bu zaman damgalarının her ikisine de hayali, tamamen senkronize, küresel olarak kullanılabilir saatlerin verileceği şeklindeki teorik varsayımlardan hareket edin).
Yapılacaklar listesi uygulamamızı hayal edelim. 'daki mobil arayüz üzerinden veri tabanına istekte bulunuyorsunuz 14:00:00.014
ve anneniz 13:59:59.678
(yani 336 milisaniye önce) aynı arayüz üzerinden yapılacaklar listesini güncelleyerek buna bulaşıkları da ekliyor. Ağ gecikmesini ve veritabanınız için olası görev sırasını dikkate alarak, siz ve annenizin yanı sıra annenizin tüm arkadaşları da uygulamanızı kullanıyorsa, veritabanı sizinkini işledikten sonra annenin isteğini yerine getirebilir. Başka bir deyişle, annenizin kız arkadaşlarından gelen isteklerin yanı sıra, isteklerinizden ikisinin aynı anda (aynı anda) aynı verilere gönderilme olasılığı vardır.
Böylece veritabanları ve dağıtılmış uygulamalar alanındaki en önemli terime geldik - eşzamanlılık. İki işlemin eşzamanlılığı tam olarak ne anlama gelebilir? Bazı işlemler T1 ve bazı işlemler T2 verilirse, o zaman:
- T1, T2 yürütmesinin başlangıç zamanından önce başlatılabilir ve T2'nin başlangıç ve bitiş zamanı arasında bitirilebilir.
- T2, T1'in başlangıç zamanından önce başlatılabilir ve T1'in başlangıcı ile bitişi arasında bitirilebilir.
- T1, T1 yürütmesinin başlangıç ve bitiş zamanı arasında başlatılabilir ve bitirilebilir
- ve T1 ve T2'nin bazı ortak yürütme sürelerine sahip olduğu diğer tüm senaryolar
Bu ders çerçevesinde, öncelikle veritabanına giren sorgulardan ve veritabanı yönetim sisteminin bu sorguları nasıl algıladığından bahsettiğimiz açıktır, ancak eşzamanlılık terimi, örneğin işletim sistemleri bağlamında önemlidir. Bu yazının konusundan çok uzaklaşmayacağım ama burada bahsettiğimiz eşzamanlılığın, bağlamda tartışılan eşzamanlılık ve eşzamanlılık ikilemi ve farklılıkları ile ilgili olmadığını belirtmekte fayda olduğunu düşünüyorum. işletim sistemleri ve yüksek performans. Paralellik, birden çok çekirdek, işlemci veya bilgisayar içeren bir ortamda eşzamanlılık elde etmenin bir yoludur. Farklı süreçlerin ortak verilere aynı anda erişmesi anlamında eşzamanlılıktan bahsediyoruz.
Ve aslında, tamamen teorik olarak ne ters gidebilir?
Paylaşılan veriler üzerinde çalışırken, "yarış koşulları" olarak da adlandırılan eşzamanlılıkla ilgili çok sayıda sorun ortaya çıkabilir. İlk sorun, bir işlem almaması gereken verileri aldığında ortaya çıkar: eksik, geçici, iptal edilmiş veya başka türlü "yanlış" veriler. İkinci sorun , işlemin eski verileri, yani veritabanının son kaydedilen durumuna karşılık gelmeyen verileri aldığı zamandır. Diyelim ki bir uygulama, sıfır bakiyeli bir kullanıcının hesabından para çekti, çünkü veritabanı, yalnızca birkaç milisaniye önce gerçekleşen son para çekme işlemini hesaba katmadan, hesap durumunu uygulamaya döndürdü. Durum böyle, değil mi?
5.2 İşlemler Bizi Kurtarmaya Geldi
Bu tür sorunları çözmek için, bir işlem kavramı ortaya çıktı - mantıksal olarak tek bir işlem olan bir veritabanıyla belirli bir sıralı işlemler grubu (durum değişiklikleri). Yine bir banka ile bir örnek vereceğim - ve tesadüfen değil, çünkü bir işlem kavramı, görünüşe göre, tam olarak parayla çalışma bağlamında ortaya çıktı. Klasik bir işlem örneği, bir banka hesabından diğerine para transferidir: önce kaynak hesaptaki tutarı çekmeniz ve ardından hedef hesaba yatırmanız gerekir.
Bu işlemin yapılabilmesi için uygulamanın veritabanında birkaç işlem yapması gerekecektir: göndericinin bakiyesini kontrol etmek, gönderenin hesabındaki tutarı bloke etmek, tutarı alıcının hesabına eklemek ve tutarı göndericiden çekmek. Böyle bir işlem için çeşitli gereksinimler olacaktır. Örneğin, uygulama bakiye hakkında eski veya yanlış bilgi alamaz - örneğin, aynı zamanda paralel bir işlem yarıda bir hatayla sonuçlandıysa ve fonlar hesaptan çekilmediyse - ve uygulamamız zaten bilgi aldı fonların silindiğini söyledi.
Bu sorunu çözmek için, bir işlemin “izolasyon” gibi bir özelliği çağrıldı: İşlemimiz, aynı anda yapılan başka bir işlem yokmuş gibi yürütülür. Veritabanımız, eşzamanlı işlemleri sanki birbiri ardına, sırayla yürütüyormuş gibi gerçekleştirir - aslında, en yüksek yalıtım düzeyi Strict Serializable olarak adlandırılır . Evet, en yüksek, yani birkaç seviye var.
"Dur" diyorsun. Atlarınızı tutun, efendim.
Her işlemin bir çağrı süresi ve yürütme süresi olduğunu nasıl anlattığımı hatırlayalım. Kolaylık sağlamak için aramayı ve yürütmeyi 2 eylem olarak düşünebilirsiniz. Ardından, tüm arama ve yürütme eylemlerinin sıralanmış listesi, veritabanının geçmişi olarak adlandırılabilir. Ardından, işlem yalıtım düzeyi bir dizi geçmiştir. Hangi hikayelerin "iyi" olduğunu belirlemek için izolasyon seviyelerini kullanırız. Bir hikayenin "serileştirilebilirliği bozduğunu" veya "serileştirilemeyeceğini" söylediğimizde, hikayenin serileştirilebilir hikayeler kümesinde olmadığını kastediyoruz.
Ne tür hikayelerden bahsettiğimizi netleştirmek için örnekler vereceğim. Örneğin, böyle bir tür tarih var - ara okuma . A işleminin, başka bir çalışan B işlemi tarafından değiştirilmiş ve henüz taahhüt edilmemiş ("taahhüt edilmemiş") bir satırdan veri okumasına izin verildiğinde gerçekleşir - yani, aslında, değişiklikler henüz nihai olarak taahhüt edilmemiştir. B işlemi ve herhangi bir zamanda bunları iptal edebilir. Ve örneğin, iptal edilen okuma, iptal edilmiş bir para çekme işlemine ilişkin örneğimizdir.
Birkaç olası anormallik vardır. Yani anormallikler, veritabanına rekabetçi erişim sırasında meydana gelebilecek bir tür istenmeyen veri durumudur. Ve belirli istenmeyen durumlardan kaçınmak için, veritabanları farklı düzeylerde yalıtım kullanır - yani, istenmeyen durumlardan farklı düzeylerde veri koruması. Bu seviyeler (4 adet) ANSI SQL-92 standardında listelenmiştir.
Bu seviyelerin tanımı bazı araştırmacılara belirsiz görünüyor ve kendi, daha ayrıntılı sınıflandırmalarını sunuyorlar. MySQL veya PostgreSQL gibi belirli DBMS tarafından tam olarak hangi izolasyon seviyelerinin sunulduğunu netleştirmeyi amaçlayan Hermitage projesinin yanı sıra, daha önce bahsedilen Jepsen'e de dikkat etmenizi tavsiye ederim. Bu depodaki dosyaları açarsanız, veritabanını belirli anormallikler için test etmek için hangi SQL komutlarını kullandıklarını görebilir ve ilgilendiğiniz veritabanları için benzer bir şey yapabilirsiniz). İlginizi çekecek depodan bir örnek:
-- Database: MySQL
-- Setup before test
create table test (id int primary key, value int) engine=innodb;
insert into test (id, value) values (1, 10), (2, 20);
-- Test the "read uncommited" isolation level on the "Intermediate Reads" (G1b) anomaly
set session transaction isolation level read uncommitted; begin; -- T1
set session transaction isolation level read uncommitted; begin; -- T2
update test set value = 101 where id = 1; -- T1
select * from test; -- T2. Shows 1 => 101
update test set value = 11 where id = 1; -- T1
commit; -- T1
select * from test; -- T2. Now shows 1 => 11
commit; -- T2
-- Result: doesn't prevent G1b
Kural olarak, aynı veritabanı için çeşitli yalıtım türlerinden birini seçebileceğinizi anlamak önemlidir. Neden en güçlü yalıtımı seçmiyorsunuz? Çünkü, bilgisayar bilimindeki her şey gibi, seçilen yalıtım düzeyi, yapmaya hazır olduğumuz bir değiş tokuşa karşılık gelmelidir - bu durumda, yürütme hızında bir ödünleşim: yalıtım düzeyi ne kadar güçlüyse, istekler o kadar yavaş olacaktır. işlenmiş. Hangi düzeyde izolasyona ihtiyacınız olduğunu anlamak için, uygulamanızın gereksinimlerini anlamanız ve seçtiğiniz veritabanının bu düzeyi sunup sunmadığını anlamanız gerekir, belgelere bakmanız gerekir - çoğu uygulama için bu yeterli olacaktır, ancak Özellikle sıkı gereksinimleriniz varsa, Hermitage projesindeki adamların yaptığı gibi bir test ayarlamak daha iyidir.
5.3 ASİT içindeki "I" ve diğer harfler
İzolasyon, temel olarak insanların genel olarak ASİT hakkında konuşurken kastettikleri şeydir. Ve bu nedenle, bu kısaltmanın analizine izolasyonla başladım ve bu kavramı açıklamaya çalışanların genellikle yaptığı gibi sırayla gitmedim. Şimdi kalan üç harfe bakalım.
Banka havalesi örneğimizi tekrar hatırlayın. Bir hesaptan diğerine para aktarma işlemi, birinci hesaptan bir para çekme işlemini ve ikinci hesapta bir yenileme işlemini içerir. İkinci hesabın yenileme işlemi başarısız olursa, muhtemelen ilk hesaptan para çekme işleminin gerçekleşmesini istemezsiniz. Yani işlem ya tamamen başarılı olur ya da hiç gerçekleşmez sadece bir kısmı için yapılamaz. Bu özelliğe "atomiklik" denir ve ASİT'te "A"dır.
İşlemimiz yürütüldüğünde, herhangi bir işlem gibi, veritabanını geçerli bir durumdan diğerine aktarır. Bazı veritabanları sözde kısıtlamalar sunar - yani, örneğin birincil veya ikincil anahtarlar, dizinler, varsayılan değerler, sütun türleri vb. ile ilgili olarak depolanan veriler için geçerli olan kurallar. Bu nedenle, bir işlem yaparken tüm bu kısıtlamaların yerine getirileceğinden emin olmalıyız .
Bu garantiye "tutarlılık" denir ve C
ACID'de bir harf (daha sonra bahsedeceğimiz dağıtılmış uygulamalar dünyasından tutarlılık ile karıştırılmamalıdır). ASİT anlamında tutarlılık için net bir örnek vereceğim: bir çevrimiçi mağaza için bir uygulama orders
tabloya bir satır eklemek istiyor ve tablodaki kimlikproduct_id
sütunda belirtilecek - tipik .products
foreign key
Ürün, örneğin ürün yelpazesinden ve buna bağlı olarak veritabanından çıkarılmışsa, satır ekleme işlemi yapılmamalıdır ve bir hata alırız. Bu garanti, diğerlerine kıyasla, bence biraz abartılı - çünkü yalnızca veritabanındaki kısıtlamaların aktif kullanımı , verilerin sorumluluğunun kaydırılması anlamına geliyorsa (ayrıca, eğer bahsediyorsak iş mantığının kısmen değiştirilmesinin yanı sıra) CHECK gibi bir kısıtlama) uygulamadan veritabanına, ki şimdi dedikleri gibi, tam da öyle.
Ve son olarak, kalır D
- "direnç" (dayanıklılık). Bir sistem arızası veya başka herhangi bir arıza, işlem sonuçlarının veya veritabanı içeriğinin kaybına yol açmamalıdır. Yani, veritabanı işlemin başarılı olduğunu yanıtladıysa, bu, verilerin kalıcı belleğe - örneğin bir sabit diske - kaydedildiği anlamına gelir. Bu arada, bu, verileri bir sonraki okuma isteğinde hemen göreceğiniz anlamına gelmez.
Daha geçen gün, AWS'den (Amazon Web Services) DynamoDB ile çalışıyordum ve kaydetmek için bazı veriler gönderdim ve bir yanıt HTTP 200
(Tamam) veya buna benzer bir şey aldıktan sonra kontrol etmeye karar verdim - ve bunu görmedim sonraki 10 Saniye boyunca veritabanındaki veriler. Yani, DynamoDB verilerimi işledi, ancak verilerin en son kopyasını almak için tüm düğümler anında senkronize edilmedi (ancak önbellekte olabilir). Burada yine dağıtılmış sistemler bağlamında tutarlılık alanına tırmandık, ancak bunun hakkında konuşma zamanı hala gelmedi.
Artık ACID garantilerinin ne olduğunu biliyoruz. Ve neden yararlı olduklarını bile biliyoruz. Ancak bunlara gerçekten her uygulamada ihtiyacımız var mı? Ve değilse, tam olarak ne zaman? Tüm DB'ler bu garantileri sunuyor mu, yoksa bunun yerine ne sunuyorlar?
GO TO FULL VERSION