برنامه نویسی

نوشتن مسیر سریع YugabyteDB – انجمن DEV

در مستندات YugabyteDB درباره تراکنش ها، دو مسیر تراکنش شرح داده شده است:

  • تراکنش‌های تک ردیفی، که «مسیر سریع» نیز نامیده می‌شوند، که در آن ردیف‌ها می‌توانند مستقیماً در LSM-Tree نهایی (یک RocksDB پیشرفته) برای رایانه لوحی نوشته شوند.

  • تراکنش‌های توزیع‌شده که در آن تراکنش SQL کارهای بیشتری انجام می‌دهد و برخی اطلاعات باقی می‌مانند، فقط در COMMIT (وضعیت و زمان تعهد) شناخته می‌شوند. YugabyteDB تمام رکوردهای موقت را در IntentsDB، LSM-Tree دیگر برای تبلت می نویسد و وضعیت تراکنش را در جدول تراکنش ذخیره می کند تا سایر تراکنش ها بتوانند بدانند کدام تغییرات برای دیگران قابل مشاهده است.

در این پست وبلاگ، من نشان خواهم داد که چگونه می توان دقیقاً چه چیزی، کجا و چه زمانی نوشته شده است.

ردیابی عملیات نوشتن در IntentsDB و RegularDB در آزمایشگاه

من یک کانتینر YugabyteDB را با ردیابی همه نوشته ها مانند پست قبلی شروع می کنم و وارد پوسته YSQL می شوم:

docker exec -it $(
docker run -d yugabytedb/yugabyte:latest sleep infinity
) bash

yugabyted start --advertise_address=0.0.0.0 \
--tserver_flags="TEST_docdb_log_write_batches=true,tserver_enable_metrics_snapshotter=false"

until postgres/bin/pg_isready ; do sleep 1 ; done | uniq

tail -f /root/var/logs/tserver/yb-tserver.INFO | grep -E '^ | tablet.cc:' &

ysqlsh

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

همه نوشته‌های مربوط به تبلت‌های YugabyteDB به سیستم وارد می‌شوند /root/var/logs/tserver/yb-tserver.INFO و من snapshotter متریک را غیرفعال کردم زیرا در تبلت ها نیز می نویسد و می خواهم فقط نوشته های خودم را ببینم. من لاگ را به کنسول خود منتقل می کنم تا در حین آزمایش برخی از DML، نوشته ها را ببینم.

من یک جدول آزمایشی ایجاد می کنم. گزارش های پیش فرض ایجاد تبلت را نشان می دهد:

yugabyte=# create table demo(id bigint primary key, value text);
CREATE TABLE

I0611 19:41:25.209566  1512 tablet.cc:469] T 3ecc7bc3dcc049d0b7e9976c7971adde P db20a8214add45e69bd4f1e168772389: Schema version for demo is 0
I0611 19:41:25.209762  1512 tablet.cc:600] T 3ecc7bc3dcc049d0b7e9976c7971adde P db20a8214add45e69bd4f1e168772389: Creating RocksDB database in dir /root/var/data/yb-data/tserver/data/rocksdb/table-000033f1000030008000000000004003/tablet-3ecc7bc3dcc049d0b7e9976c7971adde
I0611 19:41:25.236333  1512 tablet.cc:797] Opening RocksDB at: /root/var/data/yb-data/tserver/data/rocksdb/table-000033f1000030008000000000004003/tablet-3ecc7bc3dcc049d0b7e9976c7971adde
I0611 19:41:25.286031  1512 tablet.cc:812] T 3ecc7bc3dcc049d0b7e9976c7971adde P db20a8214add45e69bd4f1e168772389: Opening intents DB at: /root/var/data/yb-data/tserver/data/rocksdb/table-000033f1000030008000000000004003/tablet-3ecc7bc3dcc049d0b7e9976c7971adde.intents
I0611 19:41:25.339751  1512 tablet.cc:863] T 3ecc7bc3dcc049d0b7e9976c7971adde P db20a8214add45e69bd4f1e168772389: Successfully opened a RocksDB database at /root/var/data/yb-data/tserver/data/rocksdb/table-000033f1000030008000000000004003/tablet-3ecc7bc3dcc049d0b7e9976c7971adde, obj: 0x55c164a66000

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

