Modern bulut uygulamalarının vazgeçilmez bir bileşeni olan veritabanları, sürekli artan veri hacmi ve kullanıcı talepleri karşısında geleneksel yaklaşımlarla yetersiz kalabilmektedir. Bu noktada, yüksek performanslı ve ölçeklenebilir çözümlere duyulan ihtiyaç, sunucusuz ve NoSQL veritabanlarını ön plana çıkarmaktadır. Amazon DynamoDB, bu ihtiyaçları karşılamak üzere tasarlanmış, tam yönetilen, anahtar-değer ve belge veritabanı hizmeti olarak öne çıkar. Ancak, DynamoDB’nin gerçek gücünü ortaya çıkarmak, sadece temel kullanımla sınırlı kalmayıp, ileri düzey veri modelleme tekniklerini anlamaktan ve uygulamaktan geçer. Bu makale, AWS re:Invent 2025’teki DAT414 oturumunun ruhunu yansıtarak, DynamoDB ile veri modellemenin derinliklerine inecek, tek tablo tasarımından karmaşık erişim desenlerine kadar çeşitli stratejileri gerçek dünya senaryolarıyla açıklayacaktır.
Günümüzün hızla değişen dijital dünyasında, uygulamaların sürekli olarak daha fazla veri işlemesi, daha düşük gecikme süresiyle yanıt vermesi ve trilyonlarca isteği sorunsuz bir şekilde karşılaması beklenmektedir. Geleneksel ilişkisel veritabanları, belirli bir ölçeğe kadar bu beklentileri karşılayabilse de, genellikle yüksek trafikli, düşük gecikmeli ve yüksek ölçeklenebilirlik gerektiren iş yükleri için darboğaz oluşturma potansiyeli taşır. İşte tam da bu noktada, Amazon DynamoDB gibi NoSQL veritabanları sahneye çıkarak, bu zorluklara modern bir çözüm sunar. Tamamen yönetilen yapısı sayesinde altyapı yönetimi yükünü ortadan kaldırır ve geliştiricilerin sadece uygulamalarına odaklanmasını sağlar. Ancak, DynamoDB’nin sunduğu bu müthiş potansiyelin tamamını kullanabilmek için, onu sadece bir veri depolama alanı olarak görmek yeterli değildir; veri erişim desenlerinizi (access patterns) temel alan ileri düzey bir modelleme yaklaşımı benimsemek kritik öneme sahiptir.
Peki, DynamoDB’nin performansı ve ölçeklenebilirliği nereden geliyor ve biz bunu veri modellemeyle nasıl daha da pekiştirebiliriz? Cevap, onun dağıtık mimarisinde, anahtar-değer yapısında ve verilerinizi fiziksel olarak nasıl organize ettiğinde saklı. DynamoDB, verilerinizi partition’lara (bölümlere) ayırarak, her bir bölümü ayrı sunucularda saklar. Bu sayede, gelen isteği doğru bölüme yönlendirerek paralel işleme ve inanılmaz hızlar elde eder. Ancak bu dağıtım, doğru Partition Key (Bölüm Anahtarı) seçimiyle başlar. Yanlış bir anahtar seçimi, tüm veriyi tek bir bölüme yığarak “hot partition” adı verilen darboğazlara yol açabilir. Bu da performansı düşürür, maliyetleri artırır ve ölçeklenebilirlik vaadini boşa çıkarır. Bu nedenle, DynamoDB’de veri modelleme, uygulamanızın veriye nasıl erişeceğini önceden dikkatlice planlamak anlamına gelir. Bu planlama, ilişkisel veritabanlarındaki normalizasyon yaklaşımlarından temelde farklıdır. İlişkisel dünyada verileri tekrar etmemek için normalizasyon yaparken, DynamoDB dünyasında performans için denormalizasyonu ve erişim desenlerine özel tasarımları kucaklarız. Bu derinlemesine anlayış ve doğru uygulama, re:Invent 2025’teki DAT414 oturumunun da temel odağı olan ileri düzey DynamoDB veri modelleme tekniklerinin kapılarını aralayacaktır.
Amazon DynamoDB’nin Temellerini Anlamak: Neden Önemli?
DynamoDB’nin ileri düzey modelleme tekniklerine geçmeden önce, temel kavramlarını sağlam bir şekilde anlamak hayati öneme sahiptir. Bir NoSQL veritabanı olarak DynamoDB, ilişkisel veritabanlarının aksine önceden tanımlanmış şemalara sıkı sıkıya bağlı kalmaz, bu da geliştiricilere esneklik sunar. Ancak bu esneklik, belirli tasarım kısıtlamalarını ve en iyi uygulamaları göz ardı etmeniz gerektiği anlamına gelmez; tam tersine, DynamoDB’nin benzersiz yapısından maksimum fayda sağlamak için bu kısıtlamaları anlamak ve bunlara uygun modelleme yapmak gerekir. DynamoDB’nin kalbinde iki temel kavram yatar: Partition Key ve Sort Key.
Partition Key (Bölüm Anahtarı): Bir DynamoDB tablosundaki her öğe (item), bir Partition Key’e sahip olmak zorundadır. Bu anahtar, verilerin DynamoDB’nin depolama düğümleri arasında fiziksel olarak nasıl dağıtılacağını belirler. İyi seçilmiş bir Partition Key, verileri eşit bir şekilde dağıtarak performans darboğazlarını önler ve uygulamanızın ölçeklenmesini sağlar. Örneğin, bir KullaniciID Partition Key olarak seçildiğinde, her bir kullanıcının verileri ayrı bir bölümde depolanır veya daha fazla veri için genişletilebilir. Aşırı derecede sık kullanılan bir Partition Key (düşük kardinaliteye sahip bir anahtar), tüm isteklerin tek bir bölüme yönelmesine neden olarak “hot partition” sorununa yol açabilir.
Sort Key (Sıralama Anahtarı): İsteğe bağlı olan Sort Key, aynı Partition Key’e sahip öğelerin belirli bir sırada saklanmasını sağlar. Bir Partition Key ile bir Sort Key kombinasyonu, tablodaki her öğeyi benzersiz bir şekilde tanımlar. Bu ikiliye “Composite Primary Key” (Birleşik Birincil Anahtar) denir. Sort Key, belirli bir Partition Key altındaki öğeleri sorgularken aralık sorguları (range queries) yapmanıza olanak tanır. Örneğin, bir KullaniciID ile SiparisTarihi Sort Key olarak belirlendiğinde, bir kullanıcının belirli bir tarih aralığındaki siparişlerini kolayca sorgulayabilirsiniz. Bu özellik, verileri hiyerarşik veya zamansal olarak düzenlemek için inanılmaz derecede güçlüdür.
Bu anahtar yapılarının ötesinde, DynamoDB’nin esnek şeması, her öğenin farklı niteliklere (attributes) sahip olabilmesine izin verir. Ancak bu esneklik, karmaşık sorguları doğrudan birincil anahtar üzerinde yapamayacağınız anlamına gelir. İşte bu noktada Global Secondary Indexes (GSI) ve Local Secondary Indexes (LSI) devreye girer.
* Global Secondary Index (GSI): Veri modellemenin en güçlü araçlarından biridir. Ana tablonun birincil anahtarından farklı bir Partition Key ve isteğe bağlı bir Sort Key’e sahip bir ikincil dizindir. Bu, ana tabloya farklı bir erişim deseniyle sorgu yapmanıza olanak tanır. Örneğin, KullaniciID ve SiparisTarihi birincil anahtarken, siparişleri SiparisDurumu ve SiparisTutari‘na göre sorgulamak isterseniz bir GSI tanımlayabilirsiniz. GSI’lar, ana tablodan tamamen bağımsız bir okuma/yazma kapasitesine sahip olabilir ve veriler üzerinde farklı görünümler sunar.
* Local Secondary Index (LSI): Ana tablonun Partition Key’iyle aynı Partition Key’e sahip olmalı, ancak farklı bir Sort Key’e sahip olmalıdır. LSI’lar, aynı Partition Key altında farklı sıralama ve sorgulama seçenekleri sunar. Ana tablonun kapasitesini kullanır ve daha katı limitlere sahiptir.
DynamoDB’deki veri modelleme, uygulamanızın ihtiyaç duyduğu tüm erişim desenlerini (örneğin, “belirli bir kullanıcının tüm siparişlerini getir”, “son 24 saatteki tüm bekleyen siparişleri bul”, “belirli bir ürünün tüm incelemelerini listele”) göz önünde bulundurarak başlar. İlişkisel veritabanlarında “önce şemayı tasarla, sonra sorgula” yaklaşımı varken, DynamoDB’de “önce erişim desenlerini tanımla, sonra şemayı modelle” yaklaşımı benimsenir. Bu temel anlayış, sonraki bölümlerde ele alacağımız ileri düzey tekniklerin temelini oluşturur ve uygulamanızın hem performans hem de maliyet açısından optimize edilmesini sağlar.
İleri Düzey Veri Modelleme Teknikleri: Tek Tablo Yaklaşımı (Single-Table Design) Neden Daha İyidir ve Nasıl Uygulanır?
Amazon DynamoDB ile veri modellemede en güçlü ve aynı zamanda başlangıçta en kafa karıştırıcı yaklaşımlardan biri, “tek tablo tasarımı” veya “single-table design”dır. Geleneksel ilişkisel veritabanları geçmişinden gelen geliştiriciler için, farklı varlık türlerini (örneğin, kullanıcılar, siparişler, ürünler, incelemeler) ayrı tablolarda tutmak doğal bir yaklaşımdır. Ancak DynamoDB’nin doğası gereği, birçok durumda bu yaklaşım optimum değildir. Tek tablo tasarımı, birden fazla varlık türünü tek bir DynamoDB tablosunda depolayarak performansı artırır, maliyetleri düşürür ve operasyonel karmaşıklığı azaltır.
Neden Tek Tablo Tasarımı?
1. Maliyet Optimizasyonu: DynamoDB’de her tablonun belirli bir operasyonel yükü vardır (örneğin, yedekleme, kurtarma, izleme). Tek bir tablo kullanmak, bu operasyonel yükü ve ilişkili maliyetleri minimumda tutar. Ayrıca, her tablo için ayrı ayrı okuma/yazma kapasitesi sağlamak yerine, tek bir tabloya atanan kapasite tüm varlık türleri arasında paylaştırılabilir, bu da genel kapasite kullanımını optimize eder.
2. Gelişmiş Performans: Özellikle ilgili verilerin sık sık birlikte erişildiği durumlarda, tek tablo tasarımı daha az ağ çağrısı (network round-trip) gerektirdiğinden performansı artırır. Örneğin, bir kullanıcının profilini ve son beş siparişini tek bir Partition Key sorgusuyla getirebilirsiniz. Bu, birden fazla ayrı tabloya yapılan ayrı sorgulardan çok daha hızlıdır.
3. Atomik İşlemler: DynamoDB, tek bir Partition Key altındaki öğeler üzerinde atomik işlemler (transactional writes/updates) yapılmasına olanak tanır. Farklı varlık türlerini aynı Partition Key altında birleştirmek, karmaşık iş mantıklarının atomik olarak işlenmesini kolaylaştırır.
4. Basitleştirilmiş Geliştirme: Daha az tablo, yönetim yükünü azaltır. Uygulama kodunda daha az tablo adı, daha az kapasite yönetimi ve daha az güvenlik politikası anlamına gelir.
Tek Tablo Tasarımı Nasıl Uygulanır?
Tek tablo tasarımının temelinde, DynamoDB’nin güçlü Partition Key ve Sort Key mekanizmasını, birden fazla varlık türünü depolamak ve sorgulamak için “aşırı yüklemek” (overloading) yatar. İşte temel adımlar:
1. Jenerik Anahtar Adları: PK (Partition Key) ve SK (Sort Key) gibi jenerik anahtar adları kullanın. Bu, farklı varlık türlerinin bu anahtarları kendi özel verileriyle doldurmasını sağlar.
2. Varlık Türü Belirleyiciler: Her öğe için bir EntityType veya Type niteliği ekleyin. Bu, aynı PK/SK kombinasyonuna sahip olsa bile, hangi verinin hangi varlığa ait olduğunu anlamanıza yardımcı olur.
3. Karmaşık Sort Key’ler: Sort Key’i, farklı varlık türleri için farklı amaçlarla kullanın. Örneğin, bir USER# PK’si altında, PROFILE# SK’si kullanıcı profili için, ORDER# SK’si siparişler için, REVIEW# SK’si ise ürün incelemeleri için kullanılabilir. Bu, tek bir sorguyla ilgili tüm varlıkları çekmenize olanak tanır.
4. Global Secondary Index (GSI) Kullanımı: Tek tablo tasarımı, ana tablonun birincil erişim desenlerine odaklanırken, GSI’lar farklı sorgulama ihtiyaçları için esneklik sağlar. Örneğin, ana tabloda kullanıcı ve sipariş verilerini USER# PK altında tutarken, tüm siparişleri ORDER_STATUS# PK ile sorgulamak için bir GSI tanımlayabilirsiniz.
Vaka Analizi: E-ticaret Platformu Veri Modeli
Bir e-ticaret platformunda kullanıcılar, siparişler, ürünler ve incelemeler gibi farklı varlıklar bulunur. Geleneksel yaklaşımda her biri ayrı bir tablo olurdu. Tek tablo tasarımı ile nasıl modelleyebiliriz?
| Anahtar | Açıklama |
| :——- | :——– |
| PK | Partition Key – Varlık türüne göre değişir |
| SK | Sort Key – Varlık türüne göre değişir |
| Type | Varlık tipini (USER, ORDER, PRODUCT, REVIEW) belirtir |
| Data | JSON veya diğer ek veriler |
| GSI1PK | Global Secondary Index 1 Partition Key |
| GSI1SK | Global Secondary Index 1 Sort Key |
Örnek Veri Yapısı:
* Kullanıcı (USER) Profili:
* PK: USER#
* SK: PROFILE#
* Type: USER
* Ad: “Ayşe Yılmaz”, Email: “ayse@example.com”
* Sipariş (ORDER):
* PK: USER#
* SK: ORDER#
* Type: ORDER
* SiparisTarihi: “2023-10-27T10:00:00Z”, Durum: “Onaylandı”, ToplamTutar: 150.75
* GSI1PK: ORDER_STATUS#Onaylandı
* GSI1SK: 2023-10-27T10:00:00Z#
* Ürün (PRODUCT) İncelemesi:
* PK: PRODUCT#
* SK: REVIEW#
* Type: REVIEW
* Puan: 5, Yorum: “Harika bir ürün!”, Tarih: “2023-10-26”
* GSI1PK: USER# (Kullanıcının tüm incelemelerini getirmek için)
* GSI1SK: REVIEW#
Bu yapı sayesinde:
* Bir kullanıcının profilini ve tüm siparişlerini tek bir Query işlemiyle (PK = USER#, SK begins_with "PROFILE#" OR SK begins_with "ORDER#") alabilirsiniz.
* Tüm onaylanmış siparişleri GSI1 üzerinden sorgulayabilirsiniz (GSI1PK = ORDER_STATUS#Onaylandı).
const AWS = require('aws-sdk');
const ddb = new AWS.DynamoDB.DocumentClient({ region: 'your-region' });
// Tek tabloya kullanıcı ve sipariş ekleme örneği
async function addUserData() {
const params = {
TableName: 'YourSingleTable',
Item: {
PK: 'USER#123',
SK: 'PROFILE#123',
Type: 'USER',
Name: 'Ahmet Can',
Email: 'ahmet@example.com',
CreatedAt: new Date().toISOString()
}
};
await ddb.put(params).promise();
const orderParams = {
TableName: 'YourSingleTable',
Item: {
PK: 'USER#123',
SK: 'ORDER#ORD-001',
Type: 'ORDER',
OrderId: 'ORD-001',
OrderDate: new Date().toISOString(),
Status: 'Pending',
TotalAmount: 250.00,
GSI1PK: 'ORDER_STATUS#Pending',
GSI1SK: new Date().toISOString() + '#ORD-001' // SK for GSI can be composite
}
};
await ddb.put(orderParams).promise();
console.log("Kullanıcı ve sipariş verileri başarıyla eklendi.");
}
// Bir kullanıcının profilini ve siparişlerini getirme
async function getUserAndOrders(userId) {
const params = {
TableName: 'YourSingleTable',
KeyConditionExpression: 'PK = :pk AND begins_with(SK, :skPrefix)',
ExpressionAttributeValues: {
':pk': USER#${userId},
':skPrefix': 'PROFILE#' // or 'ORDER#' to get only orders, or 'USER#' to get all
}
};
const { Items } = await ddb.query(params).promise();
console.log(Kullanıcı ${userId} için veriler:, Items);
}
// Tüm bekleyen siparişleri GSI üzerinden getirme
async function getPendingOrders() {
const params = {
TableName: 'YourSingleTable',
IndexName: 'GSI1', // Assuming your GSI is named GSI1
KeyConditionExpression: 'GSI1PK = :gsi1pk',
ExpressionAttributeValues: {
':gsi1pk': 'ORDER_STATUS#Pending'
}
};
const { Items } = await ddb.query(params).promise();
console.log("Bekleyen Siparişler (GSI1 üzerinden):", Items);
}
// addUserData();
// getUserAndOrders('123');
// getPendingOrders();
Tek tablo tasarımı, ilk başta öğrenme eğrisine sahip olsa da, DynamoDB'nin tam potansiyelini açığa çıkararak uygulamanız için inanılmaz bir performans ve maliyet avantajı sağlayabilir. Bu yaklaşım, DynamoDB'nin doğasına en uygun modelleme şeklidir ve ileri düzey modellemenin temelini oluşturur.
Karmaşık Erişim Desenlerini Yönetmek: Global Secondary Index'ler (GSI'lar) ile Esnek Sorgulama Nasıl Sağlanır?
DynamoDB'nin tek tablo tasarımının gücünü anlamış olsak da, gerçek dünya uygulamaları genellikle birincil anahtarla doğrudan eşleşmeyen, çok çeşitli ve karmaşık erişim desenlerine sahiptir. İşte bu noktada Global Secondary Index'ler (GSI'lar), veri modellemenizin esnekliğini ve sorgulama yeteneklerini muazzam bir şekilde artıran kritik bir rol oynar. GSI'lar, ana tablonuzdaki veriler üzerinde farklı bir "görünüm" sunarak, birincil anahtarınızdan farklı bir Partition Key ve Sort Key kullanarak sorgulama yapmanızı sağlar. Bu sayede, uygulamanızın farklı modülleri veya kullanıcı arayüzleri, aynı veriye farklı kriterlere göre erişebilir.
GSI'lar Neden Bu Kadar Önemli?
Ana tablonuzdaki Partition Key ve Sort Key kombinasyonu, yalnızca belirli bir erişim deseni (örneğin, UserID ile belirli bir kullanıcının tüm verilerini getirme) için optimize edilmiştir. Ancak, "tüm aktif siparişleri listele", "belirli bir tarihten sonraki tüm ürün incelemelerini bul" veya "en çok satan ürünleri kategorisine göre sırala" gibi sorulara yanıt vermek için farklı anahtarlara ihtiyacınız vardır. GSI'lar, bu farklı sorgu ihtiyaçlarını karşılamak için devreye girer.
* Esnek Sorgulama Yetenekleri: GSI'lar, ana tablonun birincil anahtar kısıtlamalarından bağımsız olarak sorgulama yapmanızı sağlar. Kendi Partition Key ve isteğe bağlı Sort Key'ine sahip oldukları için, verilerinizi farklı bir boyutta organize etmenize imkan tanırlar.
* Paralel Erişim Desenleri: Her GSI, kendi okuma/yazma kapasite birimlerine (RCU/WCU) sahip olabilir. Bu, ana tablodan farklı olarak, yoğun sorgu alan bir GSI'ın ana tablonun performansını etkilememesini sağlar. Örneğin, raporlama için kullanılan bir GSI, canlı uygulama için kullanılan ana tablodan ayrı olarak ölçeklenebilir.
* Seyrek Dizinler (Sparse Indexes): GSI'lar, sadece belirli niteliklere sahip öğelerin dizine dahil edilmesini sağlayarak seyrek dizinler oluşturabilir. Eğer bir öğede GSI'ın Partition Key veya Sort Key niteliği yoksa, bu öğe dizine dahil edilmez. Bu, özellikle büyük tablolarda, yalnızca belirli bir alt kümedeki verileri sorgulamak istediğinizde çok verimlidir. Örneğin, sadece "aktif" durumdaki kullanıcıları listelemek için, Status niteliği "aktif" olan öğeler için bir GSI oluşturabilirsiniz.
GSI ve LSI Arasındaki Fark Nedir?
Karışıklık yaşayanlar için bu ayrımı netleştirmek önemlidir:
* Global Secondary Index (GSI):
* Ana tablonun Partition Key'inden farklı bir Partition Key'e sahip olabilir.
* Tamamen bağımsız okuma/yazma kapasitesine sahiptir.
* Ana tablonun tüm verilerini veya bir alt kümesini içerebilir (projected attributes).
* Veriler ana tabloyla "eventually consistent" (nihai tutarlı) olur, yani anlık güncellemeler gecikmeli yansıyabilir.
* Local Secondary Index (LSI):
* Ana tablonun Partition Key'iyle aynı Partition Key'e sahip olmak zorundadır.
* Farklı bir Sort Key'e sahip olmalıdır.
* Ana tablonun okuma/yazma kapasitesini kullanır.
* Veriler ana tabloyla "strongly consistent" (güçlü tutarlı) olur.
* Tablo oluşturulurken tanımlanmalı ve sonradan eklenemez.
Vaka Analizi: Sosyal Medya Uygulamasında Gönderiler ve Etiketler
Bir sosyal medya uygulamasında kullanıcılar gönderiler paylaşır ve bu gönderilere etiketler ekleyebilir.
Ana tablo yapısı şöyle olabilir:
* PK: USER#
* SK: POST#
* Type: POST
* Content: "Merhaba Dünya!", Tags: ["#AWS", "#DynamoDB"]
Erişim Desenleri:
1. Bir kullanıcının tüm gönderilerini zaman sırasına göre getir. (Ana tablo: PK=USER#, SK begins_with "POST#")
2. Tüm gönderileri etiketine göre getir (örneğin, tüm "#AWS" etiketli gönderiler). (Bu, ana tabloyla doğrudan mümkün değil.)
3. En son gönderilen tüm gönderileri getir (tüm kullanıcılardan). (Bu da ana tabloyla doğrudan mümkün değil.)
Bu karmaşık erişim desenleri için GSI'lara ihtiyacımız var:
GSI 1: Etiketlere Göre Gönderiler
* IndexName: PostsByTag
* GSI1PK: TAG#
* GSI1SK: POST#
* Projected Attributes: ALL (veya ihtiyaç duyulanlar)
Bu GSI ile, belirli bir etikete (TAG#AWS) sahip tüm gönderileri zaman sırasına göre sorgulayabiliriz.
GSI 2: Tüm Gönderiler (En Yeniye Göre)
* IndexName: LatestPosts
* GSI2PK: ALL_POSTS (Sabit bir değer, tüm gönderileri tek bir partition'a toplar)
* GSI2SK: POST# (Ters sıralama için ScanIndexForward: false kullanılabilir)
* Projected Attributes: ALL
Bu GSI ile, tüm gönderileri en yeni olandan en eskiye doğru sorgulayabiliriz. ALL_POSTS gibi sabit bir değerin GSI Partition Key olarak kullanılması, tüm verilerin tek bir partition'a yığılması riskini taşır. Eğer bu GSI'a gelen istek hacmi çok yüksek olacaksa, bu tasarım dikkatli kullanılmalı veya daha fazla partition'a yaymak için farklı stratejiler düşünülmelidir (örneğin, tarih bazlı prefix eklemek).
const AWS = require('aws-sdk');
const ddb = new AWS.DynamoDB.DocumentClient({ region: 'your-region' });
// GSI ile bir etikete göre gönderi sorgulama örneği
async function getPostsByTag(tag) {
const params = {
TableName: 'YourSocialMediaTable',
IndexName: 'PostsByTag', // GSI'ın adı
KeyConditionExpression: 'GSI1PK = :gsi1pk',
ExpressionAttributeValues: {
':gsi1pk': TAG#${tag}
},
ScanIndexForward: false // En yeni gönderileri en önce getirmek için
};
const { Items } = await ddb.query(params).promise();
console.log('${tag}' etiketine sahip gönderiler:, Items);
}
// Tüm en yeni gönderileri getirme örneği
async function getLatestPosts() {
const params = {
TableName: 'YourSocialMediaTable',
IndexName: 'LatestPosts', // Diğer GSI'ın adı
KeyConditionExpression: 'GSI2PK = :gsi2pk',
ExpressionAttributeValues: {
':gsi2pk': 'ALL_POSTS'
},
ScanIndexForward: false, // En yeni gönderileri en önce getirmek için
Limit: 10 // Sadece ilk 10 gönderiyi al
};
const { Items } = await ddb.query(params).promise();
console.log("En yeni 10 gönderi:", Items);
}
// getPostsByTag('AWS');
// getLatestPosts();
GSI'lar, DynamoDB veri modellemesinde neredeyse her zaman kullanılan ve karmaşık erişim desenlerini basit, hızlı ve maliyet etkin bir şekilde çözmenizi sağlayan temel bir araçtır. Doğru GSI tasarımı, uygulamanızın performansını ve ölçeklenebilirliğini doğrudan etkiler. Bu yüzden, veri modelleme sürecinin başından itibaren hangi erişim desenlerinin GSI gerektireceğini dikkatlice analiz etmek önemlidir.
Gelişmiş Veri Yapıları ve Desenler: Denormalizasyon ve Sparse Index'ler Performansı Artıran Sırlar Nelerdir?
DynamoDB ile veri modellemede, geleneksel ilişkisel veritabanı düşünce yapısından uzaklaşmak ve performansı en üst düzeye çıkarmak için bazı ileri düzey desenleri benimsemek gerekir. Bu desenlerin başında denormalizasyon ve Sparse Index'lerin akıllıca kullanımı gelir. Bu yaklaşımlar, özellikle yüksek okuma performansının kritik olduğu senaryolarda uygulamanızın hızını ve maliyet etkinliğini önemli ölçüde artırabilir.
Denormalizasyon: Okuma Performansının Anahtarı
İlişkisel veritabanlarında "Normalizasyon", veri tekrarını en aza indirerek veri bütünlüğünü sağlamak ve depolama alanından tasarruf etmek için önemlidir. Ancak bu, birden fazla tabloyu birleştirmeyi (JOIN) gerektirdiğinden okuma performansını düşürebilir. DynamoDB gibi NoSQL veritabanlarında ise, JOIN işlemleri genellikle yoktur veya çok sınırlıdır. Bu nedenle, DynamoDB'de denormalizasyon, yani ilgili verileri birden fazla yerde kopyalayarak saklamak, temel bir performans optimizasyon tekniğidir.
Neden Denormalizasyon?
* Tek Okuma İşlemi: İhtiyacınız olan tüm verileri tek bir DynamoDB öğesi içinde veya tek bir Partition Key sorgusunda toplayarak, birden fazla ağ çağrısı yapmaktan kaçınırsınız. Bu, gecikmeyi (latency) önemli ölçüde azaltır.
* Basitleştirilmiş Uygulama Mantığı: Uygulama katmanında karmaşık birleştirme (join) veya birden fazla sorgu yapma ihtiyacını ortadan kaldırır.
* Maliyet Azaltma: Daha az sorgu, daha az okuma kapasitesi birimi (RCU) tüketimi anlamına gelir.
Denormalizasyon Ne Zaman Kullanılır?
* Sık Birlikte Erişilen Veriler: Bir öğe her sorgulandığında, onunla ilgili başka bir öğenin de neredeyse her zaman sorgulanması gerekiyorsa. Örneğin, bir Kullanici profili ve onun son 10 Sipariş verisi her zaman birlikte gösteriliyorsa, bunları aynı Partition Key altında depolamak veya kullanıcının profil öğesi içinde son sipariş özetlerini denormalize etmek mantıklıdır.
* Az Güncellenen, Çok Okunan Veriler: Referans veriler (örneğin, ürün kategorileri, döviz kurları) gibi nadiren değişen ancak sık sık okunan veriler için denormalizasyon uygundur.
Denormalizasyonun Dezavantajı: Veri tekrarı, güncellemeleri daha karmaşık hale getirebilir. Bir denormalize edilmiş veri değiştiğinde, kopyalandığı tüm yerlerde güncellenmesi gerekir. Bu genellikle lambda fonksiyonları veya DynamoDB Streams kullanılarak otomatikleştirilir.
Sparse Index'ler: Hedefli Sorgulama İçin Güçlü Bir Araç
Daha önce GSI'ları tartışırken bahsettiğimiz bir alt kavram olan Sparse Index'ler, belirli bir ihtiyaca yönelik olarak oluşturulmuş, oldukça verimli bir GSI türüdür. Bir GSI, ana tablodaki tüm öğeleri veya yalnızca belirli niteliklere sahip öğeleri dizine ekleyebilir. Eğer bir öğede GSI'ın Partition Key veya Sort Key niteliği yoksa, o öğe dizine dahil edilmez. Bu özelliği kullanarak "seyrek" yani "sparse" dizinler oluştururuz.
Neden Sparse Index'ler?
* Hedefli Sorgulama: Yalnızca belirli bir koşulu karşılayan öğeleri sorgulamak istediğinizde, dizini gereksiz verilerle doldurmaktan kaçınır. Bu, dizin boyutunu küçültür ve sorgu performansını artırır.
* Maliyet Optimizasyonu: Daha küçük dizinler, daha az depolama maliyeti ve daha hızlı dizin güncellemeleri anlamına gelir. Ayrıca, sorgularınızın taranması gereken öğe sayısını azalttığı için okuma kapasitesi birimlerinden tasarruf sağlar.
* "Flag" Bazlı Sorgular: Bir öğenin belirli bir duruma (örneğin, "aktive edildi", "yayında", "arşivlendi") sahip olup olmadığını sorgulamak için idealdir.
Sparse Index Nasıl Oluşturulur?
GSI tanımınızda, yalnızca belirli bir niteliğin mevcut olduğu öğeleri dizine dahil edecek şekilde Partition Key veya Sort Key olarak o niteliği kullanırsınız.
Vaka Analizi: Çok Seviyeli Bir Menü Sistemi
Bir SaaS platformunda, kullanıcıların kendilerine özel çok seviyeli menüleri yönettiğini düşünelim. Her menü öğesi (Menu Item), bir üst öğeye (Parent Item) sahip olabilir ve belirli bir Durum'a (Aktif/Pasif) sahip olabilir.
Ana tablo yapısı (tek tablo tasarımı kullanarak):
* PK: TENANT# (Kiracılar arası izolasyon)
* SK: MENUITEM#
* Type: MENU_ITEM
* Title: "Ayarlar", ParentID: "null", Status: "ACTIVE"
* ParentSK: MENUITEM# (Alt öğe için)
Erişim Desenleri:
1. Bir kiracının tüm menü öğelerini getir. (Ana tablo: PK=TENANT#, SK begins_with "MENUITEM#")
2. Bir kiracının yalnızca aktif menü öğelerini getir. (Bu, doğrudan ana tabloyla etkili değil, bir FilterExpression tüm öğeleri tarayacağından verimsiz olur.)
3. Bir kiracının belirli bir üst menü öğesine ait tüm alt menü öğelerini getir.
Sparse Index ve Denormalizasyon Çözümü:
1. Aktif Menü Öğeleri için Sparse GSI:
Sadece Status niteliği "ACTIVE" olan menü öğelerini içeren bir GSI tanımlayalım:
* IndexName: ActiveMenuItems
* GSI1PK: TENANT# (Burada Sparse Index'i oluşturmak için ACTIVE flag'ini ekliyoruz)
* GSI1SK: MENUITEM#
* Projected Attributes: Title, ParentID, Status (veya ihtiyaç duyulanlar)
Bu GSI'a sadece Status: "ACTIVE" olan öğeler eklenecektir. Böylece, bir kiracının yalnızca aktif menü öğelerini getirmek için GSI1PK = TENANT# şeklinde sorgulayabiliriz. Bu, tüm menü öğelerini taramaktan çok daha verimlidir.
2. Alt Menü Öğeleri İçin Denormalizasyon ve Composite Sort Key:
Bir kiracının belirli bir üst öğesine ait alt öğelerini hızlıca getirmek için SK'yi veya bir GSI'ın SK'sini kompozit hale getirebiliriz:
* Mevcut SK yeterli değilse ( MENUITEM# ):
* Bir GSI tanımlayabiliriz:
* IndexName: MenuItemsByParent
* GSI2PK: TENANT#
* GSI2SK: MENUITEM#
Bu GSI ile, GSI2PK = TENANT# şeklinde sorgulayarak bir üst öğenin tüm alt öğelerini getirebiliriz. ParentID niteliği olmayan öğeler bu GSI'a dahil edilmeyeceğinden, bu da bir tür seyrek dizin görevi görür.
const AWS = require('aws-sdk');
const ddb = new AWS.DynamoDB.DocumentClient({ region: 'your-region' });
// Menü öğesi ekleme örneği
async function addMenuItem(tenantId, itemId, title, parentId, status) {
const item = {
PK: TENANT#${tenantId},
SK: MENUITEM#${itemId},
Type: 'MENU_ITEM',
Title: title,
Status: status
};
if (parentId) {
item.ParentID = parentId;
// GSI2PK için gerekli
item.GSI2PK = TENANT#${tenantId}#PARENT#${parentId};
item.GSI2SK = MENUITEM#${itemId};
}
if (status === 'ACTIVE') {
// Sparse GSI için gerekli
item.GSI1PK = TENANT#${tenantId}#ACTIVE;
item.GSI1SK = MENUITEM#${itemId};
}
const params = {
TableName: 'YourMenuTable',
Item: item
};
await ddb.put(params).promise();
console.log(Menü öğesi ${itemId} başarıyla eklendi.);
}
// Aktif menü öğelerini Sparse GSI üzerinden getirme
async function getActiveMenuItems(tenantId) {
const params = {
TableName: 'YourMenuTable',
IndexName: 'ActiveMenuItems', // GSI1'ın adı
KeyConditionExpression: 'GSI1PK = :gsi1pk',
ExpressionAttributeValues: {
':gsi1pk': TENANT#${tenantId}#ACTIVE
}
};
const { Items } = await ddb.query(params).promise();
console.log(Kiracı ${tenantId} için aktif menü öğeleri:, Items);
}
// Bir üst menü öğesinin altındaki öğeleri GSI üzerinden getirme
async function getSubMenuItems(tenantId, parentId) {
const params = {
TableName: 'YourMenuTable',
IndexName: 'MenuItemsByParent', // GSI2'ın adı
KeyConditionExpression: 'GSI2PK = :gsi2pk',
ExpressionAttributeValues: {
':gsi2pk': TENANT#${tenantId}#PARENT#${parentId}
}
};
const { Items } = await ddb.query(params).promise();
console.log(Kiracı ${tenantId} ve üst öğe ${parentId} için alt menü öğeleri:, Items);
}
// addMenuItem('tenant1', 'item1', 'Anasayfa', null, 'ACTIVE');
// addMenuItem('tenant1', 'item2', 'Hakkımızda', 'item1', 'ACTIVE');
// addMenuItem('tenant1', 'item3', 'İletişim', 'item1', 'INACTIVE');
// getActiveMenuItems('tenant1');
// getSubMenuItems('tenant1', 'item1');
Denormalizasyon ve Sparse Index'ler, DynamoDB veri modellemesinin ustalık gerektiren ancak büyük faydalar sağlayan iki önemli desenidir. Bu teknikler, uygulamanızın performans kritik erişim desenlerini ele alırken, aynı zamanda kaynak kullanımını optimize etmenize ve maliyetleri düşürmenize olanak tanır. Her zaman olduğu gibi, bu teknikleri uygularken uygulamanızın spesifik erişim desenlerini ve veri büyümesini dikkatlice analiz etmek, en uygun çözümü bulmak için anahtardır.
Vaka Analizi: Büyük Ölçekli Bir Uygulama İçin DynamoDB Modeli - Bir SaaS Platformunun Veri Modeli Nasıl Tasarlandı?
Şimdiye kadar ele aldığımız tek tablo tasarımı, GSI'lar, denormalizasyon ve Sparse Index'ler gibi ileri düzey teknikleri, gerçek dünya, büyük ölçekli bir SaaS (Software as a Service) platformu bağlamında bir araya getirelim. Hayalimizdeki SaaS platformu, birden fazla kiracıya (tenant) hizmet veriyor ve her kiracının kendi kullanıcıları, projeleri, görevleri ve faturalama kayıtları bulunuyor. Bu platform, yüksek performans, düşük gecikme ve maliyet etkinliği ile milyonlarca kullanıcının aynı anda erişim sağlayabilmesini hedefliyor.
SaaS Platformunun Temel Varlıkları ve Erişim Desenleri:
1. Kiracı (Tenant):
* Erişim Deseni: TenantID ile kiracı bilgilerini (adı, abonelik planı, iletişim) getir.
2. Kullanıcı (User): (Her kiracıya ait)
* Erişim Deseni 1: TenantID ve UserID ile belirli bir kullanıcı profilini getir.
* Erişim Deseni 2: TenantID ile bir kiracıya ait tüm kullanıcıları listele.
* Erişim Deseni 3: Email ile kullanıcıyı bul (globally unique).
3. Proje (Project): (Her kiracıya ait, kullanıcılara atanabilir)
* Erişim Deseni 1: TenantID ve ProjectID ile belirli bir proje detayını getir.
* Erişim Deseni 2: TenantID ile bir kiracıya ait tüm projeleri listele.
* Erişim Deseni 3: TenantID ve UserID ile bir kullanıcıya atanmış tüm projeleri listele.
4. Görev (Task): (Projelere ait, kullanıcılara atanabilir)
* Erişim Deseni 1: TenantID, ProjectID ve TaskID ile belirli bir görevi getir.
* Erişim Deseni 2: TenantID ve ProjectID ile bir projeye ait tüm görevleri listele.
* Erişim Deseni 3: TenantID, UserID ve Status ile bir kullanıcıya atanmış ve belirli bir durumda (örn. "PENDING") olan görevleri listele.
* Erişim Deseni 4: TenantID ile tüm OVERDUE görevleri listele.
5. Fatura (Invoice): (Kiracılara ait)
* Erişim Deseni 1: TenantID ve InvoiceID ile belirli bir faturayı getir.
* Erişim Deseni 2: TenantID ile bir kiracının tüm faturalarını tarih sırasına göre listele.
Tek Tablo Tasarımı Yaklaşımı:
Tüm bu varlıkları tek bir SaaSPlatform tablosunda tutacağız ve PK, SK, Type ile GSI'ları kullanarak tüm erişim desenlerini destekleyeceğiz.
Tablo Yapısı:
| Nitelik | Açıklama |
| :------- | :-------- |
| PK | Partition Key (Tenant, User, Project, Task, Invoice ID'lerini içerir) |
| SK | Sort Key (Varlık türü ve ID'lerini içerir, sıralama sağlar) |
| Type | Varlık tipini (TENANT, USER, PROJECT, TASK, INVOICE) belirtir |
| Data | Varlığa özgü JSON verisi (ad, açıklama, tarih, durum vb.) |
| GSI1PK | Global Secondary Index 1 Partition Key |
| GSI1SK | Global Secondary Index 1 Sort Key |
| GSI2PK | Global Secondary Index 2 Partition Key |
| GSI2SK | Global Secondary Index 2 Sort Key |
| GSI3PK | Global Secondary Index 3 Partition Key |
| GSI3SK | Global Secondary Index 3 Sort Key |
Veri Modelleme Detayları:
1. Kiracı (Tenant):
* PK: TENANT#
* SK: TENANT#
* Type: TENANT
* Data: { "name": "ABC Corp", "plan": "Enterprise" }
* *Erişim:* PK = TENANT#, SK = TENANT#
2. Kullanıcı (User):
* PK: TENANT#
* SK: USER#
* Type: USER
* Data: { "username": "jane.doe", "email": "jane@abc.com" }
* GSI1PK: USER_EMAIL#jane@abc.com (Email ile kullanıcı bulmak için)
* GSI1SK: USER# (Benzersizlik sağlamak için)
* *Erişim 1:* PK = TENANT#, SK = USER#
* *Erişim 2:* PK = TENANT#, SK begins_with "USER#"
* *Erişim 3:* GSI1PK = USER_EMAIL#jane@abc.com
3. Proje (Project):
* PK: TENANT#
* SK: PROJECT#
* Type: PROJECT
* Data: { "name": "Web Redesign", "status": "Active", "ownerId": "user123" }
* GSI2PK: TENANT# (Kullanıcının projelerini listelemek için)
* GSI2SK: PROJECT#
* *Erişim 1:* PK = TENANT#, SK = PROJECT#
* *Erişim 2:* PK = TENANT#, SK begins_with "PROJECT#"
* *Erişim 3:* GSI2PK = TENANT#
4. Görev (Task):
* PK: TENANT# (Proje bazında görevleri gruplamak için)
* SK: TASK#
* Type: TASK
* Data: { "title": "Implement Login", "status": "PENDING", "assignedTo": "user123", "dueDate": "2023-11-15" }
* GSI3PK: TENANT# (Kullanıcının belirli durumdaki görevlerini listelemek için - Sparse Index olarak da kullanılabilir)
* GSI3SK: TASK# (Tarihe göre sıralamak için)
* GSI1PK: TENANT# (Yalnızca süresi geçmiş görevler için Sparse Index) - Eğer dueDate > now ise bu nitelik eklenmez.
* GSI1SK: TASK#
* *Erişim 1:* PK = TENANT#, SK = TASK#
* *Erişim 2:* PK = TENANT#, SK begins_with "TASK#"
* *Erişim 3:* GSI3PK = TENANT#
* *Erişim 4:* GSI1PK = TENANT#
5. Fatura (Invoice):
* PK: TENANT#
* SK: INVOICE# (Tarihe göre sıralanmış faturalar için)
* Type: INVOICE
* Data: { "amount": 99.99, "period": "Nov 2023" }
* *Erişim 1:* PK = TENANT#, SK = INVOICE#
* *Erişim 2:* PK = TENANT#, SK begins_with "INVOICE#" (Tüm faturalar tarih sırasına göre)
Uygulama Mimarisi ve Veri Akışı:
Bu modelde, uygulamanın farklı servisleri (örneğin, kullanıcı yönetimi servisi, proje servisi, faturalama servisi) aynı DynamoDB tablosuna erişir ancak kendi varlık türlerine ve erişim desenlerine odaklanır. Veri değişiklikleri DynamoDB Streams ile yakalanabilir ve diğer servislere veya Lambda fonksiyonlarına (örneğin, OVERDUE görevleri güncellemek için) gönderilebilir, böylece denormalize edilmiş verilerin tutarlılığı sağlanır.
Bu vaka analizi, tek tablo tasarımının ve çeşitli GSI stratejilerinin, karmaşık ve çok kiracılı bir ortamda nasıl bir araya getirilerek esnek, yüksek performanslı ve maliyet etkin bir çözüm sunabileceğini göstermektedir. Anahtar, her bir erişim desenini titizlikle analiz etmek ve DynamoDB'nin benzersiz özelliklerini bu desenleri en iyi şekilde karşılamak için kullanmaktır. Bu ileri düzey tekniklerin doğru kombinasyonu, uygulamanızın gelecekteki büyüme ve değişim ihtiyaçlarına uyum sağlamasına olanak tanır.
DynamoDB ile İleri Düzey Veri Modelleme Yolculuğunun Özeti
Bu makale boyunca, Amazon DynamoDB'nin temel mekanizmalarından başlayarak, ileri düzey veri modelleme tekniklerine kadar uzanan kapsamlı bir yolculuk yaptık. Modern, ölçeklenebilir ve yüksek performanslı uygulamalar geliştirmek için DynamoDB'nin neden kritik bir araç olduğunu ve geleneksel ilişkisel veritabanı yaklaşımlarından neden farklı düşünmemiz gerektiğini anlamaya çalıştık. Özellikle tek tablo tasarımının sağladığı maliyet, performans ve operasyonel kolaylık avantajlarını detaylıca inceledik. Karmaşık erişim desenlerini yönetmek için Global Secondary Index'lerin (GSI'lar) gücünü ve bunların nasıl seyrek dizinler (sparse indexes) olarak kullanılabileceğini gördük. Ayrıca, yüksek okuma performansını sağlamak için denormalizasyonun önemini ve bunun getirebileceği avantajları tartıştık.
Unutulmamalıdır ki, DynamoDB'de başarılı bir veri modellemesi, veritabanının yeteneklerinden çok, uygulamanızın veri erişim desenlerini derinlemesine anlamakla başlar. Her zaman, "Uygulamam bu veriye nasıl erişecek?" sorusunu sormalısınız. Bu, Partition Key ve Sort Key seçimlerinizden, GSI'larınızı nasıl tasarlayacağınıza kadar tüm modelleme kararlarınızı doğrudan etkileyecektir. Esnek şema yapısı, geliştiricilere büyük bir özgürlük sunsa da, bu özgürlüğün akıllıca kullanılması, performans darboğazlarını önlemek ve maliyetleri optimize etmek için kilit rol oynar. Re:Invent 2025'teki DAT414 oturumu gibi etkinlikler, bu derinlemesine bilgiyi edinmek ve sektördeki en iyi uygulamaları öğrenmek için harika fırsatlar sunmaktadır. DynamoDB ile ileri düzey modelleme, bir sanat ve bilim kombinasyonudur; sabır, pratik ve sürekli öğrenme gerektirir, ancak sonuçları uygulamanızın başarısı için paha biçilmez olabilir.
Sıkça Sorulan Sorular
1. DynamoDB ne zaman kullanılmamalıdır?
DynamoDB, karmaşık SQL join'lerine veya OLAP (Online Analytical Processing) tarzı raporlama ihtiyaçlarına yoğun bir şekilde bağımlı uygulamalar için ideal olmayabilir. Ayrıca, ilişkisel veritabanlarının güçlü tutarlılık garantileri, karmaşık işlem yönetimi veya veri bütünlüğü kısıtlamaları (Foreign Keys) sizin için kritikse, DynamoDB ilk tercihiniz olmayabilir. Esas olarak, uygulamanızın veri erişim desenleri önceden tahmin edilemiyorsa veya çok dinamikse, DynamoDB'nin anahtar-değer yapısı zorluk çıkarabilir.
2. Tek tablo tasarımının dezavantajları var mıdır?
Evet, tek tablo tasarımının bazı dezavantajları olabilir. İlk öğrenme eğrisi yüksek olabilir çünkü ilişkisel veritabanı modellemesinden farklı bir düşünce yapısı gerektirir. Model karmaşıklaştıkça, hangi varlığın hangi anahtar kombinasyonuna sahip olduğunu takip etmek zorlaşabilir. Ayrıca, belirli bir varlık türünün güncellenmesi, aynı Partition Key altındaki diğer varlıkları etkileme riski taşır ve veri değişiklikleri genellikle DynamoDB Streams ve Lambda fonksiyonları gibi ek hizmetlerle yönetilmelidir.
3. Kapasite planlaması nasıl yapılmalı?
DynamoDB'de iki ana kapasite modu bulunur: Sağlanan (Provisioned) ve İsteğe Bağlı (On-Demand).
* İsteğe Bağlı (On-Demand): Beklenmedik veya değişken trafik yükleri olan uygulamalar için idealdir. Yalnızca kullandığınız kadar ödeme yaparsınız ve manuel kapasite planlaması gerektirmez. Ancak, çok tahmin edilebilir ve yüksek hacimli iş yüklerinde sağlanan kapasite modundan daha pahalı olabilir.
* Sağlanan (Provisioned): Tahmin edilebilir trafik yükleri olan uygulamalar için uygundur. Belirli bir okuma ve yazma kapasitesi (RCU/WCU) ayırırsınız ve maliyet etkinliği daha yüksek olabilir. Otomatik ölçeklendirme ile yoğun dönemlerde kapasitenizi otomatik olarak artırıp azaltabilirsiniz. Kapasite planlaması, geçmiş trafik verilerini analiz etmeyi ve gelecekteki büyümeyi tahmin etmeyi içerir.
4. LSI ve GSI arasındaki temel fark nedir?
Temel fark, Partition Key'leridir. LSI (Local Secondary Index), ana tablonun Partition Key'iyle aynı Partition Key'e sahip olmalıdır, ancak farklı bir Sort Key'e sahip olabilir. GSI (Global Secondary Index) ise, ana tablonun Partition Key'inden farklı bir Partition Key'e sahip olabilir. Bu, GSI'ların ana tablodan tamamen farklı bir sorgulama boyutuna izin vermesini sağlar ve kendi okuma/yazma kapasitesine sahip olabilirler. LSI'lar ana tablo kapasitesini kullanır ve güçlü tutarlılık sağlarken, GSI'lar nihai tutarlılık sağlar.
5. Veri modellemede yapılan en yaygın hata nedir?
DynamoDB veri modellemesinde yapılan en yaygın hata, ilişkisel veritabanı zihniyetiyle modellemeye çalışmaktır. Bu, ayrı varlıklar için ayrı tablolar oluşturmak, anahtar seçimini göz ardı etmek ve erişim desenlerini önceden tanımlamadan şema oluşturmak anlamına gelir. Sonuç olarak, bu yaklaşım gereksiz JOIN işlemleri, verimsiz sorgular, "hot partition" sorunları ve yüksek maliyetlere yol açar. Doğru yaklaşım, her zaman uygulamanızın veriye nasıl erişeceğini planlamak ve DynamoDB'nin tek tablo tasarımı ve GSI'lar gibi özelliklerini bu desenleri karşılamak için kullanmaktır.