T 3ecc7bc3dcc049d0b7e9976c7971adde شناسه تبلت است و P db20a8214add45e69bd4f1e168772389 همتای تبلت است. در این خوشه تک گره، جدول فقط با یک تبلت و یک همتا، رهبر شروع می شود.

یک درج ساده: مسیر سریع

من یک ردیف را در این جدول ساده وارد می کنم:

yugabyte=# insert into demo values(42,'answer');
INSERT 0 1

I0611 19:43:10.814085   119 tablet.cc:1489] T 3ecc7bc3dcc049d0b7e9976c7971adde P db20a8214add45e69bd4f1e168772389: Wrote 2 key/value pairs to kRegular RocksDB:
  T 3ecc7bc3dcc049d0b7e9976c7971adde P db20a8214add45e69bd4f1e168772389 [R]: 1. PutCF: SubDocKey(DocKey(0xbf4f, [42], []), [SystemColumnId(0); HT{ physical: 1686512590795474 }]) => null
  T 3ecc7bc3dcc049d0b7e9976c7971adde P db20a8214add45e69bd4f1e168772389 [R]: 2. PutCF: SubDocKey(DocKey(0xbf4f, [42], []), [ColumnId(1); HT{ physical: 1686512590795474 w: 1 }]) => "answer"
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

مهمترین اطلاعات اینجاست: Wrote 2 key/value pairs to kRegular RocksDB. دو کلید/مقدار در خطوط بعدی به تفصیل آمده است. هر دو کلید دارند ([42] با مقدار هش آن در جلو برای به اشتراک گذاری). یکی برای خود ردیف است (SystemColumnId(0)، و یکی برای اولین ستون بدون کلید است (ColumnId(1)) تنظیم مقدار آن (=> "answer"). آنها هر دو نسخه MVCC خود را به عنوان Hybrid Time دارند (HT) با دوران به عنوان زمان فیزیکی آن.

تراکنش های توزیع شده SQL

این مثال ساده و سریع بود. ردیف جدول SQL به عنوان یک سند با مقدار کلید-مقدار واحد درج شد. YugabyteDB به طور خودکار از “مسیر سریع” در اینجا استفاده کرده است. با این حال، تراکنش های SQL می تواند پیچیده تر از این باشد:

  • چندین ردیف، که می توانند در گره های مختلف باشند
  • برای بررسی کلیدهای خارجی جداول دیگر را بخوانید
  • به روز رسانی شاخص های ثانویه
  • به جداول اضافی می پیوندد
  • بیانیه حالت در یک تراکنش
  • معاملات چند صورته

اولاً، در یک تراکنش پیچیده، MVCC Hybrid Timestamp قبل از پایان تراکنش (زمان COMMIT) مشخص نیست. این بدان معنی است که ما نمی توانیم به طور مستقیم کلید / مقدار را همانطور که در بالا دیدیم بنویسیم. سوابق موقت تراکنش با ارجاع به جدول تراکنش در ساختار دیگری از همان رایانه لوحی نوشته می شود. در commit، وضعیت در جدول تراکنش تنظیم می‌شود و تبلت‌ها می‌توانند رکوردهای موقت را در ساختار نهایی اعمال کنند.

این ساختارها LSM-Tree: IntentsDB برای رکوردهای موقت و RegularDB برای رکوردهای نهایی هستند. در ردیابی بالا، کلید/مقدارها مستقیماً در قسمت نوشته شده بودند kRegular RocksDB و هر عملیات با برچسب گذاری شد [R] برای شناسایی پایگاه داده معمولی

YugabyteDB

بیایید دو ردیف را در یک تراکنش وارد کنیم:

yugabyte=#  insert into demo values(1,'HitchHiker'),(2,'Restaurant');
INSERT 0 2

I0611 20:07:45.474311  3384 tablet.cc:1489] T 3ecc7bc3dcc049d0b7e9976c7971adde P db20a8214add45e69bd4f1e168772389: Wrote 15 key/value pairs to kIntents RocksDB:
  T 3ecc7bc3dcc049d0b7e9976c7971adde P db20a8214add45e69bd4f1e168772389 [I]: 1. PutCF: TXN META 4c256c42-2ee1-4be1-8c8f-002e477c3280 => { transaction_id: 4c256c42-2ee1-4be1-8c8f-002e477c3280 isolation: SNAPSHOT_ISOLATION status_tablet: 5ec8daec64cd451395b8306ad26e4c80 priority: 5525364300130340406 start_time: { physical: 1686514065448922 } locality: GLOBAL old_status_tablet:  external_transaction: 0}
  T 3ecc7bc3dcc049d0b7e9976c7971adde P db20a8214add45e69bd4f1e168772389 [I]: 2. PutCF: SubDocKey(DocKey(0xeda9, [1], []), [SystemColumnId(0)]) [kStrongRead, kStrongWrite] HT{ physical: 1686514065465871 } => TransactionId(4c256c42-2ee1-4be1-8c8f-002e477c3280) WriteId(0) null
  T 3ecc7bc3dcc049d0b7e9976c7971adde P db20a8214add45e69bd4f1e168772389 [I]: 3. PutCF: TXN REV 4c256c42-2ee1-4be1-8c8f-002e477c3280 HT{ physical: 1686514065465871 } => SubDocKey(DocKey(0xeda9, [1], []), [SystemColumnId(0)]) [kStrongRead, kStrongWrite] HT{ physical: 1686514065465871 }
  T 3ecc7bc3dcc049d0b7e9976c7971adde P db20a8214add45e69bd4f1e168772389 [I]: 4. PutCF: SubDocKey(DocKey(0xeda9, [1], []), [ColumnId(1)]) [kStrongRead, kStrongWrite] HT{ physical: 1686514065465871 w: 1 } => TransactionId(4c256c42-2ee1-4be1-8c8f-002e477c3280) WriteId(1) "HitchHiker"
  T 3ecc7bc3dcc049d0b7e9976c7971adde P db20a8214add45e69bd4f1e168772389 [I]: 5. PutCF: TXN REV 4c256c42-2ee1-4be1-8c8f-002e477c3280 HT{ physical: 1686514065465871 w: 1 } => SubDocKey(DocKey(0xeda9, [1], []), [ColumnId(1)]) [kStrongRead, kStrongWrite] HT{ physical: 1686514065465871 w: 1 }
  T 3ecc7bc3dcc049d0b7e9976c7971adde P db20a8214add45e69bd4f1e168772389 [I]: 6. PutCF: SubDocKey(DocKey(0xcfaa, [2], []), [SystemColumnId(0)]) [kStrongRead, kStrongWrite] HT{ physical: 1686514065465871 w: 2 } => TransactionId(4c256c42-2ee1-4be1-8c8f-002e477c3280) WriteId(2) null
  T 3ecc7bc3dcc049d0b7e9976c7971adde P db20a8214add45e69bd4f1e168772389 [I]: 7. PutCF: TXN REV 4c256c42-2ee1-4be1-8c8f-002e477c3280 HT{ physical: 1686514065465871 w: 2 } => SubDocKey(DocKey(0xcfaa, [2], []), [SystemColumnId(0)]) [kStrongRead, kStrongWrite] HT{ physical: 1686514065465871 w: 2 }
  T 3ecc7bc3dcc049d0b7e9976c7971adde P db20a8214add45e69bd4f1e168772389 [I]: 8. PutCF: SubDocKey(DocKey(0xcfaa, [2], []), [ColumnId(1)]) [kStrongRead, kStrongWrite] HT{ physical: 1686514065465871 w: 3 } => TransactionId(4c256c42-2ee1-4be1-8c8f-002e477c3280) WriteId(3) "Restaurant"
  T 3ecc7bc3dcc049d0b7e9976c7971adde P db20a8214add45e69bd4f1e168772389 [I]: 9. PutCF: TXN REV 4c256c42-2ee1-4be1-8c8f-002e477c3280 HT{ physical: 1686514065465871 w: 3 } => SubDocKey(DocKey(0xcfaa, [2], []), [ColumnId(1)]) [kStrongRead, kStrongWrite] HT{ physical: 1686514065465871 w: 3 }
  T 3ecc7bc3dcc049d0b7e9976c7971adde P db20a8214add45e69bd4f1e168772389 [I]: 10. PutCF: SubDocKey(DocKey(0xcfaa, [2], []), []) [kWeakRead, kWeakWrite] HT{ physical: 1686514065465871 w: 4 } => TransactionId(4c256c42-2ee1-4be1-8c8f-002e477c3280) none
  T 3ecc7bc3dcc049d0b7e9976c7971adde P db20a8214add45e69bd4f1e168772389 [I]: 11. PutCF: TXN REV 4c256c42-2ee1-4be1-8c8f-002e477c3280 HT{ physical: 1686514065465871 w: 4 } => SubDocKey(DocKey(0xcfaa, [2], []), []) [kWeakRead, kWeakWrite] HT{ physical: 1686514065465871 w: 4 }
  T 3ecc7bc3dcc049d0b7e9976c7971adde P db20a8214add45e69bd4f1e168772389 [I]: 12. PutCF: SubDocKey(DocKey(0xeda9, [1], []), []) [kWeakRead, kWeakWrite] HT{ physical: 1686514065465871 w: 5 } => TransactionId(4c256c42-2ee1-4be1-8c8f-002e477c3280) none
  T 3ecc7bc3dcc049d0b7e9976c7971adde P db20a8214add45e69bd4f1e168772389 [I]: 13. PutCF: TXN REV 4c256c42-2ee1-4be1-8c8f-002e477c3280 HT{ physical: 1686514065465871 w: 5 } => SubDocKey(DocKey(0xeda9, [1], []), []) [kWeakRead, kWeakWrite] HT{ physical: 1686514065465871 w: 5 }
  T 3ecc7bc3dcc049d0b7e9976c7971adde P db20a8214add45e69bd4f1e168772389 [I]: 14. PutCF: SubDocKey(DocKey([], []), []) [kWeakRead, kWeakWrite] HT{ physical: 1686514065465871 w: 6 } => TransactionId(4c256c42-2ee1-4be1-8c8f-002e477c3280) none
  T 3ecc7bc3dcc049d0b7e9976c7971adde P db20a8214add45e69bd4f1e168772389 [I]: 15. PutCF: TXN REV 4c256c42-2ee1-4be1-8c8f-002e477c3280 HT{ physical: 1686514065465871 w: 6 } => SubDocKey(DocKey([], []), []) [kWeakRead, kWeakWrite] HT{ physical: 1686514065465871 w: 6 }
I0611 20:07:45.476101   119 tablet.cc:1489] T 3ecc7bc3dcc049d0b7e9976c7971adde P db20a8214add45e69bd4f1e168772389: Wrote 4 key/value pairs to kRegular RocksDB:
  T 3ecc7bc3dcc049d0b7e9976c7971adde P db20a8214add45e69bd4f1e168772389 [R]: 1. PutCF: SubDocKey(DocKey(0xeda9, [1], []), [SystemColumnId(0); HT{ physical: 1686514065475595 }]) => null; intent doc ht: HT{ physical: 1686514065465871 }
  T 3ecc7bc3dcc049d0b7e9976c7971adde P db20a8214add45e69bd4f1e168772389 [R]: 2. PutCF: SubDocKey(DocKey(0xeda9, [1], []), [ColumnId(1); HT{ physical: 1686514065475595 w: 1 }]) => "HitchHiker"; intent doc ht: HT{ physical: 1686514065465871 w: 1 }
  T 3ecc7bc3dcc049d0b7e9976c7971adde P db20a8214add45e69bd4f1e168772389 [R]: 3. PutCF: SubDocKey(DocKey(0xcfaa, [2], []), [SystemColumnId(0); HT{ physical: 1686514065475595 w: 2 }]) => null; intent doc ht: HT{ physical: 1686514065465871 w: 2 }
  T 3ecc7bc3dcc049d0b7e9976c7971adde P db20a8214add45e69bd4f1e168772389 [R]: 4. PutCF: SubDocKey(DocKey(0xcfaa, [2], []), [ColumnId(1); HT{ physical: 1686514065475595 w: 3 }]) => "Restaurant"; intent doc ht: HT{ physical: 1686514065465871 w: 3 }
I0611 20:07:45.476333   200 tablet.cc:1489] T 3ecc7bc3dcc049d0b7e9976c7971adde P db20a8214add45e69bd4f1e168772389: Wrote 15 key/value pairs to kIntents RocksDB:
  T 3ecc7bc3dcc049d0b7e9976c7971adde P db20a8214add45e69bd4f1e168772389 [I]: 1. SingleDeleteCF: TXN META 4c256c42-2ee1-4be1-8c8f-002e477c3280
  T 3ecc7bc3dcc049d0b7e9976c7971adde P db20a8214add45e69bd4f1e168772389 [I]: 2. SingleDeleteCF: TXN REV 4c256c42-2ee1-4be1-8c8f-002e477c3280 HT{ physical: 1686514065465871 }
  T 3ecc7bc3dcc049d0b7e9976c7971adde P db20a8214add45e69bd4f1e168772389 [I]: 3. SingleDeleteCF: SubDocKey(DocKey(0xeda9, [1], []), [SystemColumnId(0)]) [kStrongRead, kStrongWrite] HT{ physical: 1686514065465871 }
  T 3ecc7bc3dcc049d0b7e9976c7971adde P db20a8214add45e69bd4f1e168772389 [I]: 4. SingleDeleteCF: TXN REV 4c256c42-2ee1-4be1-8c8f-002e477c3280 HT{ physical: 1686514065465871 w: 1 }
  T 3ecc7bc3dcc049d0b7e9976c7971adde P db20a8214add45e69bd4f1e168772389 [I]: 5. SingleDeleteCF: SubDocKey(DocKey(0xeda9, [1], []), [ColumnId(1)]) [kStrongRead, kStrongWrite] HT{ physical: 1686514065465871 w: 1 }
  T 3ecc7bc3dcc049d0b7e9976c7971adde P db20a8214add45e69bd4f1e168772389 [I]: 6. SingleDeleteCF: TXN REV 4c256c42-2ee1-4be1-8c8f-002e477c3280 HT{ physical: 1686514065465871 w: 2 }
  T 3ecc7bc3dcc049d0b7e9976c7971adde P db20a8214add45e69bd4f1e168772389 [I]: 7. SingleDeleteCF: SubDocKey(DocKey(0xcfaa, [2], []), [SystemColumnId(0)]) [kStrongRead, kStrongWrite] HT{ physical: 1686514065465871 w: 2 }
  T 3ecc7bc3dcc049d0b7e9976c7971adde P db20a8214add45e69bd4f1e168772389 [I]: 8. SingleDeleteCF: TXN REV 4c256c42-2ee1-4be1-8c8f-002e477c3280 HT{ physical: 1686514065465871 w: 3 }
  T 3ecc7bc3dcc049d0b7e9976c7971adde P db20a8214add45e69bd4f1e168772389 [I]: 9. SingleDeleteCF: SubDocKey(DocKey(0xcfaa, [2], []), [ColumnId(1)]) [kStrongRead, kStrongWrite] HT{ physical: 1686514065465871 w: 3 }
  T 3ecc7bc3dcc049d0b7e9976c7971adde P db20a8214add45e69bd4f1e168772389 [I]: 10. SingleDeleteCF: TXN REV 4c256c42-2ee1-4be1-8c8f-002e477c3280 HT{ physical: 1686514065465871 w: 4 }
  T 3ecc7bc3dcc049d0b7e9976c7971adde P db20a8214add45e69bd4f1e168772389 [I]: 11. SingleDeleteCF: SubDocKey(DocKey(0xcfaa, [2], []), []) [kWeakRead, kWeakWrite] HT{ physical: 1686514065465871 w: 4 }
  T 3ecc7bc3dcc049d0b7e9976c7971adde P db20a8214add45e69bd4f1e168772389 [I]: 12. SingleDeleteCF: TXN REV 4c256c42-2ee1-4be1-8c8f-002e477c3280 HT{ physical: 1686514065465871 w: 5 }
  T 3ecc7bc3dcc049d0b7e9976c7971adde P db20a8214add45e69bd4f1e168772389 [I]: 13. SingleDeleteCF: SubDocKey(DocKey(0xeda9, [1], []), []) [kWeakRead, kWeakWrite] HT{ physical: 1686514065465871 w: 5 }
  T 3ecc7bc3dcc049d0b7e9976c7971adde P db20a8214add45e69bd4f1e168772389 [I]: 14. SingleDeleteCF: TXN REV 4c256c42-2ee1-4be1-8c8f-002e477c3280 HT{ physical: 1686514065465871 w: 6 }
  T 3ecc7bc3dcc049d0b7e9976c7971adde P db20a8214add45e69bd4f1e168772389 [I]: 15. SingleDeleteCF: SubDocKey(DocKey([], []), []) [kWeakRead, kWeakWrite] HT{ physical: 1686514065465871 w: 6 }
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

در اینجا موارد بسیار بیشتری وجود دارد، با 3 دسته نوشته:

  • در طول درج، 15 جفت کلید/مقدار در kIntents RocksDB نوشت
  • بعد از commit، 4 جفت کلید/مقدار در kRegular RocksDB نوشت
  • 15 جفت کلید/مقدار در kIntents RocksDB نوشت

فقط اولی، به kIntents RocksDB، IntentsDB با رکوردهای موقت، در طول فرمان کاربر اتفاق می افتد. پس از انجام تراکنش، تمام جلسات دیگر می توانند سوابق موقت را بخوانند و از وضعیت تراکنش و مُهر زمانی ارزیابی کنند. این دسته اول 15 جفت کلید/مقدار شامل تغییرات، اما همچنین فراداده تراکنش و شاخص معکوس (برای یافتن همه تغییرات مربوط به یک تراکنش) را می نویسد. پس از commit، هر تبلت رکوردهای موقت را روی RegularDB اعمال می‌کند و این 4 جفت کلید/مقدار مشابه چیزی است که در مسیر سریع دیده‌ایم: 1 در هر سطر و 1 در هر ستون بدون کلید. دسته سوم پاکسازی 15 رکورد موقت در IntentsDB است.

آن 15 رکورد موقت چیست؟ آنها همان جفت های کلید/مقدار هستند که در مسیر سریع دیده ایم، به علاوه اطلاعات کنترل تراکنش.

  • 1. PutCF: TXN META ابرداده تراکنش با زمان شروع، سطح جداسازی و غیره است.
  • 2. PutCF: SubDocKey(DocKey(0xeda9, [1], []), [SystemColumnId(0)]) همانطور که در مسیر سریع دیدیم، ورودی ردیف با اطلاعات قفل اضافی است ([kStrongRead, kStrongWrite] برای قصد خواندن و نوشتن) و transactionId که به ابرداده فوق ارجاع می دهد
  • 3. PutCF: TXN REV عملیات قبلی را به شاخص معکوس اضافه می کند.

ما برای همه تغییرات الگوی یکسانی خواهیم داشت به طوری که هر عملیات به تراکنش اشاره می کند و تراکنش دارای لیستی از عملیات است.

به طور خلاصه …

آنچه ما “مسیر سریع” می نامیم دارای ویژگی های زیر است:

  • یک تراکنش تک تکه است (در مقابل تراکنش توزیع شده)
  • مربوط به معاملات تک ردیفی است. در نسخه‌های بعدی، ممکن است همین کار را برای تراکنش‌های چند ردیفی که تک شارد هستند، انجام دهیم.
  • به طور خودکار شناسایی می شود (زمانی که فقط یک ردیف وجود دارد)
  • دور زدن کامل IntentsDB (سوابق موقت و پاکسازی نهایی)
  • نیازی به به روز رسانی جدول تراکنش ندارد (تعهد رافت نوشتن همان تعهد SQL تراکنش است)
  • به عملکرد NoSQL نزدیک است (زیرا NoSQL این عملکرد را با رد کردن هر ویژگی که به عملیات چندشارد نیاز دارد به دست می آورد)

با ردیابی نوشته‌ها در آزمایشگاه، می‌توان دید که تراکنش‌های توزیع‌شده همان کار را انجام می‌دهند، اما با حالت‌های میانی و کار بیشتر. به طور خلاصه، YugabyteDB همه ویژگی‌های SQL را ارائه می‌کند، اما زمانی که تراکنش‌ها به سادگی NoSQL هستند، می‌تواند عملکردی مشابه NoSQL، شفاف و همچنان در یک طرح رابطه‌ای یا سندی ارائه دهد.

نوشته های مشابه

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *

همچنین ببینید
بستن
دکمه بازگشت به بالا